[
  {
    "path": ".editorconfig",
    "content": "[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\nmax_line_length = 120\ntab_width = 4\nij_continuation_indent_size = 8\nij_formatter_off_tag = @formatter:off\nij_formatter_on_tag = @formatter:on\nij_formatter_tags_enabled = true\nij_smart_tabs = false\nij_visual_guides = none\nij_wrap_on_typing = false\n\n[*.java]\nij_java_align_consecutive_assignments = false\nij_java_align_consecutive_variable_declarations = false\nij_java_align_group_field_declarations = false\nij_java_align_multiline_annotation_parameters = false\nij_java_align_multiline_array_initializer_expression = false\nij_java_align_multiline_assignment = false\nij_java_align_multiline_binary_operation = false\nij_java_align_multiline_chained_methods = false\nij_java_align_multiline_deconstruction_list_components = true\nij_java_align_multiline_extends_list = false\nij_java_align_multiline_for = true\nij_java_align_multiline_method_parentheses = false\nij_java_align_multiline_parameters = true\nij_java_align_multiline_parameters_in_calls = false\nij_java_align_multiline_parenthesized_expression = false\nij_java_align_multiline_records = true\nij_java_align_multiline_resources = true\nij_java_align_multiline_ternary_operation = false\nij_java_align_multiline_text_blocks = false\nij_java_align_multiline_throws_list = false\nij_java_align_subsequent_simple_methods = false\nij_java_align_throws_keyword = false\nij_java_align_types_in_multi_catch = true\nij_java_annotation_parameter_wrap = off\nij_java_array_initializer_new_line_after_left_brace = false\nij_java_array_initializer_right_brace_on_new_line = false\nij_java_array_initializer_wrap = off\nij_java_assert_statement_colon_on_next_line = false\nij_java_assert_statement_wrap = off\nij_java_assignment_wrap = off\nij_java_binary_operation_sign_on_next_line = true\nij_java_binary_operation_wrap = off\nij_java_blank_lines_after_anonymous_class_header = 0\nij_java_blank_lines_after_class_header = 0\nij_java_blank_lines_after_imports = 1\nij_java_blank_lines_after_package = 1\nij_java_blank_lines_around_class = 1\nij_java_blank_lines_around_field = 0\nij_java_blank_lines_around_field_in_interface = 0\nij_java_blank_lines_around_initializer = 1\nij_java_blank_lines_around_method = 1\nij_java_blank_lines_around_method_in_interface = 1\nij_java_blank_lines_before_class_end = 0\nij_java_blank_lines_before_imports = 1\nij_java_blank_lines_before_method_body = 0\nij_java_blank_lines_before_package = 0\nij_java_block_brace_style = end_of_line\nij_java_block_comment_add_space = false\nij_java_block_comment_at_first_column = true\nij_java_builder_methods = none\nij_java_call_parameters_new_line_after_left_paren = false\nij_java_call_parameters_right_paren_on_new_line = false\nij_java_call_parameters_wrap = off\nij_java_case_statement_on_separate_line = true\nij_java_catch_on_new_line = false\nij_java_class_annotation_wrap = split_into_lines\nij_java_class_brace_style = end_of_line\nij_java_class_count_to_use_import_on_demand = 99\nij_java_class_names_in_javadoc = 3\nij_java_deconstruction_list_wrap = normal\nij_java_do_not_indent_top_level_class_members = false\nij_java_do_not_wrap_after_single_annotation = false\nij_java_do_not_wrap_after_single_annotation_in_parameter = false\nij_java_do_while_brace_force = never\nij_java_doc_add_blank_line_after_description = true\nij_java_doc_add_blank_line_after_param_comments = false\nij_java_doc_add_blank_line_after_return = false\nij_java_doc_add_p_tag_on_empty_lines = false\nij_java_doc_align_exception_comments = true\nij_java_doc_align_param_comments = true\nij_java_doc_do_not_wrap_if_one_line = false\nij_java_doc_enable_formatting = true\nij_java_doc_enable_leading_asterisks = true\nij_java_doc_indent_on_continuation = false\nij_java_doc_keep_empty_lines = true\nij_java_doc_keep_empty_parameter_tag = true\nij_java_doc_keep_empty_return_tag = true\nij_java_doc_keep_empty_throws_tag = true\nij_java_doc_keep_invalid_tags = true\nij_java_doc_param_description_on_new_line = false\nij_java_doc_preserve_line_breaks = false\nij_java_doc_use_throws_not_exception_tag = true\nij_java_else_on_new_line = false\nij_java_enum_constants_wrap = off\nij_java_extends_keyword_wrap = off\nij_java_extends_list_wrap = off\nij_java_field_annotation_wrap = split_into_lines\nij_java_field_name_prefix = m\nij_java_finally_on_new_line = false\nij_java_for_brace_force = never\nij_java_for_statement_new_line_after_left_paren = false\nij_java_for_statement_right_paren_on_new_line = false\nij_java_for_statement_wrap = off\nij_java_generate_final_locals = false\nij_java_generate_final_parameters = false\nij_java_if_brace_force = never\nij_java_imports_layout = android.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,|,$*,|\nij_java_indent_case_from_switch = true\nij_java_insert_inner_class_imports = false\nij_java_insert_override_annotation = true\nij_java_keep_blank_lines_before_right_brace = 2\nij_java_keep_blank_lines_between_package_declaration_and_header = 2\nij_java_keep_blank_lines_in_code = 2\nij_java_keep_blank_lines_in_declarations = 2\nij_java_keep_builder_methods_indents = false\nij_java_keep_control_statement_in_one_line = true\nij_java_keep_first_column_comment = true\nij_java_keep_indents_on_empty_lines = false\nij_java_keep_line_breaks = true\nij_java_keep_multiple_expressions_in_one_line = false\nij_java_keep_simple_blocks_in_one_line = false\nij_java_keep_simple_classes_in_one_line = false\nij_java_keep_simple_lambdas_in_one_line = false\nij_java_keep_simple_methods_in_one_line = false\nij_java_label_indent_absolute = false\nij_java_label_indent_size = 0\nij_java_lambda_brace_style = end_of_line\nij_java_layout_static_imports_separately = true\nij_java_line_comment_add_space = false\nij_java_line_comment_add_space_on_reformat = false\nij_java_line_comment_at_first_column = true\nij_java_method_annotation_wrap = split_into_lines\nij_java_method_brace_style = end_of_line\nij_java_method_call_chain_wrap = off\nij_java_method_parameters_new_line_after_left_paren = false\nij_java_method_parameters_right_paren_on_new_line = false\nij_java_method_parameters_wrap = off\nij_java_modifier_list_wrap = false\nij_java_multi_catch_types_wrap = normal\nij_java_names_count_to_use_import_on_demand = 99\nij_java_new_line_after_lparen_in_annotation = false\nij_java_new_line_after_lparen_in_deconstruction_pattern = true\nij_java_new_line_after_lparen_in_record_header = false\nij_java_parameter_annotation_wrap = off\nij_java_parentheses_expression_new_line_after_left_paren = false\nij_java_parentheses_expression_right_paren_on_new_line = false\nij_java_place_assignment_sign_on_next_line = false\nij_java_prefer_longer_names = true\nij_java_prefer_parameters_wrap = false\nij_java_record_components_wrap = normal\nij_java_repeat_synchronized = true\nij_java_replace_instanceof_and_cast = false\nij_java_replace_null_check = true\nij_java_replace_sum_lambda_with_method_ref = true\nij_java_resource_list_new_line_after_left_paren = false\nij_java_resource_list_right_paren_on_new_line = false\nij_java_resource_list_wrap = off\nij_java_rparen_on_new_line_in_annotation = false\nij_java_rparen_on_new_line_in_deconstruction_pattern = true\nij_java_rparen_on_new_line_in_record_header = false\nij_java_space_after_closing_angle_bracket_in_type_argument = false\nij_java_space_after_colon = true\nij_java_space_after_comma = true\nij_java_space_after_comma_in_type_arguments = true\nij_java_space_after_for_semicolon = true\nij_java_space_after_quest = true\nij_java_space_after_type_cast = true\nij_java_space_before_annotation_array_initializer_left_brace = false\nij_java_space_before_annotation_parameter_list = false\nij_java_space_before_array_initializer_left_brace = false\nij_java_space_before_catch_keyword = true\nij_java_space_before_catch_left_brace = true\nij_java_space_before_catch_parentheses = true\nij_java_space_before_class_left_brace = true\nij_java_space_before_colon = true\nij_java_space_before_colon_in_foreach = true\nij_java_space_before_comma = false\nij_java_space_before_deconstruction_list = false\nij_java_space_before_do_left_brace = true\nij_java_space_before_else_keyword = true\nij_java_space_before_else_left_brace = true\nij_java_space_before_finally_keyword = true\nij_java_space_before_finally_left_brace = true\nij_java_space_before_for_left_brace = true\nij_java_space_before_for_parentheses = true\nij_java_space_before_for_semicolon = false\nij_java_space_before_if_left_brace = true\nij_java_space_before_if_parentheses = true\nij_java_space_before_method_call_parentheses = false\nij_java_space_before_method_left_brace = true\nij_java_space_before_method_parentheses = false\nij_java_space_before_opening_angle_bracket_in_type_parameter = false\nij_java_space_before_quest = true\nij_java_space_before_switch_left_brace = true\nij_java_space_before_switch_parentheses = true\nij_java_space_before_synchronized_left_brace = true\nij_java_space_before_synchronized_parentheses = true\nij_java_space_before_try_left_brace = true\nij_java_space_before_try_parentheses = true\nij_java_space_before_type_parameter_list = false\nij_java_space_before_while_keyword = true\nij_java_space_before_while_left_brace = true\nij_java_space_before_while_parentheses = true\nij_java_space_inside_one_line_enum_braces = false\nij_java_space_within_empty_array_initializer_braces = false\nij_java_space_within_empty_method_call_parentheses = false\nij_java_space_within_empty_method_parentheses = false\nij_java_spaces_around_additive_operators = true\nij_java_spaces_around_annotation_eq = true\nij_java_spaces_around_assignment_operators = true\nij_java_spaces_around_bitwise_operators = true\nij_java_spaces_around_equality_operators = true\nij_java_spaces_around_lambda_arrow = true\nij_java_spaces_around_logical_operators = true\nij_java_spaces_around_method_ref_dbl_colon = false\nij_java_spaces_around_multiplicative_operators = true\nij_java_spaces_around_relational_operators = true\nij_java_spaces_around_shift_operators = true\nij_java_spaces_around_type_bounds_in_type_parameters = true\nij_java_spaces_around_unary_operator = false\nij_java_spaces_within_angle_brackets = false\nij_java_spaces_within_annotation_parentheses = false\nij_java_spaces_within_array_initializer_braces = false\nij_java_spaces_within_braces = false\nij_java_spaces_within_brackets = false\nij_java_spaces_within_cast_parentheses = false\nij_java_spaces_within_catch_parentheses = false\nij_java_spaces_within_deconstruction_list = false\nij_java_spaces_within_for_parentheses = false\nij_java_spaces_within_if_parentheses = false\nij_java_spaces_within_method_call_parentheses = false\nij_java_spaces_within_method_parentheses = false\nij_java_spaces_within_parentheses = false\nij_java_spaces_within_record_header = false\nij_java_spaces_within_switch_parentheses = false\nij_java_spaces_within_synchronized_parentheses = false\nij_java_spaces_within_try_parentheses = false\nij_java_spaces_within_while_parentheses = false\nij_java_special_else_if_treatment = true\nij_java_subclass_name_suffix = Impl\nij_java_ternary_operation_signs_on_next_line = false\nij_java_ternary_operation_wrap = off\nij_java_test_name_suffix = Test\nij_java_throws_keyword_wrap = off\nij_java_throws_list_wrap = off\nij_java_use_external_annotations = false\nij_java_use_fq_class_names = false\nij_java_use_relative_indents = false\nij_java_use_single_class_imports = true\nij_java_variable_annotation_wrap = off\nij_java_visibility = public\nij_java_while_brace_force = never\nij_java_while_on_new_line = false\nij_java_wrap_comments = false\nij_java_wrap_first_method_in_call_chain = false\nij_java_wrap_long_lines = false\n\n[*.markdown]\nij_markdown_force_one_space_after_blockquote_symbol = true\nij_markdown_force_one_space_after_header_symbol = true\nij_markdown_force_one_space_after_list_bullet = true\nij_markdown_force_one_space_between_words = true\nij_markdown_format_tables = true\nij_markdown_insert_quote_arrows_on_wrap = true\nij_markdown_keep_indents_on_empty_lines = false\nij_markdown_keep_line_breaks_inside_text_blocks = true\nij_markdown_max_lines_around_block_elements = 1\nij_markdown_max_lines_around_header = 1\nij_markdown_max_lines_between_paragraphs = 1\nij_markdown_min_lines_around_block_elements = 1\nij_markdown_min_lines_around_header = 1\nij_markdown_min_lines_between_paragraphs = 1\nij_markdown_wrap_text_if_long = true\nij_markdown_wrap_text_inside_blockquotes = true\n\n[*.properties]\nij_properties_align_group_field_declarations = false\nij_properties_keep_blank_lines = false\nij_properties_key_value_delimiter = equals\nij_properties_spaces_around_key_value_delimiter = false\n\n[.editorconfig]\nij_editorconfig_align_group_field_declarations = false\nij_editorconfig_space_after_colon = false\nij_editorconfig_space_after_comma = true\nij_editorconfig_space_before_colon = false\nij_editorconfig_space_before_comma = false\nij_editorconfig_spaces_around_assignment_operators = true\n\n[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]\nij_continuation_indent_size = 4\nij_xml_align_attributes = false\nij_xml_align_text = false\nij_xml_attribute_wrap = normal\nij_xml_block_comment_add_space = false\nij_xml_block_comment_at_first_column = true\nij_xml_keep_blank_lines = 2\nij_xml_keep_indents_on_empty_lines = false\nij_xml_keep_line_breaks = false\nij_xml_keep_line_breaks_in_text = true\nij_xml_keep_whitespaces = false\nij_xml_keep_whitespaces_around_cdata = preserve\nij_xml_keep_whitespaces_inside_cdata = false\nij_xml_line_comment_at_first_column = true\nij_xml_space_after_tag_name = false\nij_xml_space_around_equals_in_attribute = false\nij_xml_space_inside_empty_tag = true\nij_xml_text_wrap = normal\nij_xml_use_custom_settings = true\n\n[{*.apinotes,*.yaml,*.yml,.clang-format,.clang-tidy,_clang-format}]\nindent_size = 2\nij_yaml_align_values_properties = do_not_align\nij_yaml_autoinsert_sequence_marker = true\nij_yaml_block_mapping_on_new_line = false\nij_yaml_indent_sequence_value = true\nij_yaml_keep_indents_on_empty_lines = false\nij_yaml_keep_line_breaks = true\nij_yaml_sequence_on_new_line = false\nij_yaml_space_before_colon = false\nij_yaml_spaces_within_braces = true\nij_yaml_spaces_within_brackets = true\n\n[{*.bash,*.sh,*.zsh}]\nindent_size = 2\ntab_width = 2\nij_shell_binary_ops_start_line = false\nij_shell_keep_column_alignment_padding = false\nij_shell_minify_program = false\nij_shell_redirect_followed_by_space = false\nij_shell_switch_cases_indented = false\nij_shell_use_unix_line_separator = true\n\n[{*.c,*.c++,*.c++m,*.cc,*.ccm,*.cp,*.cpp,*.cppm,*.cu,*.cuh,*.cxx,*.cxxm,*.h,*.h++,*.hh,*.hp,*.hpp,*.hxx,*.i,*.icc,*.ii,*.inl,*.ino,*.ipp,*.ixx,*.m,*.mm,*.mxx,*.pch,*.tcc,*.tpp}]\nij_c_add_brief_tag = false\nij_c_add_getter_prefix = true\nij_c_add_setter_prefix = true\nij_c_align_dictionary_pair_values = false\nij_c_align_group_field_declarations = false\nij_c_align_init_list_in_columns = true\nij_c_align_multiline_array_initializer_expression = true\nij_c_align_multiline_assignment = true\nij_c_align_multiline_binary_operation = true\nij_c_align_multiline_chained_methods = false\nij_c_align_multiline_for = true\nij_c_align_multiline_ternary_operation = true\nij_c_array_initializer_comma_on_next_line = false\nij_c_array_initializer_new_line_after_left_brace = false\nij_c_array_initializer_right_brace_on_new_line = false\nij_c_array_initializer_wrap = normal\nij_c_assignment_wrap = off\nij_c_binary_operation_sign_on_next_line = false\nij_c_binary_operation_wrap = normal\nij_c_blank_lines_after_class_header = 0\nij_c_blank_lines_after_imports = 1\nij_c_blank_lines_around_class = 1\nij_c_blank_lines_around_field = 0\nij_c_blank_lines_around_field_in_interface = 0\nij_c_blank_lines_around_method = 1\nij_c_blank_lines_around_method_in_interface = 1\nij_c_blank_lines_around_namespace = 0\nij_c_blank_lines_around_properties_in_declaration = 0\nij_c_blank_lines_around_properties_in_interface = 0\nij_c_blank_lines_before_imports = 1\nij_c_blank_lines_before_method_body = 0\nij_c_block_brace_placement = end_of_line\nij_c_block_brace_style = end_of_line\nij_c_block_comment_at_first_column = true\nij_c_catch_on_new_line = false\nij_c_class_brace_style = end_of_line\nij_c_class_constructor_init_list_align_multiline = true\nij_c_class_constructor_init_list_comma_on_next_line = false\nij_c_class_constructor_init_list_new_line_after_colon = never\nij_c_class_constructor_init_list_new_line_before_colon = if_long\nij_c_class_constructor_init_list_wrap = normal\nij_c_copy_is_deep = false\nij_c_create_interface_for_categories = true\nij_c_declare_generated_methods = true\nij_c_description_include_member_names = true\nij_c_discharged_short_ternary_operator = false\nij_c_do_not_add_breaks = false\nij_c_do_while_brace_force = never\nij_c_else_on_new_line = false\nij_c_enum_constants_comma_on_next_line = false\nij_c_enum_constants_wrap = on_every_item\nij_c_for_brace_force = never\nij_c_for_statement_new_line_after_left_paren = false\nij_c_for_statement_right_paren_on_new_line = false\nij_c_for_statement_wrap = off\nij_c_function_brace_placement = end_of_line\nij_c_function_call_arguments_align_multiline = true\nij_c_function_call_arguments_align_multiline_pars = false\nij_c_function_call_arguments_comma_on_next_line = false\nij_c_function_call_arguments_new_line_after_lpar = false\nij_c_function_call_arguments_new_line_before_rpar = false\nij_c_function_call_arguments_wrap = normal\nij_c_function_non_top_after_return_type_wrap = normal\nij_c_function_parameters_align_multiline = true\nij_c_function_parameters_align_multiline_pars = false\nij_c_function_parameters_comma_on_next_line = false\nij_c_function_parameters_new_line_after_lpar = false\nij_c_function_parameters_new_line_before_rpar = false\nij_c_function_parameters_wrap = normal\nij_c_function_top_after_return_type_wrap = normal\nij_c_generate_additional_eq_operators = true\nij_c_generate_additional_rel_operators = true\nij_c_generate_class_constructor = true\nij_c_generate_comparison_operators_use_std_tie = false\nij_c_generate_instance_variables_for_properties = ask\nij_c_generate_operators_as_members = true\nij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT}\nij_c_if_brace_force = never\nij_c_in_line_short_ternary_operator = true\nij_c_indent_block_comment = true\nij_c_indent_c_struct_members = 4\nij_c_indent_case_from_switch = true\nij_c_indent_class_members = 4\nij_c_indent_directive_as_code = false\nij_c_indent_implementation_members = 0\nij_c_indent_inside_code_block = 4\nij_c_indent_interface_members = 0\nij_c_indent_interface_members_except_ivars_block = false\nij_c_indent_namespace_members = 4\nij_c_indent_preprocessor_directive = 0\nij_c_indent_visibility_keywords = 0\nij_c_insert_override = true\nij_c_insert_virtual_with_override = false\nij_c_introduce_auto_consts = false\nij_c_introduce_auto_vars = false\nij_c_introduce_const_params = false\nij_c_introduce_const_vars = false\nij_c_introduce_constexpr_consts = false\nij_c_introduce_generate_property = false\nij_c_introduce_generate_synthesize = true\nij_c_introduce_globals_to_header = true\nij_c_introduce_prop_to_private_category = false\nij_c_introduce_static_consts = true\nij_c_introduce_use_ns_types = false\nij_c_ivars_prefix = _\nij_c_keep_blank_lines_before_end = 2\nij_c_keep_blank_lines_before_right_brace = 2\nij_c_keep_blank_lines_in_code = 2\nij_c_keep_blank_lines_in_declarations = 2\nij_c_keep_case_expressions_in_one_line = false\nij_c_keep_control_statement_in_one_line = true\nij_c_keep_directive_at_first_column = true\nij_c_keep_first_column_comment = true\nij_c_keep_line_breaks = true\nij_c_keep_nested_namespaces_in_one_line = false\nij_c_keep_simple_blocks_in_one_line = true\nij_c_keep_simple_methods_in_one_line = true\nij_c_keep_structures_in_one_line = false\nij_c_lambda_capture_list_align_multiline = false\nij_c_lambda_capture_list_align_multiline_bracket = false\nij_c_lambda_capture_list_comma_on_next_line = false\nij_c_lambda_capture_list_new_line_after_lbracket = false\nij_c_lambda_capture_list_new_line_before_rbracket = false\nij_c_lambda_capture_list_wrap = off\nij_c_line_comment_add_space = false\nij_c_line_comment_at_first_column = true\nij_c_method_brace_placement = end_of_line\nij_c_method_call_arguments_align_by_colons = true\nij_c_method_call_arguments_align_multiline = false\nij_c_method_call_arguments_special_dictionary_pairs_treatment = true\nij_c_method_call_arguments_wrap = off\nij_c_method_call_chain_wrap = off\nij_c_method_parameters_align_by_colons = true\nij_c_method_parameters_align_multiline = false\nij_c_method_parameters_wrap = off\nij_c_namespace_brace_placement = end_of_line\nij_c_parentheses_expression_new_line_after_left_paren = false\nij_c_parentheses_expression_right_paren_on_new_line = false\nij_c_place_assignment_sign_on_next_line = false\nij_c_property_nonatomic = true\nij_c_put_ivars_to_implementation = true\nij_c_refactor_compatibility_aliases_and_classes = true\nij_c_refactor_properties_and_ivars = true\nij_c_release_style = ivar\nij_c_retain_object_parameters_in_constructor = true\nij_c_semicolon_after_method_signature = false\nij_c_shift_operation_align_multiline = true\nij_c_shift_operation_wrap = normal\nij_c_show_non_virtual_functions = false\nij_c_space_after_colon = true\nij_c_space_after_colon_in_foreach = true\nij_c_space_after_colon_in_selector = false\nij_c_space_after_comma = true\nij_c_space_after_cup_in_blocks = false\nij_c_space_after_dictionary_literal_colon = true\nij_c_space_after_for_semicolon = true\nij_c_space_after_init_list_colon = true\nij_c_space_after_method_parameter_type_parentheses = false\nij_c_space_after_method_return_type_parentheses = false\nij_c_space_after_pointer_in_declaration = false\nij_c_space_after_quest = true\nij_c_space_after_reference_in_declaration = false\nij_c_space_after_reference_in_rvalue = false\nij_c_space_after_structures_rbrace = true\nij_c_space_after_superclass_colon = true\nij_c_space_after_type_cast = true\nij_c_space_after_visibility_sign_in_method_declaration = true\nij_c_space_before_autorelease_pool_lbrace = true\nij_c_space_before_catch_keyword = true\nij_c_space_before_catch_left_brace = true\nij_c_space_before_catch_parentheses = true\nij_c_space_before_category_parentheses = true\nij_c_space_before_chained_send_message = true\nij_c_space_before_class_left_brace = true\nij_c_space_before_colon = true\nij_c_space_before_colon_in_foreach = false\nij_c_space_before_comma = false\nij_c_space_before_dictionary_literal_colon = false\nij_c_space_before_do_left_brace = true\nij_c_space_before_else_keyword = true\nij_c_space_before_else_left_brace = true\nij_c_space_before_export_lbrace = true\nij_c_space_before_for_left_brace = true\nij_c_space_before_for_parentheses = true\nij_c_space_before_for_semicolon = false\nij_c_space_before_if_left_brace = true\nij_c_space_before_if_parentheses = true\nij_c_space_before_init_list = false\nij_c_space_before_init_list_colon = true\nij_c_space_before_method_call_parentheses = false\nij_c_space_before_method_left_brace = true\nij_c_space_before_method_parentheses = false\nij_c_space_before_namespace_lbrace = true\nij_c_space_before_pointer_in_declaration = true\nij_c_space_before_property_attributes_parentheses = false\nij_c_space_before_protocols_brackets = true\nij_c_space_before_quest = true\nij_c_space_before_reference_in_declaration = true\nij_c_space_before_superclass_colon = true\nij_c_space_before_switch_left_brace = true\nij_c_space_before_switch_parentheses = true\nij_c_space_before_template_call_lt = false\nij_c_space_before_template_declaration_lt = false\nij_c_space_before_try_left_brace = true\nij_c_space_before_while_keyword = true\nij_c_space_before_while_left_brace = true\nij_c_space_before_while_parentheses = true\nij_c_space_between_adjacent_brackets = false\nij_c_space_between_operator_and_punctuator = false\nij_c_space_within_empty_array_initializer_braces = false\nij_c_spaces_around_additive_operators = true\nij_c_spaces_around_assignment_operators = true\nij_c_spaces_around_bitwise_operators = true\nij_c_spaces_around_equality_operators = true\nij_c_spaces_around_lambda_arrow = true\nij_c_spaces_around_logical_operators = true\nij_c_spaces_around_multiplicative_operators = true\nij_c_spaces_around_pm_operators = false\nij_c_spaces_around_relational_operators = true\nij_c_spaces_around_shift_operators = true\nij_c_spaces_around_unary_operator = false\nij_c_spaces_within_array_initializer_braces = false\nij_c_spaces_within_braces = true\nij_c_spaces_within_brackets = false\nij_c_spaces_within_cast_parentheses = false\nij_c_spaces_within_catch_parentheses = false\nij_c_spaces_within_category_parentheses = false\nij_c_spaces_within_empty_braces = false\nij_c_spaces_within_empty_function_call_parentheses = false\nij_c_spaces_within_empty_function_declaration_parentheses = false\nij_c_spaces_within_empty_lambda_capture_list_bracket = false\nij_c_spaces_within_empty_template_call_ltgt = false\nij_c_spaces_within_empty_template_declaration_ltgt = false\nij_c_spaces_within_for_parentheses = false\nij_c_spaces_within_function_call_parentheses = false\nij_c_spaces_within_function_declaration_parentheses = false\nij_c_spaces_within_if_parentheses = false\nij_c_spaces_within_lambda_capture_list_bracket = false\nij_c_spaces_within_method_parameter_type_parentheses = false\nij_c_spaces_within_method_return_type_parentheses = false\nij_c_spaces_within_parentheses = false\nij_c_spaces_within_property_attributes_parentheses = false\nij_c_spaces_within_protocols_brackets = false\nij_c_spaces_within_send_message_brackets = false\nij_c_spaces_within_structured_binding_list_bracket = false\nij_c_spaces_within_switch_parentheses = false\nij_c_spaces_within_template_call_ltgt = false\nij_c_spaces_within_template_declaration_ltgt = false\nij_c_spaces_within_template_double_gt = true\nij_c_spaces_within_while_parentheses = false\nij_c_special_else_if_treatment = true\nij_c_structured_binding_list_align_multiline = false\nij_c_structured_binding_list_align_multiline_bracket = false\nij_c_structured_binding_list_comma_on_next_line = false\nij_c_structured_binding_list_new_line_after_lbracket = false\nij_c_structured_binding_list_new_line_before_rbracket = false\nij_c_structured_binding_list_wrap = off\nij_c_superclass_list_after_colon = never\nij_c_superclass_list_align_multiline = true\nij_c_superclass_list_before_colon = if_long\nij_c_superclass_list_comma_on_next_line = false\nij_c_superclass_list_wrap = on_every_item\nij_c_tag_prefix_of_block_comment = at\nij_c_tag_prefix_of_line_comment = back_slash\nij_c_template_call_arguments_align_multiline = false\nij_c_template_call_arguments_align_multiline_pars = false\nij_c_template_call_arguments_comma_on_next_line = false\nij_c_template_call_arguments_new_line_after_lt = false\nij_c_template_call_arguments_new_line_before_gt = false\nij_c_template_call_arguments_wrap = off\nij_c_template_declaration_function_body_indent = false\nij_c_template_declaration_function_wrap = split_into_lines\nij_c_template_declaration_struct_body_indent = false\nij_c_template_declaration_struct_wrap = split_into_lines\nij_c_template_parameters_align_multiline = false\nij_c_template_parameters_align_multiline_pars = false\nij_c_template_parameters_comma_on_next_line = false\nij_c_template_parameters_new_line_after_lt = false\nij_c_template_parameters_new_line_before_gt = false\nij_c_template_parameters_wrap = off\nij_c_ternary_operation_signs_on_next_line = true\nij_c_ternary_operation_wrap = normal\nij_c_type_qualifiers_placement = before\nij_c_use_modern_casts = true\nij_c_use_setters_in_constructor = true\nij_c_while_brace_force = never\nij_c_while_on_new_line = false\nij_c_wrap_property_declaration = off\n\n[{*.cmake,CMakeLists.txt}]\nij_cmake_align_multiline_parameters_in_calls = false\nij_cmake_force_commands_case = 2\nij_cmake_keep_blank_lines_in_code = 2\nij_cmake_space_before_for_parentheses = true\nij_cmake_space_before_if_parentheses = true\nij_cmake_space_before_method_call_parentheses = false\nij_cmake_space_before_method_parentheses = false\nij_cmake_space_before_while_parentheses = true\nij_cmake_spaces_within_for_parentheses = false\nij_cmake_spaces_within_if_parentheses = false\nij_cmake_spaces_within_method_call_parentheses = false\nij_cmake_spaces_within_method_parentheses = false\nij_cmake_spaces_within_while_parentheses = false\n\n[{*.gant,*.groovy,*.gy}]\nij_groovy_align_group_field_declarations = false\nij_groovy_align_multiline_array_initializer_expression = false\nij_groovy_align_multiline_assignment = false\nij_groovy_align_multiline_binary_operation = false\nij_groovy_align_multiline_chained_methods = false\nij_groovy_align_multiline_extends_list = false\nij_groovy_align_multiline_for = true\nij_groovy_align_multiline_list_or_map = true\nij_groovy_align_multiline_method_parentheses = false\nij_groovy_align_multiline_parameters = true\nij_groovy_align_multiline_parameters_in_calls = false\nij_groovy_align_multiline_resources = true\nij_groovy_align_multiline_ternary_operation = false\nij_groovy_align_multiline_throws_list = false\nij_groovy_align_named_args_in_map = true\nij_groovy_align_throws_keyword = false\nij_groovy_array_initializer_new_line_after_left_brace = false\nij_groovy_array_initializer_right_brace_on_new_line = false\nij_groovy_array_initializer_wrap = off\nij_groovy_assert_statement_wrap = off\nij_groovy_assignment_wrap = off\nij_groovy_binary_operation_wrap = off\nij_groovy_blank_lines_after_class_header = 0\nij_groovy_blank_lines_after_imports = 1\nij_groovy_blank_lines_after_package = 1\nij_groovy_blank_lines_around_class = 1\nij_groovy_blank_lines_around_field = 0\nij_groovy_blank_lines_around_field_in_interface = 0\nij_groovy_blank_lines_around_method = 1\nij_groovy_blank_lines_around_method_in_interface = 1\nij_groovy_blank_lines_before_imports = 1\nij_groovy_blank_lines_before_method_body = 0\nij_groovy_blank_lines_before_package = 0\nij_groovy_block_brace_style = end_of_line\nij_groovy_block_comment_add_space = false\nij_groovy_block_comment_at_first_column = true\nij_groovy_call_parameters_new_line_after_left_paren = false\nij_groovy_call_parameters_right_paren_on_new_line = false\nij_groovy_call_parameters_wrap = off\nij_groovy_catch_on_new_line = false\nij_groovy_class_annotation_wrap = split_into_lines\nij_groovy_class_brace_style = end_of_line\nij_groovy_class_count_to_use_import_on_demand = 5\nij_groovy_do_while_brace_force = never\nij_groovy_else_on_new_line = false\nij_groovy_enable_groovydoc_formatting = true\nij_groovy_enum_constants_wrap = off\nij_groovy_extends_keyword_wrap = off\nij_groovy_extends_list_wrap = off\nij_groovy_field_annotation_wrap = split_into_lines\nij_groovy_finally_on_new_line = false\nij_groovy_for_brace_force = never\nij_groovy_for_statement_new_line_after_left_paren = false\nij_groovy_for_statement_right_paren_on_new_line = false\nij_groovy_for_statement_wrap = off\nij_groovy_ginq_general_clause_wrap_policy = 2\nij_groovy_ginq_having_wrap_policy = 1\nij_groovy_ginq_indent_having_clause = true\nij_groovy_ginq_indent_on_clause = true\nij_groovy_ginq_on_wrap_policy = 1\nij_groovy_ginq_space_after_keyword = true\nij_groovy_if_brace_force = never\nij_groovy_import_annotation_wrap = 2\nij_groovy_imports_layout = *,|,javax.**,java.**,|,$*\nij_groovy_indent_case_from_switch = true\nij_groovy_indent_label_blocks = true\nij_groovy_insert_inner_class_imports = false\nij_groovy_keep_blank_lines_before_right_brace = 2\nij_groovy_keep_blank_lines_in_code = 2\nij_groovy_keep_blank_lines_in_declarations = 2\nij_groovy_keep_control_statement_in_one_line = true\nij_groovy_keep_first_column_comment = true\nij_groovy_keep_indents_on_empty_lines = false\nij_groovy_keep_line_breaks = true\nij_groovy_keep_multiple_expressions_in_one_line = false\nij_groovy_keep_simple_blocks_in_one_line = false\nij_groovy_keep_simple_classes_in_one_line = true\nij_groovy_keep_simple_lambdas_in_one_line = true\nij_groovy_keep_simple_methods_in_one_line = true\nij_groovy_label_indent_absolute = false\nij_groovy_label_indent_size = 0\nij_groovy_lambda_brace_style = end_of_line\nij_groovy_layout_static_imports_separately = true\nij_groovy_line_comment_add_space = false\nij_groovy_line_comment_add_space_on_reformat = false\nij_groovy_line_comment_at_first_column = true\nij_groovy_method_annotation_wrap = split_into_lines\nij_groovy_method_brace_style = end_of_line\nij_groovy_method_call_chain_wrap = off\nij_groovy_method_parameters_new_line_after_left_paren = false\nij_groovy_method_parameters_right_paren_on_new_line = false\nij_groovy_method_parameters_wrap = off\nij_groovy_modifier_list_wrap = false\nij_groovy_names_count_to_use_import_on_demand = 3\nij_groovy_packages_to_use_import_on_demand = java.awt.*,javax.swing.*\nij_groovy_parameter_annotation_wrap = off\nij_groovy_parentheses_expression_new_line_after_left_paren = false\nij_groovy_parentheses_expression_right_paren_on_new_line = false\nij_groovy_prefer_parameters_wrap = false\nij_groovy_resource_list_new_line_after_left_paren = false\nij_groovy_resource_list_right_paren_on_new_line = false\nij_groovy_resource_list_wrap = off\nij_groovy_space_after_assert_separator = true\nij_groovy_space_after_colon = true\nij_groovy_space_after_comma = true\nij_groovy_space_after_comma_in_type_arguments = true\nij_groovy_space_after_for_semicolon = true\nij_groovy_space_after_quest = true\nij_groovy_space_after_type_cast = true\nij_groovy_space_before_annotation_parameter_list = false\nij_groovy_space_before_array_initializer_left_brace = false\nij_groovy_space_before_assert_separator = false\nij_groovy_space_before_catch_keyword = true\nij_groovy_space_before_catch_left_brace = true\nij_groovy_space_before_catch_parentheses = true\nij_groovy_space_before_class_left_brace = true\nij_groovy_space_before_closure_left_brace = true\nij_groovy_space_before_colon = true\nij_groovy_space_before_comma = false\nij_groovy_space_before_do_left_brace = true\nij_groovy_space_before_else_keyword = true\nij_groovy_space_before_else_left_brace = true\nij_groovy_space_before_finally_keyword = true\nij_groovy_space_before_finally_left_brace = true\nij_groovy_space_before_for_left_brace = true\nij_groovy_space_before_for_parentheses = true\nij_groovy_space_before_for_semicolon = false\nij_groovy_space_before_if_left_brace = true\nij_groovy_space_before_if_parentheses = true\nij_groovy_space_before_method_call_parentheses = false\nij_groovy_space_before_method_left_brace = true\nij_groovy_space_before_method_parentheses = false\nij_groovy_space_before_quest = true\nij_groovy_space_before_record_parentheses = false\nij_groovy_space_before_switch_left_brace = true\nij_groovy_space_before_switch_parentheses = true\nij_groovy_space_before_synchronized_left_brace = true\nij_groovy_space_before_synchronized_parentheses = true\nij_groovy_space_before_try_left_brace = true\nij_groovy_space_before_try_parentheses = true\nij_groovy_space_before_while_keyword = true\nij_groovy_space_before_while_left_brace = true\nij_groovy_space_before_while_parentheses = true\nij_groovy_space_in_named_argument = true\nij_groovy_space_in_named_argument_before_colon = false\nij_groovy_space_within_empty_array_initializer_braces = false\nij_groovy_space_within_empty_method_call_parentheses = false\nij_groovy_spaces_around_additive_operators = true\nij_groovy_spaces_around_assignment_operators = true\nij_groovy_spaces_around_bitwise_operators = true\nij_groovy_spaces_around_equality_operators = true\nij_groovy_spaces_around_lambda_arrow = true\nij_groovy_spaces_around_logical_operators = true\nij_groovy_spaces_around_multiplicative_operators = true\nij_groovy_spaces_around_regex_operators = true\nij_groovy_spaces_around_relational_operators = true\nij_groovy_spaces_around_shift_operators = true\nij_groovy_spaces_within_annotation_parentheses = false\nij_groovy_spaces_within_array_initializer_braces = false\nij_groovy_spaces_within_braces = true\nij_groovy_spaces_within_brackets = false\nij_groovy_spaces_within_cast_parentheses = false\nij_groovy_spaces_within_catch_parentheses = false\nij_groovy_spaces_within_for_parentheses = false\nij_groovy_spaces_within_gstring_injection_braces = false\nij_groovy_spaces_within_if_parentheses = false\nij_groovy_spaces_within_list_or_map = false\nij_groovy_spaces_within_method_call_parentheses = false\nij_groovy_spaces_within_method_parentheses = false\nij_groovy_spaces_within_parentheses = false\nij_groovy_spaces_within_switch_parentheses = false\nij_groovy_spaces_within_synchronized_parentheses = false\nij_groovy_spaces_within_try_parentheses = false\nij_groovy_spaces_within_tuple_expression = false\nij_groovy_spaces_within_while_parentheses = false\nij_groovy_special_else_if_treatment = true\nij_groovy_ternary_operation_wrap = off\nij_groovy_throws_keyword_wrap = off\nij_groovy_throws_list_wrap = off\nij_groovy_use_flying_geese_braces = false\nij_groovy_use_fq_class_names = false\nij_groovy_use_fq_class_names_in_javadoc = true\nij_groovy_use_relative_indents = false\nij_groovy_use_single_class_imports = true\nij_groovy_variable_annotation_wrap = off\nij_groovy_while_brace_force = never\nij_groovy_while_on_new_line = false\nij_groovy_wrap_chain_calls_after_dot = false\nij_groovy_wrap_long_lines = false\n\n[{*.kt,*.kts}]\nindent_style = space\ninsert_final_newline = true\nmax_line_length = 100\nindent_size = 2\nij_continuation_indent_size = 2\nij_java_names_count_to_use_import_on_demand = 9999\nij_kotlin_align_in_columns_case_branch = false\nij_kotlin_align_multiline_binary_operation = false\nij_kotlin_align_multiline_extends_list = false\nij_kotlin_align_multiline_method_parentheses = false\nij_kotlin_align_multiline_parameters = true\nij_kotlin_align_multiline_parameters_in_calls = false\nij_kotlin_allow_trailing_comma = true\nij_kotlin_allow_trailing_comma_on_call_site = true\nij_kotlin_assignment_wrap = normal\nij_kotlin_blank_lines_after_class_header = 0\nij_kotlin_blank_lines_around_block_when_branches = 0\nij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1\nij_kotlin_block_comment_at_first_column = true\nij_kotlin_call_parameters_new_line_after_left_paren = true\nij_kotlin_call_parameters_right_paren_on_new_line = false\nij_kotlin_call_parameters_wrap = on_every_item\nij_kotlin_catch_on_new_line = false\nij_kotlin_class_annotation_wrap = split_into_lines\nij_kotlin_code_style_defaults = KOTLIN_OFFICIAL\nij_kotlin_continuation_indent_for_chained_calls = true\nij_kotlin_continuation_indent_for_expression_bodies = true\nij_kotlin_continuation_indent_in_argument_lists = true\nij_kotlin_continuation_indent_in_elvis = false\nij_kotlin_continuation_indent_in_if_conditions = false\nij_kotlin_continuation_indent_in_parameter_lists = false\nij_kotlin_continuation_indent_in_supertype_lists = false\nij_kotlin_else_on_new_line = false\nij_kotlin_enum_constants_wrap = off\nij_kotlin_extends_list_wrap = normal\nij_kotlin_field_annotation_wrap = split_into_lines\nij_kotlin_finally_on_new_line = false\nij_kotlin_if_rparen_on_new_line = false\nij_kotlin_import_nested_classes = false\nij_kotlin_insert_whitespaces_in_simple_one_line_method = true\nij_kotlin_keep_blank_lines_before_right_brace = 2\nij_kotlin_keep_blank_lines_in_code = 2\nij_kotlin_keep_blank_lines_in_declarations = 2\nij_kotlin_keep_first_column_comment = true\nij_kotlin_keep_indents_on_empty_lines = false\nij_kotlin_keep_line_breaks = true\nij_kotlin_lbrace_on_next_line = false\nij_kotlin_line_comment_add_space = false\nij_kotlin_line_comment_at_first_column = true\nij_kotlin_method_annotation_wrap = split_into_lines\nij_kotlin_method_call_chain_wrap = normal\nij_kotlin_method_parameters_new_line_after_left_paren = true\nij_kotlin_method_parameters_right_paren_on_new_line = true\nij_kotlin_method_parameters_wrap = on_every_item\nij_kotlin_name_count_to_use_star_import = 9999\nij_kotlin_name_count_to_use_star_import_for_members = 9999\nij_kotlin_parameter_annotation_wrap = off\nij_kotlin_space_after_comma = true\nij_kotlin_space_after_extend_colon = true\nij_kotlin_space_after_type_colon = true\nij_kotlin_space_before_catch_parentheses = true\nij_kotlin_space_before_comma = false\nij_kotlin_space_before_extend_colon = true\nij_kotlin_space_before_for_parentheses = true\nij_kotlin_space_before_if_parentheses = true\nij_kotlin_space_before_lambda_arrow = true\nij_kotlin_space_before_type_colon = false\nij_kotlin_space_before_when_parentheses = true\nij_kotlin_space_before_while_parentheses = true\nij_kotlin_spaces_around_additive_operators = true\nij_kotlin_spaces_around_assignment_operators = true\nij_kotlin_spaces_around_equality_operators = true\nij_kotlin_spaces_around_function_type_arrow = true\nij_kotlin_spaces_around_logical_operators = true\nij_kotlin_spaces_around_multiplicative_operators = true\nij_kotlin_spaces_around_range = false\nij_kotlin_spaces_around_relational_operators = true\nij_kotlin_spaces_around_unary_operator = false\nij_kotlin_spaces_around_when_arrow = true\nij_kotlin_variable_annotation_wrap = off\nij_kotlin_while_on_new_line = false\nij_kotlin_wrap_elvis_expressions = 1\nij_kotlin_wrap_expression_body_functions = 1\nij_kotlin_wrap_first_method_in_call_chain = false\n\n[{*.har,*.json}]\nindent_size = 2\nij_json_array_wrapping = split_into_lines\nij_json_keep_blank_lines_in_code = 0\nij_json_keep_indents_on_empty_lines = false\nij_json_keep_line_breaks = true\nij_json_keep_trailing_comma = false\nij_json_object_wrapping = split_into_lines\nij_json_property_alignment = do_not_align\nij_json_space_after_colon = true\nij_json_space_after_comma = true\nij_json_space_before_colon = false\nij_json_space_before_comma = false\nij_json_spaces_within_braces = false\nij_json_spaces_within_brackets = false\nij_json_wrap_long_lines = false\n\n[{*.htm,*.html,*.sht,*.shtm,*.shtml}]\nij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3\nij_html_align_attributes = true\nij_html_align_text = false\nij_html_attribute_wrap = normal\nij_html_block_comment_add_space = false\nij_html_block_comment_at_first_column = true\nij_html_do_not_align_children_of_min_lines = 0\nij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p\nij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot\nij_html_enforce_quotes = false\nij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var\nij_html_keep_blank_lines = 2\nij_html_keep_indents_on_empty_lines = false\nij_html_keep_line_breaks = true\nij_html_keep_line_breaks_in_text = true\nij_html_keep_whitespaces = false\nij_html_keep_whitespaces_inside = span,pre,textarea\nij_html_line_comment_at_first_column = true\nij_html_new_line_after_last_attribute = never\nij_html_new_line_before_first_attribute = never\nij_html_quote_style = double\nij_html_remove_new_line_before_tags = br\nij_html_space_after_tag_name = false\nij_html_space_around_equality_in_attribute = false\nij_html_space_inside_empty_tag = false\nij_html_text_wrap = normal\n\n[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}]\nij_toml_keep_indents_on_empty_lines = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "#\n# https://help.github.com/articles/dealing-with-line-endings/\n#\n# Linux start script should use lf\n/gradlew        text eol=lf\n\n# These are Windows script files and should use crlf\n*.bat           text eol=crlf\n\n# Binary files should be left untouched\n*.jar           binary\n\n"
  },
  {
    "path": ".github/actions/prepare-emulator-action/action.yml",
    "content": "name: 'Prepare Emulator'\ndescription: 'Common emulator setup steps'\nruns:\n  using: \"composite\"\n  steps:\n    # This is needed for hardware acceleration, see https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/\n    - name: Enable Hardware Acceleration\n      shell: bash\n      run: |\n        echo 'KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0666\", OPTIONS+=\"static_node=kvm\"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules\n        sudo udevadm control --reload-rules\n        sudo udevadm trigger --name-match=kvm\n\n    # This is needed to accept the Android license, see https://issuetracker.google.com/issues/193118030\n    - name: Accept Android SDK License\n      uses: android-actions/setup-android@v3\n"
  },
  {
    "path": ".github/actions/setup-action/action.yml",
    "content": "name: 'Setup'\ndescription: 'Common setup steps'\n\ninputs:\n  gradle-encryption-key:\n    description: \"The key used to encrypt the Gradle cache\"\n    required: true\n\nruns:\n  using: \"composite\"\n  steps:\n    - uses: actions/setup-java@v4\n      with:\n        distribution: 'zulu'\n        java-version: 21\n\n    - name: Setup Gradle\n      uses: gradle/actions/setup-gradle@v4\n      with:\n        # Only save Gradle User Home state for builds on the 'main' branch.\n        # Builds on other branches will only read existing entries from the cache.\n        cache-read-only: ${{ github.ref != 'refs/heads/main' }}\n\n        # Don't reuse cache entries from any other Job.\n        gradle-home-cache-strict-match: true\n\n        cache-encryption-key: ${{ inputs.gradle-encryption-key }}\n"
  },
  {
    "path": ".github/workflows/blueprints-starter-ci.yml",
    "content": "name: Build Starter Blueprint (Android + iOS + WASM + Desktop)\n\non:\n  push:\n    paths:\n      - 'blueprints/starter/**'\n      - '.github/**'\n    tags-ignore:\n      - '**'\n  pull_request:\n    paths:\n      - 'blueprints/starter/**'\n      - '.github/**'\n\npermissions:\n  contents: read\n\njobs:\n  build-ios-starter-app:\n    runs-on: macos-latest-xlarge\n    timeout-minutes: 25\n    defaults:\n      run:\n        working-directory: blueprints/starter\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n      - name: Build iOS Framework\n        run: ./gradlew :app:linkDebugFrameworkIosSimulatorArm64\n\n  build-wasm-starter-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n    defaults:\n      run:\n        working-directory: blueprints/starter\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n      - name: Build WASM binary\n        run: ./gradlew :app:wasmJsBrowserDistribution\n\n  build-android-starter-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n    defaults:\n      run:\n        working-directory: blueprints/starter\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n      - name: Build Android\n        run: ./gradlew :app:assembleDebug\n\n  build-desktop-starter-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n    defaults:\n      run:\n        working-directory: blueprints/starter\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n      - name: Build Desktop binary\n        run: ./gradlew :app:desktopMainClasses\n\n  ktfmt:\n    runs-on: macos-latest\n    timeout-minutes: 25\n    defaults:\n      run:\n        working-directory: blueprints/starter\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Install ktfmt\n        run: brew install ktfmt\n\n      - name: Run ktfmt\n        run: ktfmt --google-style --dry-run --set-exit-if-changed $(find . -type f -name \"*.kt\")\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n    tags-ignore:\n      - '**'\n    paths-ignore:\n      - '**/*.md'\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  test-android:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew testDebugUnitTest --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-ios:\n    runs-on: macos-latest-xlarge\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew iosSimulatorArm64Test -Pkotlin.incremental.native=false --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-desktop:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew desktopTest --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-linux:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew linuxX64Test --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-wasm:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew wasmJsTest --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-jvm-modules:\n    name: test-jvm-modules\n\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Test\n        run: ./gradlew :kotlin-inject-extensions:contribute:impl-code-generators:test :metro-extensions:contribute:impl-code-generators:test :metro-extensions:contribute:impl-compiler-plugin:test --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-emulator-renderer-android-view:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Prepare emulator\n        uses: ./.github/actions/prepare-emulator-action\n\n      - name: Test\n        run: ./gradlew :renderer-android-view:public:emulatorCheck --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-emulator-renderer-compose-multiplatform:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Prepare emulator\n        uses: ./.github/actions/prepare-emulator-action\n\n      - name: Test\n        run: ./gradlew :renderer-compose-multiplatform:public:emulatorCheck --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-emulator-sample-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Prepare emulator\n        uses: ./.github/actions/prepare-emulator-action\n\n      - name: Test\n        run: ./gradlew :sample:app:emulatorCheck --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  test-emulator-sample-app-ksp:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Prepare emulator\n        uses: ./.github/actions/prepare-emulator-action\n\n      - name: Test\n        run: ./gradlew :sample:app:emulatorCheck -Papp.platform.metro.ksp=true --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  build-ios-sample-app:\n    runs-on: macos-latest-xlarge\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      # The command to build is executed by the Android Studio iOS app run action.\n      #\n      # The destination id was printed in the Github Action console\n      #\n      #   { platform:iOS Simulator, id:77D15A8A-0E47-4200-A192-A0C6311C808D, OS:18.2, name:iPhone SE (3rd generation) }\n      #\n      #\n      # Downloading the iOS platform is needed with the latest macos runners. This is quite slow, so we should see\n      # if can we avoid this in the future.\n      # See: https://github.com/actions/runner-images/issues/12758#issuecomment-3206748945\n      - name: Build swift\n        run: |\n          /usr/bin/xcodebuild -version\n          /usr/bin/xcodebuild -showsdks\n          /usr/bin/xcrun simctl list devices\n          export DESTINATION_DEVICE=`/usr/bin/xcrun simctl list devices | grep -A 1 \"iOS 18.6\" | grep -oE '\\([0-9A-F-]+\\)' | head -1 | tr -d '()'`\n          echo \"Using simulator $DESTINATION_DEVICE\"\n          /usr/bin/xcodebuild -project sample/iosApp/iosApp.xcodeproj -scheme iosApp -configuration Debug OBJROOT=build/ios SYMROOT=build/ios -destination id=$DESTINATION_DEVICE -allowProvisioningDeviceRegistration -allowProvisioningUpdates\n\n  build-wasm-sample-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Build wasm binary\n        run: ./gradlew :sample:app:wasmJsBrowserDistribution\n\n  binary-compatibility-check:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: API check\n        run: ./gradlew apiCheck --stacktrace --show-version --continue\n\n  ktfmt:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: ktfmt\n        run: ./gradlew ktfmtCheck --stacktrace --show-version --continue\n\n  android-lint:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Android Lint\n        run: ./gradlew lint --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  detekt:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Detekt\n        run: ./gradlew detekt --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  module-structure-check:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Module Structure Check\n        run: ./gradlew checkModuleStructureDependencies --stacktrace --show-version --continue\n\n  publish-maven-local:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Publish to Maven Local\n        run: |\n          ./gradlew publishToMavenLocal --stacktrace --show-version --no-configuration-cache\n          ./gradlew -p gradle-plugin publishToMavenLocal --stacktrace --show-version --no-configuration-cache\n\n  build-src:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Run release task\n        run: ./gradlew -p buildSrc release --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n\n  gradle-plugin:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Run release task\n        run: ./gradlew -p gradle-plugin release --stacktrace --show-version --continue\n\n      - name: Upload Test Results\n        uses: actions/upload-artifact@v4\n        if: ${{ failure() }}\n        with:\n          name: test-results-${{ github.job }}\n          path: ./**/build/reports/\n"
  },
  {
    "path": ".github/workflows/pages.yml",
    "content": "# Simple workflow for deploying static content to GitHub Pages\nname: Deploy Wiki\n\non:\n  # Runs on pushes targeting the default branch\n  push:\n    branches:\n      - main\n\n  # Allows to run this workflow manually from the Actions tab.\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: write\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  build-wasm-sample-app:\n    runs-on: ubuntu-latest\n    timeout-minutes: 25\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Build wasm binary\n        run: ./gradlew :sample:app:wasmJsBrowserDistribution :recipes:app:wasmJsBrowserDistribution\n\n      - name: Upload wasm binaries\n        uses: actions/upload-artifact@v4\n        with:\n          name: wasm-files\n          path: |\n            ./sample/app/build/dist/wasmJs/productionExecutable/\n            ./recipes/app/build/dist/wasmJs/productionExecutable/\n\n  build-mkdocs:\n    needs: build-wasm-sample-app\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: 3.9\n      - name: Download wasm binaries\n        uses: actions/download-artifact@v4\n        with:\n          name: wasm-files\n          path: docs/web\n      - run: |\n          cp CHANGELOG.md docs/changelog.md\n      - run: |\n          pip install mkdocs-material\n          pip install \"mkdocs-material[imaging]\"\n      - run: mkdocs gh-deploy --config-file mkdocs.yml --force\n\n  deploy-mkdocs:\n    needs: build-mkdocs\n    if: github.repository == 'amzn/app-platform'\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: gh-pages\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: '.'\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/publish-release.yml",
    "content": "name: Publish Release\n\non:\n  push:\n    tags:\n      - '*.*.*'\n\npermissions:\n  contents: read\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"release-upload\"\n  cancel-in-progress: false\n\njobs:\n  publish-release:\n    # Needed for creating the release: https://github.com/softprops/action-gh-release?tab=readme-ov-file#permissions\n    permissions:\n      contents: write\n\n    runs-on: macos-latest-xlarge\n    if: github.repository == 'amzn/app-platform'\n    timeout-minutes: 60\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Publish release\n        run: |\n          ./gradlew clean publishAndReleaseToMavenCentral -PRELEASE_SIGNING_ENABLED=true --no-build-cache --stacktrace --show-version --no-configuration-cache\n          ./gradlew -p gradle-plugin clean publishAndReleaseToMavenCentral -PRELEASE_SIGNING_ENABLED=true --no-build-cache --stacktrace --show-version --no-configuration-cache\n        env:\n          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_PORTAL_USERNAME }}\n          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PORTAL_PASSWORD }}\n          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }}\n          ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY_PASSWORD }}\n\n      - name: Extract release notes\n        id: release_notes\n        uses: ffurrer2/extract-release-notes@v2\n\n      - name: Check if pre-release\n        id: prerelease\n        run: |\n          version=$(grep VERSION_NAME gradle.properties | cut -d'=' -f2)\n          if [[ $version == *\"-beta\"* ]]; then\n            echo \"isPrerelease=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"isPrerelease=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2\n        with:\n          body: ${{ steps.release_notes.outputs.release_notes }}\n          prerelease: ${{ steps.prerelease.outputs.isPrerelease }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/publish-snapshot.yml",
    "content": "name: Publish Snapshot\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**/*.md'\n\npermissions:\n  contents: read\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"snapshot-upload\"\n  cancel-in-progress: false\n\njobs:\n  publish-snapshot:\n    runs-on: macos-latest-xlarge\n    if: github.repository == 'amzn/app-platform'\n    timeout-minutes: 60\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup\n        uses: ./.github/actions/setup-action\n        with:\n          gradle-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}\n\n      - name: Publish snapshot\n        run: |\n          ./gradlew clean publish -PRELEASE_SIGNING_ENABLED=true --no-build-cache --stacktrace --show-version --no-configuration-cache\n          ./gradlew -p gradle-plugin clean publish -PRELEASE_SIGNING_ENABLED=true --no-build-cache --stacktrace --show-version --no-configuration-cache\n        env:\n          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_PORTAL_USERNAME }}\n          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PORTAL_PASSWORD }}\n          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY }}\n          ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.ARTIFACT_SIGNING_PRIVATE_KEY_PASSWORD }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Gradle\n.gradle\n/.gradle/\nbuild/\n!/scripts/**/build\nlocal.properties\n/reports/\n\n# IntelliJ IDEA\n.idea/*\n!.idea/ktfmt.xml\n*.iml\n*.ipl\n*.ipr\n*.iws\n.shelf/\n\n# kotlin\n.kotlin\n\n# iOS\n**/xcuserdata/\n\n# Steve Jobs\n.DS_Store\n\n"
  },
  {
    "path": ".idea/ktfmt.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KtfmtSettings\">\n    <option name=\"enableKtfmt\" value=\"Enabled\" />\n    <option name=\"uiFormatterStyle\" value=\"Google (internal)\" />\n  </component>\n</project>"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS.md\n\n## Purpose\n\nThis repository is the Amazon App Platform: a Kotlin Multiplatform application framework plus example applications and a starter blueprint. The core concepts are documented in [`docs/`](docs/) and implemented across reusable library modules plus a few app entrypoints.\n\nStart here before changing code:\n\n- `README.md`\n- `docs/index.md`\n- `docs/setup.md`\n- `docs/module-structure.md`\n- `docs/di.md`\n- `docs/presenter.md`\n- `docs/renderer.md`\n- `docs/template.md`\n- `docs/testing.md`\n- `settings.gradle`\n- `buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/`\n\n`mkdocs.yml` is the docs site manifest. The Pages workflow builds Wasm artifacts for `:sample:app` and `:recipes:app` and copies them into `docs/web/` before publishing.\n\n## Repo Shape\n\nImportant top-level areas:\n\n- `gradle-plugin/`: the published `software.amazon.app.platform` Gradle plugin.\n- `buildSrc/`: repo-local convention plugins used by this repository’s own modules. This is where platform targets, emulator config, desktop packaging, and Wasm defaults are defined.\n- `docs/`: framework documentation. Treat this as the authoritative product docs.\n- `sample/`: the main sample app. This is the best place to study end-to-end usage of scopes, DI, presenters, renderers, templates, fakes, and robots.\n- `recipes/`: a second example app plus reusable “recipe” patterns, including the separate `recipesIosApp` SwiftUI/Xcode wrapper.\n- `blueprints/starter/`: a standalone starter app template with its own Gradle wrapper, version catalog, and README.\n\nCore framework module families:\n\n- `scope`, `di-common`\n- `presenter`, `presenter-molecule`\n- `renderer`, `renderer-android-view`, `renderer-compose-multiplatform`\n- `robot`, `robot-compose-multiplatform`, `robot-internal`\n- `kotlin-inject`, `kotlin-inject-extensions`\n- `metro`, `metro-extensions`\n- `ksp-common`\n\nCompiler plugin work currently lives in:\n\n- `metro-extensions/contribute/impl-compiler-plugin/`: JVM-only Kotlin compiler plugin module for Metro-backed App Platform DI extensions such as `@ContributesRobot`. `src/main/` contains FIR generation and diagnostics. `src/test/resources/box`, `diagnostics`, and `dump` contain compiler test data. `src/test/java/.../runners/` contains generated JUnit test runners and must be regenerated with `generateTests` after adding or renaming test data files.\n\n## Architecture Rules\n\nThe most important repo rule is the module structure documented in `docs/module-structure.md`.\n\n- `:public` modules expose reusable APIs and shared code.\n- `:impl` modules contain concrete implementations.\n- `:testing` modules hold shared fakes and test helpers.\n- `:*-robots` modules hold shared UI robots.\n- `:app` modules are the only modules allowed to depend on `:impl` modules.\n\nDo not introduce a dependency from a non-`:app` module to an `:impl` module. The build enforces this via `checkModuleStructureDependencies`.\n\nThe framework’s architectural flow is:\n\n1. `Scope` and DI assemble objects for a lifecycle boundary.\n2. `MoleculePresenter` implementations produce models.\n3. App-specific `Template` presenters wrap the root model tree.\n4. `RendererFactory` resolves platform renderers for those models.\n5. Thin platform entrypoints bootstrap the root scope and start rendering.\n\nRepresentative entrypoints:\n\n- Android: `sample/app/src/androidMain/.../AndroidApplication.kt`, `MainActivity.kt`\n- iOS: `sample/app/src/iosMain/.../MainViewController.kt`, `sample/iosApp/`\n- Desktop: `sample/app/src/desktopMain/.../Main.kt`, `DesktopApp.kt`\n- Wasm: `sample/app/src/wasmJsMain/.../Main.kt`\n\n## Toolchain\n\nLocal development should match CI as closely as possible. These versions live in `gradle/libs.versions.toml`.\n\nExpected warning: Gradle prints a warning that configuration-on-demand is not supported for Wasm targets. This is noisy but currently normal in this repo.\n\nFor Metro compiler-plugin work, prefer source over decompiled artifacts:\n\n- Reference implementation: `https://github.com/square/metro-extensions`\n- Metro source: use a local checkout if you have one, otherwise upstream Metro on GitHub\n- Avoid relying on `.gradle/caches` or decompiled JARs when the source is available\n\n## Run The Apps\n\nThere are three app-style entrypoints to care about:\n\n- `:sample:app`: main sample app inside the root build.\n- `:recipes:app`: recipe/demo app inside the root build.\n- `blueprints/starter`: standalone starter app; run commands from inside that directory or use its own `./gradlew`.\n\n### Android\n\nInstall the debug APK onto a connected device or emulator:\n\n```bash\n./gradlew :sample:app:installDebug\n./gradlew :recipes:app:installDebug\n```\n\nFor the standalone starter:\n\n```bash\ncd blueprints/starter\n./gradlew :app:installDebug\n```\n\n`buildSrc/.../BaseAndroidPlugin.kt` configures managed emulator tests with a local device named `emulator` using a Pixel 3 / API 30 `aosp-atd` image.\n\n### iOS\n\nSample app:\n\n```bash\nopen sample/iosApp/iosApp.xcodeproj\n```\n\nRecipe app:\n\n```bash\nopen recipes/recipesIosApp/recipesIosApp.xcodeproj\n```\n\nThe Xcode projects include a shell build phase that calls Gradle:\n\n- `:sample:app:embedAndSignAppleFrameworkForXcode`\n- `:recipes:app:embedAndSignAppleFrameworkForXcode`\n\nIf you only want to build the Kotlin framework without opening Xcode:\n\n```bash\n./gradlew :sample:app:linkDebugFrameworkIosSimulatorArm64\n./gradlew :recipes:app:linkDebugFrameworkIosSimulatorArm64\n```\n\nCI builds the sample iOS wrapper with `xcodebuild -project sample/iosApp/iosApp.xcodeproj -scheme iosApp ... -destination id=<simulator-id>`. Use `xcrun simctl list devices` to pick a simulator if you need a pure CLI invocation.\n\n### Desktop\n\nRun the desktop Compose app:\n\n```bash\n./gradlew :sample:app:run\n./gradlew :recipes:app:run\n```\n\nStarter blueprint:\n\n```bash\ncd blueprints/starter\n./gradlew :app:run\n```\n\nDesktop packaging tasks such as `packageDmg`, `packageDeb`, and `packageMsi` are available on app modules.\n\n### Wasm\n\nDevelopment server:\n\n```bash\n./gradlew :sample:app:wasmJsBrowserDevelopmentRun\n./gradlew :recipes:app:wasmJsBrowserDevelopmentRun\n```\n\nProduction bundle:\n\n```bash\n./gradlew :sample:app:wasmJsBrowserDistribution\n./gradlew :recipes:app:wasmJsBrowserDistribution\n```\n\nStarter blueprint:\n\n```bash\ncd blueprints/starter\n./gradlew :app:wasmJsBrowserDevelopmentRun\n```\n\nAfter a production Wasm build, serve the generated files from:\n\n- `sample/app/build/dist/wasmJs/productionExecutable/`\n- `recipes/app/build/dist/wasmJs/productionExecutable/`\n\nThe starter README suggests `npx http-server` from the production output directory.\n\n## Run The Tests\n\n### Repo-wide CI-style checks\n\nThese are the main root-level quality gates used by GitHub Actions:\n\n```bash\n./gradlew testDebugUnitTest\n./gradlew iosSimulatorArm64Test -Pkotlin.incremental.native=true\n./gradlew desktopTest\n./gradlew linuxX64Test\n./gradlew wasmJsTest\n./gradlew apiCheck\n./gradlew ktfmtCheck\n./gradlew detekt\n./gradlew lint\n./gradlew checkModuleStructureDependencies\n```\n\n### Sample app tests by platform\n\nAndroid instrumented UI tests:\n\n```bash\n./gradlew :sample:app:emulatorCheck\n```\n\nOr against a manually started device:\n\n```bash\n./gradlew :sample:app:connectedDebugAndroidTest\n```\n\nDesktop UI tests:\n\n```bash\n./gradlew :sample:app:desktopTest\n```\n\nAndroid unit tests:\n\n```bash\n./gradlew :sample:app:testDebugUnitTest\n```\n\niOS simulator tests:\n\n```bash\n./gradlew :sample:app:iosSimulatorArm64Test -Pkotlin.incremental.native=true\n```\n\nAll sample app target tests:\n\n```bash\n./gradlew :sample:app:allTests\n```\n\n### Metro compiler-plugin module\n\nRun these from the repo root:\n\n```bash\n./gradlew :metro-extensions:contribute:impl-compiler-plugin:test\n./gradlew :metro-extensions:contribute:impl-compiler-plugin:test --tests 'software.amazon.app.platform.metro.compiler.runners.BoxTestGenerated$Metro.testTinyGraph'\n./gradlew :metro-extensions:contribute:impl-compiler-plugin:test -PupdateTestData\n./gradlew :metro-extensions:contribute:impl-compiler-plugin:generateTests\n./gradlew :metro-extensions:contribute:impl-compiler-plugin:ktfmtCheck\n```\n\nUse this workflow for compiler tests:\n\n- Add new test data under `src/test/resources/box`, `diagnostics`, or `dump`\n- Run `:metro-extensions:contribute:impl-compiler-plugin:generateTests` after adding or renaming test data files\n- Run `:metro-extensions:contribute:impl-compiler-plugin:test`\n- Use `-PupdateTestData` when intentionally updating FIR or IR golden files\n\nTest data conventions for this module:\n\n- `box/`: compile-and-run tests. Each file exposes `fun box(): String` and should return `\"OK\"`.\n- `diagnostics/`: compiler error tests with inline diagnostic markers plus `.fir.diag.txt` golden files.\n- `dump/`: compiler dump tests with `.fir.txt` goldens, plus `.fir.kt.txt` files for IR text dumps.\n\n`apiCheck` and `apiDump` are disabled for this module, so do not use them as validation commands here.\n\n### Where tests live\n\n- Android UI tests: `sample/app/src/androidInstrumentedTest/`\n- Desktop UI tests: `sample/app/src/desktopTest/`\n- Shared unit tests: `sample/*/src/commonTest/`\n- Shared fakes: `sample/user/testing/`\n- Shared robots: `sample/login/impl-robots/`, `sample/user/impl-robots/`\n- Compiler plugin test data: `metro-extensions/contribute/impl-compiler-plugin/src/test/resources/`\n- Generated compiler test runners: `metro-extensions/contribute/impl-compiler-plugin/src/test/java/software/amazon/app/platform/metro/compiler/runners/`\n\n## Current Test Reality\n\nAs of this checkout:\n\n- `:sample:app:desktopTest` runs successfully.\n- `:sample:app:testDebugUnitTest` succeeds but currently has `NO-SOURCE`.\n- `:sample:app:iosSimulatorArm64Test -Pkotlin.incremental.native=true` succeeds but is currently skipped because `sample/app` has no iOS test sources.\n- Android UI coverage for the sample app is in `androidInstrumentedTest` and is exercised through `emulatorCheck`/`connectedDebugAndroidTest`.\n\n## Wasm Lockfile Caveat\n\nWasm tasks are currently strict about the committed Yarn lockfile under `kotlin-js-store/wasm/yarn.lock`.\n\nIf a Wasm task fails with:\n\n```text\nExecution failed for task ':kotlinWasmStoreYarnLock'.\nLock file was changed. Run the `kotlinWasmUpgradeYarnLock` task to actualize lock file\n```\n\nthen the generated `build/wasm/yarn.lock` does not match the committed lock. In this checkout, both `:sample:app:wasmJsTest` and `:sample:app:wasmJsBrowserDistribution` hit that failure.\n\nTreat `kotlinWasmUpgradeYarnLock` as an intentional dependency update step, not a routine run command. If you change Wasm/npm dependencies on purpose, update and review `kotlin-js-store/wasm/yarn.lock` in the same change.\n\n## Docs Workflow\n\nTo work on docs locally:\n\n```bash\ncp CHANGELOG.md docs/changelog.md\npip install mkdocs-material \"mkdocs-material[imaging]\"\nmkdocs serve\n```\n\nWhen changing framework behavior, update both:\n\n- the relevant `docs/*.md` page\n- the sample and/or starter code that demonstrates that behavior\n\nIf a change affects how consumers start a new project, also update `blueprints/starter/README.md`.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## [Unreleased]\n\n### Added\n\n### Changed\n\n### Deprecated\n\n### Removed\n\n- Removed Apple x86_64 targets from the repository builds by dropping `iosX64` where it was still configured, aligning with Compose Multiplatform's removal of Apple x86_64 target support: https://kotlinlang.org/docs/multiplatform/whats-new-compose-111.html#dropped-support-for-apple-x86-64-targets\n\n### Fixed\n\n### Security\n\n### Other Notes & Contributions\n\n\n## [0.0.10] - 2026-04-20\n\n### Added\n\n- Migrate the blueprints/starter app from kotlin-inject to Metro, see #178\n- Add a compiler plugin for Metro extensions, see #179. The compiler plugin is now used by default, but the KSP implementations can be enabled by setting the Gradle property `-Papp.platform.metro.ksp=true`.\n\n## Changed\n\n- Metro to `1.0.0-RC2`\n\n\n## [0.0.9] - 2026-04-13\n\n### Added\n\n- Convert the sample app to [Metro](https://zacsweers.github.io/metro/), see #173. With the recent Kotlin and Metro version updates, issues we saw with Metro and targets other than Android/JVM are solved, and Metro is now the [recommended default](https://amzn.github.io/app-platform/di/) for dependency injection.\n\n### Changed\n\n- Kotlin to `2.3.20`\n- Gradle to `9.4.1`\n- metro to `0.13.2`\n\n\n## [0.0.8] - 2026-01-27\n\n### Added\n\n- Added a recipe for `Presenter` integration with SwiftUI, see #154.\n\n### Changed\n\n- Kotlin to `2.2.21`, see #161\n- KSP to `2.3.4`\n- kotlin-inject to `0.9.0`\n- kotlin-inject-anvil to `0.1.7`\n- metro to `0.10.1`\n- Remove testing for KSP1 and use KSP2\n\n### Other Notes & Contributions\n\n- Special thanks to [@rvenable](https://github.com/rvenable) for creating the original Swift APIs that served as the foundation for #154!\n\n\n## [0.0.7] - 2025-09-26\n\n### Changed\n\n- Changed the min SDK from 21 to 23, see #149.\n\n### Fixed\n\n- Fix NPE when removing Android Views from multiple child renderers with the same parent on activity destruction, see #150.\n\n\n## [0.0.6] - 2025-09-05\n\n### Added\n\n- Added support for [Metro](https://zacsweers.github.io/metro/) as dependency injection framework. User can choose between [`kotlin-inject-anvil`](https://github.com/amzn/kotlin-inject-anvil) and [Metro](https://zacsweers.github.io/metro/). For more details see the [documentation](https://amzn.github.io/app-platform/di/) for how to setup and use both dependency injection frameworks with App Platform.\n\n### Changed\n\n- Changed the provided `CoroutineScope` within `ViewRenderer` from a custom scope to `MainScope()`, see #124.\n- Disallow changing the parent View for `ViewRenderers`. For a different parent view `RendererFactory.getRenderer()` will now return a new `Renderer` instead of the cached instance. The cached instance is only returned for the same parent view, see #139.\n\n### Deprecated\n\n- Deprecated `diComponent()` and introduce `kotlinInjectComponent()` as replacement, see #106.\n- Deprecated `RendererFactory.getChildRendererForParent()`. `RendererFactory.getRenderer()` now provides the same functionality, see #139.\n\n### Fixed\n\n- Fix and stop suppressing NPE when removing Android Views, which lead to an inconsistent state and potential crashes laters, see #136.\n- Cancel the `CoroutineScope` in `ViewRenderer` in rare cases where `onDetach` for the view isn't triggered. This caused potential leaks, see #140.\n\n\n## [0.0.5] - 2025-08-15\n\n### Added\n\n- Added support for the new [Android-KMP library plugin](https://developer.android.com/kotlin/multiplatform/plugin) in App Platform's Gradle plugin.\n- Added a [recipe](https://amzn.github.io/app-platform/presenter/#navigation-3) for how to use the Navigation 3 library with App Platform.\n\n### Changed\n\n- Upgraded Kotlin to `2.2.10`.\n\n\n## [0.0.4] - 2025-07-25\n\n### Added\n\n- Added a search field to the wiki.\n- Added a [blueprint project](https://github.com/amzn/app-platform/tree/main/blueprints/starter) for App Platform that can be copied to spin up new projects faster, see #63.\n- Added support for back press events in `Presenters`. The API is similar to the one from Compose Multiplatform and Android Compose. See the [documentation in the wiki](https://amzn.github.io/app-platform/presenter/#back-gestures) for more details.\n- Added a [recipes application](https://amzn.github.io/app-platform/#web-recipe-app) showing solutions to common problems. All solutions have been [documented in the wiki](https://amzn.github.io/app-platform/presenter/#recipes).\n\n### Changed\n\n- Upgraded Kotlin to `2.2.0`.\n\n\n## [0.0.3] - 2025-05-28\n\n### Added\n\n- Wasm JS is now officially supported and artifacts are published.\n\n### Changed\n\n- Snapshots are now published to the Central Portal Snapshots repository at https://central.sonatype.com/repository/maven-snapshots/.\n- Upgraded Kotlin to `2.1.21`.\n\n### Removed\n\n- Removed the deprecated `onEvent` function used in `MoleculePresenters`. This is no longer needed since Kotlin 2.0.20, see #21.\n\n\n## [0.0.2] - 2025-05-02\n\n### Changed\n\n- **Breaking change:** Changed the constructor from `ComposeAndroidRendererFactory` to two factory functions instead. A new API allows you to use this factory without an Android View as parent, see #39.\n\n### Deprecated\n\n- Deprecated the `onEvent` function used in `MoleculePresenters`. This is no longer needed since Kotlin 2.0.20, see #21.\n\n### Fixed\n\n- Made the `ModuleStructureDependencyCheckTask` cacheable, see #19.\n- Fixed violations for Gradle's project isolation feature, see #20.\n\n### Other Notes\n\n- Updated the sample application with a shared transition animation to highlight how animations can be implemented for `Template` updates, see #37.\n\n\n## [0.0.1] - 2025-04-17\n\n- Initial release.\n\n[Unreleased]: https://github.com/amzn/app-platform/compare/0.0.10...HEAD\n[0.0.10]: https://github.com/amzn/app-platform/compare/0.0.10\n[0.0.9]: https://github.com/amzn/app-platform/compare/0.0.9\n[0.0.8]: https://github.com/amzn/app-platform/compare/0.0.8\n[0.0.7]: https://github.com/amzn/app-platform/compare/0.0.7\n[0.0.6]: https://github.com/amzn/app-platform/compare/0.0.6\n[0.0.5]: https://github.com/amzn/app-platform/compare/0.0.5\n[0.0.4]: https://github.com/amzn/app-platform/compare/0.0.4\n[0.0.3]: https://github.com/amzn/app-platform/compare/0.0.3\n[0.0.2]: https://github.com/amzn/app-platform/compare/0.0.2\n[0.0.1]: https://github.com/amzn/app-platform/compare/0.0.1\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nThank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional\ndocumentation, we greatly value feedback and contributions from our community.\n\nPlease read through this document before submitting any issues or pull requests to ensure we have all the necessary\ninformation to effectively respond to your bug report or contribution.\n\n\n## Reporting Bugs/Feature Requests\n\nWe welcome you to use the GitHub issue tracker to report bugs or suggest features.\n\nWhen filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already\nreported the issue. Please try to include as much information as you can. Details like these are incredibly useful:\n\n* A reproducible test case or series of steps\n* The version of our code being used\n* Any modifications you've made relevant to the bug\n* Anything unusual about your environment or deployment\n\n\n## Contributing via Pull Requests\nContributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:\n\n1. You are working against the latest source on the *main* branch.\n2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.\n3. You open an issue to discuss any significant work - we would hate for your time to be wasted.\n\nTo send us a pull request, please:\n\n1. Fork the repository.\n2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.\n3. Ensure local tests pass.\n4. Commit to your fork using clear commit messages.\n5. Send us a pull request, answering any default questions in the pull request interface.\n6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.\n\nGitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and\n[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).\n\n\n## Finding contributions to work on\nLooking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.\n\n\n## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments.\n\n\n## Security issue notifications\nIf you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.\n\n\n## Licensing\n\nSee the [LICENSE](https://github.com/amzn/app-platform/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n"
  },
  {
    "path": "NOTICE",
    "content": "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n"
  },
  {
    "path": "README.md",
    "content": "# App Platform\n\n[![Maven Central](https://img.shields.io/maven-central/v/software.amazon.app.platform/gradle-plugin.svg?label=Maven%20Central)](https://central.sonatype.com/search?smo=true&namespace=software.amazon.app.platform)\n[![CI](https://github.com/amzn/app-platform/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/amzn/app-platform/actions/workflows/ci.yml)\n\n<img src=\"docs/images/app-platform-logo.png\" alt=\"App Platform\" style=\"width:150px\" align=\"left\"/>\n\nThe App Platform is a lightweight application framework for state and memory management suitable\nfor Kotlin Multiplatform projects, in particular Android, iOS, JVM, native and Web. It makes the\ndependency inversion and dependency injection (DI) design patterns first class principles to develop\nfeatures and support the variety of platforms. The UI layer is entirely decoupled from the business logic,\nwhich allows different application targets to change the look and feel.\n\n### [amzn.github.io/app-platform](https://amzn.github.io/app-platform/)\n\n## Security\n\nSee [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.\n\n## License\n\nThis project is licensed under the Apache-2.0 License.\n"
  },
  {
    "path": "RELEASING.md",
    "content": "# Production Releases\n\n1. Checkout `origin/main`.\n2. Update the `CHANGELOG.md` file with the changes of this release (the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).\n   * Copy the template for the next unreleased version at the top.\n   * Delete unused section in the new release.\n   * Update the links at the bottom of the CHANGELOG.md file and don't forget to change the link for the unreleased version.\n3. Update the version in `gradle.properties` and remove the `-SNAPSHOT` suffix.\n4. Commit the changes and create a tag:\n   ```\n   git commit -am \"Releasing 0.1.0.\"\n   git tag 0.1.0\n   ```\n5. Update the version in `gradle.properties` and add the `-SNAPSHOT` suffix.\n6. Commit the change:\n   ```\n   git commit -am \"Prepare next development version.\"\n   ```\n7. Push the two commits. This will start a Github action that publishes the release to Maven Central and creates a new release on Github.\n   ```\n   git push && git push --tags\n   ```\n\n# Snapshot Releases\n\nSnapshot releases are automatically created whenever a commit to the `main` branch is pushed.\n\n# Manually uploading a release\n\nDepending on the version in the `gradle.properties` file it will be either a production or snapshot release.\n```\n./gradlew clean publish --no-build-cache\n```\n\n# Installing in Maven Local\n\n```\n./gradlew publishToMavenLocal\n```\n"
  },
  {
    "path": "blueprints/README.md",
    "content": "# Blueprints\n\nThis folder contains reusable templates (\"blueprints\") to help you quickly get started with projects using [App Platform](https://github.com/amzn/app-platform).\n\n## 📁 `starter/`\n\nThe `starter/` blueprint provides everything you need to bootstrap a new project with App Platform. It includes:\n\n- Pre-configured `build.gradle.kts` files for Kotlin Multiplatform\n- Android + iOS + Desktop + WASM targets with Compose UI enabled\n- App Platform integrations like Molecule presenters and Kotlin Inject\n- A working module structure with navigation and templates\n\n> 💡 More blueprints may be added in the future to support different project styles or configurations.\n"
  },
  {
    "path": "blueprints/starter/.gitignore",
    "content": "# Gradle\n.gradle\n/.gradle/\nbuild/\nlocal.properties\n/reports/\n\n# IntelliJ IDEA\n.idea/*\n*.iml\n*.ipl\n*.ipr\n*.iws\n.shelf/\n\n# kotlin\n.kotlin\n\n# iOS\n**/xcuserdata/\n\n# Steve Jobs\n.DS_Store\n"
  },
  {
    "path": "blueprints/starter/README.md",
    "content": "# Template App for Amazon App Platform\n\nThis is a Kotlin Multiplatform template application built using the [Amazon App Platform](https://github.com/amzn/app-platform). It provides a modern, opinionated starting point for building scalable, testable, and multiplatform Compose applications.\n\n## Overview\n\nThis template demonstrates:\n\n- Kotlin Multiplatform targeting Android, iOS, WebAssembly (WASM), and Desktop (JVM)\n- [App Platform](https://github.com/amzn/app-platform) conventions for Metro DI, state, rendering, and navigation\n- Molecule-powered presenters\n- Scoped dependency injection using Metro graphs, `@ContributesBinding`, `@SingleIn`, `@ContributesScoped`, and `@ContributesRenderer`\n- Reactive state with `StateFlow`\n- Compose UI for Android, Desktop, and WASM\n- Modular code structure for feature separation\n\n## Features\n\n- `ExampleRepository`: A simple `StateFlow`-based repository that emits data\n- `ExampleValueGenerator`: A scoped class that updates the repository with random values every 3 seconds\n- `NavigationHeaderPresenter` and `NavigationDetailPresenter`: Molecule presenters driving the top bar and content UI\n- `NavigationHeaderRenderer` and `NavigationDetailRenderer`: A ComposeRenderer showing example state\n\n## Modules\n\n- `:app` – Main app entrypoint using Compose + App Platform + Metro\n- `:templates` – Main module for templates and the entry point into the application\n- `:navigation` – Example feature module\n\n## Running the App\n\n### Android\n\n```bash\n./gradlew :app:installDebug\n```\n\n### WASM (WebAssembly)\n\n```bash\n./gradlew :app:wasmJsBrowserDevelopmentRun\n```\n\n### iOS\n\n#### Option 1: Run from IntelliJ IDEA or Android Studio\n\n1. Install the [Kotlin Multiplatform IDE plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform).\n\n2. Select the iosApp run configuration and run the app.\n\n#### Option 2: Run via Xcode\n\n1. Open the Xcode project:\n   ```bash\n   open iosApp/iosApp.xcodeproj\n   ```\n\n2. Select a simulator and run the app (`Cmd + R`)\n\n> The required Kotlin Multiplatform framework will be built automatically as part of the Xcode build process (`./gradlew :app:embedAndSignAppleFrameworkForXcode`).\n\n### Desktop (JVM)\n\n```bash\n./gradlew :app:run\n```\n\n> This runs the desktop Compose app using the JVM target.\n \n\n## Formatting\n\n### ktfmt\n```bash\nktfmt **/*.kt --google-style\n```\n\n> This will run through all the kt files and format them.\n\n## Configuration\n\nYou can modify app behavior by editing:\n\n- `gradle.properties` – JVM and native memory settings\n- `libs.versions.toml` – Centralized dependency version catalog\n- `app/build.gradle.kts` – Platform-specific targets and UI modules\n\n## Contributing\n\nFeel free to fork and adapt this template for your own projects. If you find bugs or improvements related to App Platform usage, consider opening issues or PRs against [amzn/app-platform](https://github.com/amzn/app-platform).\n\n## License\n\nThis project inherits the license of the [Amazon App Platform](https://github.com/amzn/app-platform).\n"
  },
  {
    "path": "blueprints/starter/app/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport dev.zacsweers.metro.gradle.DiagnosticSeverity\nimport dev.zacsweers.metro.gradle.MetroPluginExtension\nimport org.jetbrains.compose.desktop.application.dsl.TargetFormat\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget\nimport software.amazon.app.platform.gradle.AppPlatformPlugin\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidApplication)\n  alias(libs.plugins.kotlinMultiplatform)\n  alias(libs.plugins.composeMultiplatform)\n  alias(libs.plugins.composeCompiler)\n}\n\nappPlatform {\n  enableComposeUi(true)\n  enableMetro(true)\n  enableModuleStructure(true)\n  enableMoleculePresenters(true)\n  addImplModuleDependencies(true)\n}\n\nconfigure<MetroPluginExtension> {\n  unusedGraphInputsSeverity.set(DiagnosticSeverity.NONE)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidTarget {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  targets.withType<KotlinNativeTarget>().configureEach {\n    binaries.framework {\n      baseName = \"TemplateApp\"\n      AppPlatformPlugin.exportedDependencies().forEach { export(it) }\n    }\n  }\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n    binaries.executable()\n\n    browser {\n      commonWebpackConfig {\n        outputFileName = \"template-app.js\"\n      }\n    }\n  }\n\n  sourceSets {\n    val desktopMain by getting\n    commonMain {\n      dependencies {\n        implementation(project(\":navigation:impl\"))\n        implementation(project(\":templates:impl\"))\n\n        AppPlatformPlugin.exportedDependencies().forEach { api(it) }\n      }\n    }\n\n    androidMain {\n      dependencies {\n        implementation(libs.androidx.activity.compose)\n      }\n    }\n\n    desktopMain.dependencies {\n      implementation(compose.desktop.currentOs)\n      implementation(libs.coroutines.swing)\n    }\n  }\n}\n\nandroid {\n  compileSdk = libs.versions.android.compileSdk.get().toInt()\n\n  defaultConfig {\n    applicationId = \"software.amazon.app.platform.template\"\n    versionCode = 1\n    versionName = \"1.0\"\n    minSdk = libs.versions.android.minSdk.get().toInt()\n    targetSdk = libs.versions.android.targetSdk.get().toInt()\n  }\n\n  packaging {\n    resources {\n      excludes += \"/META-INF/{AL2.0,LGPL2.1}\"\n    }\n  }\n\n  buildTypes {\n    getByName(\"release\") {\n      isMinifyEnabled = false\n    }\n  }\n}\n\ncompose.desktop {\n  application {\n    mainClass = \"software.amazon.app.platform.template.MainKt\"\n\n    nativeDistributions {\n      targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)\n      packageName = \"TemplateApp\"\n      packageVersion = \"1.0.0\"\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n        android:name=\"software.amazon.app.platform.template.AndroidApplication\"\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@android:style/Theme.Material.Light.NoActionBar\"\n        tools:ignore=\"MissingApplicationIcon\">\n        <activity\n            android:name=\"software.amazon.app.platform.template.MainActivity\"\n            android:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/kotlin/software/amazon/app/platform/template/AndroidAppGraph.kt",
    "content": "package software.amazon.app.platform.template\n\nimport android.app.Application\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Android app graph. Note that [application] is an Android specific type and classes\n * living in the Android source folder can therefore inject [Application].\n */\n@DependencyGraph(AppScope::class)\ninterface AndroidAppGraph {\n  /** The factory to create a new instance of [AndroidAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [AndroidAppGraph] instance. [application] and [rootScopeProvider] are provided\n     * in the [AndroidAppGraph] and can be injected.\n     */\n    fun create(\n      @Provides application: Application,\n      @Provides rootScopeProvider: RootScopeProvider,\n    ): AndroidAppGraph\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/kotlin/software/amazon/app/platform/template/AndroidApplication.kt",
    "content": "package software.amazon.app.platform.template\n\nimport android.app.Application\nimport dev.zacsweers.metro.createGraphFactory\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * The [Application] class of our sample app. Note that this class implements [RootScopeProvider].\n * This is helpful to get access to the root scope from Android components such as activities.\n */\nopen class AndroidApplication : Application(), RootScopeProvider {\n  private val templateApplication = software.amazon.app.platform.template.Application()\n\n  override val rootScope: Scope\n    get() = templateApplication.rootScope\n\n  override fun onCreate() {\n    templateApplication.create(metroGraph(templateApplication))\n    super.onCreate()\n  }\n\n  /** Create the [AppGraph]. In UI tests we use a different instance. */\n  protected open fun metroGraph(\n    templateApplication: software.amazon.app.platform.template.Application\n  ): AppGraph {\n    return createGraphFactory<AndroidAppGraph.Factory>().create(this, templateApplication)\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/kotlin/software/amazon/app/platform/template/MainActivity.kt",
    "content": "package software.amazon.app.platform.template\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport software.amazon.app.platform.renderer.ComposeAndroidRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The only `Activity` of our sample app. This class is just an entry point to start rendering\n * templates.\n */\nclass MainActivity : ComponentActivity() {\n  private val rootScopeProvider\n    get() = application as RootScopeProvider\n\n  private val viewModel by viewModels<MainActivityViewModel>()\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    enableEdgeToEdge()\n\n    val rendererFactory =\n      ComposeAndroidRendererFactory.createForComposeUi(rootScopeProvider = rootScopeProvider)\n\n    setContent {\n      val template by viewModel.templates.collectAsState()\n\n      val renderer = rendererFactory.getComposeRenderer(template)\n      renderer.renderCompose(template)\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/kotlin/software/amazon/app/platform/template/MainActivityViewModel.kt",
    "content": "package software.amazon.app.platform.template\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\nimport software.amazon.app.platform.template.templates.AppTemplate\n\n/**\n * `ViewModel` that hosts the stream of templates and survives configuration changes. Note that we\n * use [application] to get access to the root scope.\n */\nclass MainActivityViewModel(application: Application) : AndroidViewModel(application) {\n  private val graph = (application as RootScopeProvider).rootScope.metroDependencyGraph<Graph>()\n  private val templateProvider = graph.templateProviderFactory.createTemplateProvider()\n\n  /** The stream of templates that are rendered by [MainActivity]. */\n  val templates: StateFlow<AppTemplate> = templateProvider.templates\n\n  override fun onCleared() {\n    templateProvider.cancel()\n  }\n\n  /** Graph interface to give us access to objects from the app graph. */\n  @ContributesTo(AppScope::class)\n  interface Graph {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/androidMain/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">TemplateApp</string>\n</resources>"
  },
  {
    "path": "blueprints/starter/app/src/commonMain/kotlin/software/amazon/app/platform/template/AppGraph.kt",
    "content": "package software.amazon.app.platform.template\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Multibinds\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\n\n/**\n * Shared interface for the app graph. The final graphs live in the platform specific source folders\n * in order to have access to platform specific code.\n */\n@ContributesTo(AppScope::class)\ninterface AppGraph {\n  /** All [Scoped] instances part of the app scope. */\n  @Multibinds(allowEmpty = true) @ForScope(AppScope::class) val appScopedInstances: Set<Scoped>\n\n  /** The coroutine scope that runs as long as the app scope is alive. */\n  @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/commonMain/kotlin/software/amazon/app/platform/template/Application.kt",
    "content": "package software.amazon.app.platform.template\n\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\nimport software.amazon.app.platform.scope.register\n\n/**\n * Shared class between the platform to manage the root scope. It itself implements the\n * [RootScopeProvider] interface.\n */\nclass Application : RootScopeProvider {\n  private var _rootScope: Scope? = null\n\n  override val rootScope: Scope\n    get() = checkNotNull(_rootScope) { \"Must call create() first.\" }\n\n  /** Creates the root scope and remembers the instance. */\n  fun create(appGraph: AppGraph) {\n    check(_rootScope == null) { \"create() should be called only once.\" }\n\n    _rootScope = Scope.buildRootScope {\n      addMetroDependencyGraph(appGraph)\n\n      addCoroutineScopeScoped(appGraph.appScopeCoroutineScopeScoped)\n    }\n\n    // Register instances after the rootScope has been set to avoid race conditions for Scoped\n    // instances that may use the rootScope.\n    rootScope.register(appGraph.appScopedInstances)\n  }\n\n  /** Destroys the root scope. */\n  fun destroy() {\n    rootScope.destroy()\n    _rootScope = null\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/commonMain/kotlin/software/amazon/app/platform/template/TemplateProvider.kt",
    "content": "package software.amazon.app.platform.template\n\nimport dev.zacsweers.metro.Assisted\nimport dev.zacsweers.metro.AssistedFactory\nimport dev.zacsweers.metro.AssistedInject\nimport dev.zacsweers.metro.Inject\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.presenter.molecule.MoleculeScope\nimport software.amazon.app.platform.presenter.molecule.MoleculeScopeFactory\nimport software.amazon.app.platform.presenter.molecule.launchMoleculePresenter\nimport software.amazon.app.platform.template.navigation.NavigationPresenter\nimport software.amazon.app.platform.template.templates.AppTemplate\nimport software.amazon.app.platform.template.templates.AppTemplatePresenter\n\n/**\n * Shared class between all platforms to start collecting [AppTemplate] in a [StateFlow]. Inject\n * [Factory] to create a new instance. Once the instance is no longer needed, call [cancel] to clean\n * up any resources.\n *\n * [NavigationPresenter] serves as the root presenter and gets wrapped in a [AppTemplatePresenter].\n */\n@AssistedInject\nclass TemplateProvider(\n  presenter: NavigationPresenter,\n  templatePresenterFactory: AppTemplatePresenter.Factory,\n  @Assisted private val moleculeScope: MoleculeScope,\n) {\n  /** The templates that should be rendered in the UI. */\n  val templates: StateFlow<AppTemplate> by lazy {\n    moleculeScope\n      .launchMoleculePresenter(\n        presenter = templatePresenterFactory.createAppTemplatePresenter(presenter),\n        input = Unit,\n      )\n      .model\n  }\n\n  /** Releases all resources and stops [templates] from updating further. */\n  fun cancel() {\n    moleculeScope.cancel()\n  }\n\n  /**\n   * The assisted factory for Metro to create a new [TemplateProvider]. This factory is wrapped by\n   * [Factory], which should be used instead.\n   */\n  @AssistedFactory\n  fun interface InternalFactory {\n    /** Create a new instance of [TemplateProvider] with the given [MoleculeScope]. */\n    fun create(moleculeScope: MoleculeScope): TemplateProvider\n  }\n\n  /** Factory class to create a new instance of [TemplateProvider]. */\n  @Inject\n  class Factory(\n    private val moleculeScopeFactory: MoleculeScopeFactory,\n    private val templateProviderFactory: InternalFactory,\n  ) {\n    /**\n     * Creates a new instance of [TemplateProvider]. Call [TemplateProvider.cancel] when the\n     * instance not needed anymore to avoid leaking resources.\n     */\n    fun createTemplateProvider(): TemplateProvider {\n      return templateProviderFactory.create(moleculeScopeFactory.createMoleculeScope())\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/desktopMain/kotlin/software/amazon/app/platform/template/DesktopApp.kt",
    "content": "package software.amazon.app.platform.template\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * Responsible for creating the app graph [graph] and producing templates. Call [destroy] to clean\n * up any resources.\n */\nclass DesktopApp(private val graph: (RootScopeProvider) -> AppGraph) : RootScopeProvider {\n  override val rootScope: Scope\n    get() = application.rootScope\n\n  private val application = Application().apply { create(graph(this)) }\n\n  private val templateProvider =\n    rootScope.metroDependencyGraph<Graph>().templateProviderFactory.createTemplateProvider()\n\n  /** Call this composable function to start rendering templates on the screen. */\n  @Composable\n  fun renderTemplates() {\n    val template by templateProvider.templates.collectAsState()\n\n    val factory = remember { ComposeRendererFactory(application) }\n\n    val renderer = factory.getComposeRenderer(template)\n    renderer.renderCompose(template)\n  }\n\n  /** Cancels and releases all resources. */\n  fun destroy() {\n    templateProvider.cancel()\n    application.destroy()\n  }\n\n  /** Graph interface to give us access to objects from the app graph. */\n  @ContributesTo(AppScope::class)\n  interface Graph {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/desktopMain/kotlin/software/amazon/app/platform/template/DesktopAppGraph.kt",
    "content": "package software.amazon.app.platform.template\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Desktop app graph. Unlike the Android and iOS specific counterpart, this class doesn't\n * have any platform specific types.\n */\n@DependencyGraph(AppScope::class)\ninterface DesktopAppGraph {\n  /** The factory to create a new instance of [DesktopAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [DesktopAppGraph] instance. [rootScopeProvider] is provided in the\n     * [DesktopAppGraph] and can be injected.\n     */\n    fun create(@Provides rootScopeProvider: RootScopeProvider): DesktopAppGraph\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/desktopMain/kotlin/software/amazon/app/platform/template/Main.kt",
    "content": "package software.amazon.app.platform.template\n\nimport androidx.compose.ui.window.Window\nimport androidx.compose.ui.window.application\nimport dev.zacsweers.metro.createGraphFactory\n\n/** The main function to launch the Desktop app. */\nfun main() {\n  val desktopApp = DesktopApp { createGraphFactory<DesktopAppGraph.Factory>().create(it) }\n\n  application {\n    Window(\n      onCloseRequest = {\n        desktopApp.destroy()\n        exitApplication()\n      },\n      alwaysOnTop = true,\n      title = \"Template App\",\n    ) {\n      desktopApp.renderTemplates()\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/iosMain/kotlin/software/amazon/app/platform/template/IosAppGraph.kt",
    "content": "package software.amazon.app.platform.template\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.createGraphFactory\nimport platform.UIKit.UIApplication\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final iOS app graph. Note that [uiApplication] is an iOS specific type and classes living in\n * the iOS source folder can therefore inject [UIApplication].\n */\n@DependencyGraph(AppScope::class)\ninterface IosAppGraph {\n  /** The factory to create a new instance of [IosAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [IosAppGraph] instance. [uiApplication] and [rootScopeProvider] are provided in\n     * the [IosAppGraph] and can be injected.\n     */\n    fun create(\n      @Provides uiApplication: UIApplication,\n      @Provides rootScopeProvider: RootScopeProvider,\n    ): IosAppGraph\n  }\n\n  /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n  val templateProviderFactory: TemplateProvider.Factory\n}\n\n/** This function is called from Swift to create a new graph instance. */\n@Suppress(\"unused\")\nfun createIosAppGraph(application: UIApplication, rootScopeProvider: RootScopeProvider): AppGraph {\n  return createGraphFactory<IosAppGraph.Factory>().create(application, rootScopeProvider)\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/iosMain/kotlin/software/amazon/app/platform/template/MainViewController.kt",
    "content": "package software.amazon.app.platform.template\n\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.window.ComposeUIViewController\nimport platform.UIKit.UIViewController\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * This function is called from Swift to hook up the Compose Multiplatform UI.\n *\n * This is our entry point to start producing templates and hooking up our [Renderer] runtime. Other\n * platforms extract this code into classes that are effectively singletons. But this approach is\n * good enough for the iOS sample.\n */\n@Suppress(\"unused\")\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    // Create a single instance.\n    val templateProvider = remember {\n      rootScopeProvider.rootScope\n        .metroDependencyGraph<IosAppGraph>()\n        .templateProviderFactory\n        .createTemplateProvider()\n    }\n\n    DisposableEffect(Unit) {\n      onDispose {\n        // Cancel the provider when it's no longer needed.\n        templateProvider.cancel()\n      }\n    }\n\n    // Only a single factory is needed.\n    val factory = remember { ComposeRendererFactory(rootScopeProvider) }\n\n    // Render templates using our Renderer runtime.\n    val template by templateProvider.templates.collectAsState()\n\n    val renderer = factory.getRenderer(template::class)\n    renderer.renderCompose(template)\n  }\n"
  },
  {
    "path": "blueprints/starter/app/src/wasmJsMain/kotlin/software/amazon/app/platform/template/Main.kt",
    "content": "package software.amazon.app.platform.template\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.ExperimentalComposeUiApi\nimport androidx.compose.ui.window.ComposeViewport\nimport dev.zacsweers.metro.createGraphFactory\nimport kotlinx.browser.document\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/** The entry point of our sample app. */\n@OptIn(ExperimentalComposeUiApi::class)\nfun main() {\n  ComposeViewport(checkNotNull(document.body)) { AppPlatform() }\n}\n\n@Composable\nprivate fun AppPlatform() {\n  val application = remember {\n    Application().apply { create(createGraphFactory<WasmJsAppGraph.Factory>().create(this)) }\n  }\n\n  // Create a single instance.\n  val templateProvider = remember {\n    application.rootScope\n      .metroDependencyGraph<WasmJsAppGraph>()\n      .templateProviderFactory\n      .createTemplateProvider()\n  }\n\n  DisposableEffect(Unit) {\n    onDispose {\n      // Cancel the provider when it's no longer needed.\n      templateProvider.cancel()\n    }\n  }\n\n  // Only a single factory is needed.\n  val factory = remember { ComposeRendererFactory(application) }\n\n  // Render templates using our Renderer runtime.\n  val template by templateProvider.templates.collectAsState()\n\n  val renderer = factory.getRenderer(template::class)\n  renderer.renderCompose(template)\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/wasmJsMain/kotlin/software/amazon/app/platform/template/WasmJsAppGraph.kt",
    "content": "package software.amazon.app.platform.template\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Wasm app graph.\n *\n * Unlike the Android and iOS specific counterpart, this class doesn't have any platform specific\n * types.\n */\n@DependencyGraph(AppScope::class)\ninterface WasmJsAppGraph {\n  /** The factory to create a new instance of [WasmJsAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [WasmJsAppGraph] instance. [rootScopeProvider] is provided in the\n     * [WasmJsAppGraph] and can be injected.\n     */\n    fun create(@Provides rootScopeProvider: RootScopeProvider): WasmJsAppGraph\n  }\n\n  /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n  val templateProviderFactory: TemplateProvider.Factory\n}\n"
  },
  {
    "path": "blueprints/starter/app/src/wasmJsMain/resources/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>TemplateApp</title>\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"styles.css\">\n    <script type=\"application/javascript\" src=\"template-app.js\"></script>\n</head>\n<body>\n</body>\n</html>"
  },
  {
    "path": "blueprints/starter/app/src/wasmJsMain/resources/styles.css",
    "content": "html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n}"
  },
  {
    "path": "blueprints/starter/build.gradle.kts",
    "content": "plugins {\n  alias(libs.plugins.androidApplication) apply false\n  alias(libs.plugins.androidKmpLibrary) apply false\n  alias(libs.plugins.kotlinMultiplatform) apply false\n  alias(libs.plugins.composeMultiplatform) apply false\n  alias(libs.plugins.composeCompiler) apply false\n  alias(libs.plugins.metro) apply false\n  alias(libs.plugins.appPlatform)\n}\n"
  },
  {
    "path": "blueprints/starter/gradle/libs.versions.toml",
    "content": "[versions]\nassertk = \"0.28.1\"\napp-platform = \"0.0.9\"\nagp = \"8.13.2\"\nandroid-compileSdk = \"36\"\nandroid-minSdk = \"23\"\nandroid-targetSdk = \"36\"\nandroidx-activity = \"1.13.0\"\ncompose-material-icons = \"1.7.3\"\ncompose-material3 = \"1.9.0\"\ncompose-multiplatform = \"1.10.3\"\ncoroutines = \"1.10.2\"\nkotlin = \"2.3.20\"\nmetro = \"1.0.0-RC2\"\n\n[libraries]\nandroidx-activity-compose = { module = \"androidx.activity:activity-compose\", version.ref = \"androidx-activity\" }\nassertk = { module = \"com.willowtreeapps.assertk:assertk\", version.ref = \"assertk\" }\ncompose-material = { module = \"org.jetbrains.compose.material3:material3\", version.ref = \"compose-material3\" }\ncompose-material-icons = { module = \"org.jetbrains.compose.material:material-icons-extended\", version.ref = \"compose-material-icons\" }\nkotlinx-coroutines-core = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-core\", version.ref = \"coroutines\" }\ncoroutines-swing = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-swing\", version.ref = \"coroutines\" }\ncoroutines-test = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-test\", version.ref = \"coroutines\" }\n\n[plugins]\nandroidApplication = { id = \"com.android.application\", version.ref = \"agp\" }\nandroidKmpLibrary = { id = \"com.android.kotlin.multiplatform.library\", version.ref = \"agp\" }\nappPlatform = { id = \"software.amazon.app.platform\", version.ref = \"app-platform\" }\nkotlinMultiplatform = { id = \"org.jetbrains.kotlin.multiplatform\", version.ref = \"kotlin\" }\ncomposeMultiplatform = { id = \"org.jetbrains.compose\", version.ref = \"compose-multiplatform\" }\ncomposeCompiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nmetro = { id = \"dev.zacsweers.metro\", version.ref = \"metro\" }\n"
  },
  {
    "path": "blueprints/starter/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "blueprints/starter/gradle.properties",
    "content": "GROUP=software.amazon.app.platform.template\n\norg.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8\norg.gradle.daemon=true\norg.gradle.parallel=true\norg.gradle.caching=true\n\nandroid.useAndroidX=true\nandroid.enableJetifier=false\nandroid.nonTransitiveRClass=true\n\n# https://youtrack.jetbrains.com/issue/KT-82395\nkotlin.incremental.js=false\nkotlin.incremental.js.klib=false\n"
  },
  {
    "path": "blueprints/starter/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "blueprints/starter/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "blueprints/starter/iosApp/Configuration/Config.xcconfig",
    "content": "TEAM_ID=\nBUNDLE_ID=software.amazon.app.platform.template.Template\nAPP_NAME=Template\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"app-icon-1024.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"tinted\"\n        }\n      ],\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/ComposeContentView.swift",
    "content": "import UIKit\nimport SwiftUI\nimport TemplateApp\n\nstruct ComposeView: UIViewControllerRepresentable {\n    private var rootScopeProvider: RootScopeProvider\n\n    init(rootScopeProvider: RootScopeProvider) {\n        self.rootScopeProvider = rootScopeProvider\n    }\n\n    func makeUIViewController(context: Context) -> UIViewController {\n        MainViewControllerKt.mainViewController(rootScopeProvider: rootScopeProvider)\n    }\n\n    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}\n}\n\nstruct ComposeContentView: View {\n    var rootScopeProvider: RootScopeProvider\n\n    init(rootScopeProvider: RootScopeProvider) {\n        self.rootScopeProvider = rootScopeProvider\n    }\n\n    var body: some View {\n        ComposeView(rootScopeProvider: rootScopeProvider).ignoresSafeArea(.keyboard) // Compose has own keyboard handler\n    }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>CADisableMinimumFrameDurationOnPhone</key>\n\t<true/>\n\t<key>UIApplicationSceneManifest</key>\n\t<dict>\n\t\t<key>UIApplicationSupportsMultipleScenes</key>\n\t\t<false/>\n\t</dict>\n\t<key>UILaunchScreen</key>\n\t<dict/>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp/iOSApp.swift",
    "content": "import TemplateApp\nimport SwiftUI\n\nclass AppDelegate: NSObject, UIApplicationDelegate, RootScopeProvider {\n\n    private let templateApplication: Application = Application()\n\n    var rootScope: Scope {\n        get {\n            templateApplication.rootScope\n        }\n    }\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {\n        templateApplication.create(appGraph: IosAppGraphKt.createIosAppGraph(application: application, rootScopeProvider: templateApplication))\n        return true\n    }\n}\n\n@main\nstruct iOSApp: App {\n\n    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            ComposeContentView(rootScopeProvider: appDelegate)\n        }\n    }\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 70;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t1BACA3B135BB44908CC94158 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */; };\n\t\t1BACAC1D12A9468E4FB4B657 /* ComposeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */; };\n\t\t530CD4FE2E208D79001A7515 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 530CD4FA2E208D79001A7515 /* Assets.xcassets */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposeContentView.swift; sourceTree = \"<group>\"; };\n\t\t1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = \"<group>\"; };\n\t\t530CD4FA2E208D79001A7515 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t530CD4FB2E208D79001A7515 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t7555FF7B242A565900829871 /* Template.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Template.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tAB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t530CD4F92E208D79001A7515 /* Preview Content */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = \"Preview Content\"; sourceTree = \"<group>\"; };\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXGroup section */\n\t\t7555FF72242A565900829871 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAB1DB47929225F7C00F7AF9C /* Configuration */,\n\t\t\t\t7555FF7D242A565900829871 /* iosApp */,\n\t\t\t\t7555FF7C242A565900829871 /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7555FF7C242A565900829871 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t7555FF7B242A565900829871 /* Template.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7555FF7D242A565900829871 /* iosApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */,\n\t\t\t\t1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */,\n\t\t\t\t530CD4F92E208D79001A7515 /* Preview Content */,\n\t\t\t\t530CD4FA2E208D79001A7515 /* Assets.xcassets */,\n\t\t\t\t530CD4FB2E208D79001A7515 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = iosApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tAB1DB47929225F7C00F7AF9C /* Configuration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAB3632DC29227652001CCB65 /* Config.xcconfig */,\n\t\t\t);\n\t\t\tpath = Configuration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t7555FF7A242A565900829871 /* iosApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget \"iosApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tF36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,\n\t\t\t\t7555FF77242A565900829871 /* Sources */,\n\t\t\t\t7555FF79242A565900829871 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t530CD4F92E208D79001A7515 /* Preview Content */,\n\t\t\t);\n\t\t\tname = iosApp;\n\t\t\tproductName = iosApp;\n\t\t\tproductReference = 7555FF7B242A565900829871 /* Template.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t7555FF73242A565900829871 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 1130;\n\t\t\t\tLastUpgradeCheck = 1130;\n\t\t\t\tORGANIZATIONNAME = orgName;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t7555FF7A242A565900829871 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 11.3.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject \"iosApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 7555FF72242A565900829871;\n\t\t\tproductRefGroup = 7555FF7C242A565900829871 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t7555FF7A242A565900829871 /* iosApp */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t7555FF79242A565900829871 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t530CD4FE2E208D79001A7515 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tF36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Compile Kotlin Framework\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"if [ \\\"YES\\\" = \\\"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\\\" ]; then\\n  echo \\\"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\\\\\"YES\\\\\\\"\\\"\\n  exit 0\\nfi\\ncd \\\"$SRCROOT/..\\\"\\n./gradlew :app:embedAndSignAppleFrameworkForXcode --rerun-tasks\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t7555FF77242A565900829871 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1BACAC1D12A9468E4FB4B657 /* ComposeContentView.swift in Sources */,\n\t\t\t\t1BACA3B135BB44908CC94158 /* iOSApp.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t7555FFA3242A565B00829871 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t7555FFA4242A565B00829871 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t7555FFA6242A565B00829871 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"iosApp/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"${TEAM_ID}\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = \"$(SRCROOT)/../app/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\";\n\t\t\t\tINFOPLIST_FILE = iosApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-framework\",\n\t\t\t\t\tTemplateApp,\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"${BUNDLE_ID}${TEAM_ID}\";\n\t\t\t\tPRODUCT_NAME = \"${APP_NAME}\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t7555FFA7242A565B00829871 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"iosApp/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"${TEAM_ID}\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = \"$(SRCROOT)/../app/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\";\n\t\t\t\tINFOPLIST_FILE = iosApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-framework\",\n\t\t\t\t\tTemplateApp,\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"${BUNDLE_ID}${TEAM_ID}\";\n\t\t\t\tPRODUCT_NAME = \"${APP_NAME}\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t7555FF76242A565900829871 /* Build configuration list for PBXProject \"iosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t7555FFA3242A565B00829871 /* Debug */,\n\t\t\t\t7555FFA4242A565B00829871 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget \"iosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t7555FFA6242A565B00829871 /* Debug */,\n\t\t\t\t7555FFA7242A565B00829871 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 7555FF73242A565900829871 /* Project object */;\n}\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "blueprints/starter/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1630\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\"\n      buildArchitectures = \"Automatic\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"7555FF7A242A565900829871\"\n               BuildableName = \"Template.app\"\n               BlueprintName = \"iosApp\"\n               ReferencedContainer = \"container:iosApp.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"7555FF7A242A565900829871\"\n            BuildableName = \"Template.app\"\n            BlueprintName = \"iosApp\"\n            ReferencedContainer = \"container:iosApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"7555FF7A242A565900829871\"\n            BuildableName = \"Template.app\"\n            BlueprintName = \"iosApp\"\n            ReferencedContainer = \"container:iosApp.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidKmpLibrary)\n  alias(libs.plugins.kotlinMultiplatform)\n}\n\nappPlatform {\n  enableComposeUi(true)\n  enableModuleStructure(true)\n  enableMetro(true)\n  enableMoleculePresenters(true)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidLibrary {\n    compileSdk = libs.versions.android.compileSdk.get().toInt()\n    minSdk = libs.versions.android.minSdk.get().toInt()\n\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n\n    browser()\n  }\n\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(libs.compose.material)\n        implementation(libs.compose.material.icons)\n        implementation(project(\":templates:public\"))\n      }\n    }\n    commonTest {\n      dependencies {\n        implementation(kotlin(\"test\"))\n        implementation(libs.assertk)\n        implementation(libs.coroutines.test)\n        implementation(project(\":navigation:testing\"))\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/ExampleRepositoryImpl.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.SingleIn\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\n\n/**\n * Default implementation of [ExampleRepository] that holds an integer [StateFlow] and allows its\n * value to be updated.\n *\n * Useful for testing reactive state flow usage with presenters or other consumers.\n */\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class)\nclass ExampleRepositoryImpl : ExampleRepository {\n  private val _exampleStateFlow = MutableStateFlow(0)\n  override val exampleStateFlow: StateFlow<Int> = _exampleStateFlow.asStateFlow()\n\n  override fun setExampleFlowValue(value: Int) {\n    println(\"value: $value\")\n    _exampleStateFlow.value = value\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/ExampleValueGenerator.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.SingleIn\nimport kotlinx.coroutines.delay\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.launch\n\n/**\n * A scoped service that continuously generates random values and feeds them into\n * [ExampleRepository] every 3 seconds. This is active only while the [AppScope] is alive.\n *\n * This class is:\n * - Bound to [AppScope] via `@ContributesScoped`\n * - A singleton within that scope via `@SingleIn`\n * - Injected via constructor using `@Inject`\n *\n * The generator starts emitting random integers in the range 1 to 100 as soon as the scope is\n * entered.\n *\n * @property exampleRepository the repository where generated values are pushed\n */\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass ExampleValueGenerator(private val exampleRepository: ExampleRepository) : Scoped {\n  override fun onEnterScope(scope: Scope) {\n    scope.launch {\n      while (true) {\n        val random = (1..100).random()\n        println(\"random: $random\")\n        exampleRepository.setExampleFlowValue(random)\n        delay(3000L)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationDetailPresenterImpl.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlinx.coroutines.delay\nimport software.amazon.app.platform.template.navigation.NavigationDetailPresenter.Model\n\n@Inject\n@ContributesBinding(AppScope::class)\nclass NavigationDetailPresenterImpl(private val exampleRepository: ExampleRepository) :\n  NavigationDetailPresenter {\n  @Composable\n  override fun present(input: Unit): Model {\n    val exampleValue by exampleRepository.exampleStateFlow.collectAsState()\n    var exampleCount by remember { mutableStateOf(0) }\n\n    LaunchedEffect(exampleValue) {\n      // Add a delay, otherwise the state is not updating properly on iOS.\n      delay(1.milliseconds)\n      exampleCount++\n    }\n\n    return Model(exampleValue = exampleValue, exampleCount = exampleCount)\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationDetailRenderer.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.template.navigation.NavigationDetailPresenter.Model\n\n@ContributesRenderer\nclass NavigationDetailRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(\n      modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background),\n      verticalArrangement = Arrangement.Center,\n      horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n      Text(\n        text = \"Hello, welcome to amzn/app-platform Template App\",\n        style = MaterialTheme.typography.headlineMedium,\n        textAlign = TextAlign.Center,\n        modifier = Modifier.padding(16.dp),\n      )\n      Text(\n        text = \"Every 3 seconds a new exampleValue is generated: ${model.exampleValue}\",\n        style = MaterialTheme.typography.bodyLarge,\n        textAlign = TextAlign.Center,\n        modifier = Modifier.padding(16.dp),\n      )\n      Text(\n        text = \"Total number of exampleValues shown: ${model.exampleCount}\",\n        style = MaterialTheme.typography.bodyLarge,\n        textAlign = TextAlign.Center,\n        modifier = Modifier.padding(16.dp),\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationHeaderPresenterImpl.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.template.navigation.NavigationHeaderPresenter.Model\n\n@Inject\n@ContributesBinding(AppScope::class)\nclass NavigationHeaderPresenterImpl() : NavigationHeaderPresenter {\n  @Composable\n  override fun present(input: Unit): Model {\n    var clickedCount by remember { mutableStateOf(0) }\n\n    return Model(clickedCount = clickedCount) {\n      when (it) {\n        NavigationHeaderPresenter.Event.Clicked -> {\n          clickedCount++\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationHeaderRenderer.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Stairs\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.template.navigation.NavigationHeaderPresenter.Model\n\n@ContributesRenderer\nclass NavigationHeaderRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {\n      Row(\n        modifier = Modifier.fillMaxWidth().padding(24.dp),\n        horizontalArrangement = Arrangement.SpaceBetween,\n        verticalAlignment = Alignment.CenterVertically,\n      ) {\n        Row(verticalAlignment = Alignment.CenterVertically) {\n          Icon(\n            imageVector = Icons.Filled.Stairs,\n            contentDescription = \"Icon\",\n            tint = MaterialTheme.colorScheme.onBackground,\n            modifier = Modifier.size(24.dp),\n          )\n          Spacer(Modifier.width(8.dp))\n          Text(\n            \"Template App\",\n            color = MaterialTheme.colorScheme.onBackground,\n            style = MaterialTheme.typography.titleMedium,\n          )\n        }\n\n        Text(\n          text = \"Click Me (times clicked: ${model.clickedCount})\",\n          color = MaterialTheme.colorScheme.onBackground,\n          style = MaterialTheme.typography.titleMedium,\n          // Sends event to NavigationHeaderPresenter to be processed which will update\n          // the above clickedCount value.\n          modifier = Modifier.clickable { model.onEvent(NavigationHeaderPresenter.Event.Clicked) },\n        )\n      }\n\n      Spacer(\n        modifier =\n          Modifier.fillMaxWidth().height(1.dp).background(MaterialTheme.colorScheme.primary)\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationPresenterImpl.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.runtime.Composable\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.template.templates.AppTemplate\n\n@Inject\n@ContributesBinding(AppScope::class)\nclass NavigationPresenterImpl(\n  private val navigationHeaderPresenter: NavigationHeaderPresenter,\n  private val navigationDetailPresenter: NavigationDetailPresenter,\n) : NavigationPresenter {\n  @Composable\n  override fun present(input: Unit): BaseModel {\n    val navigationBarModel = navigationHeaderPresenter.present(Unit)\n    val navigationDetailModel = navigationDetailPresenter.present(Unit)\n    return AppTemplate.HeaderDetailTemplate(navigationBarModel, navigationDetailModel)\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/template/navigation/NavigationDetailPresenterTest.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.test.Test\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.molecule.test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass NavigationDetailPresenterTest {\n\n  @Test\n  fun `model changes when setExampleFlowValue is called`() = runTest {\n    val exampleRepository = FakeExampleRepository()\n    NavigationDetailPresenterImpl(exampleRepository).test(this) {\n      awaitItem().let { model ->\n        assertThat(model.exampleValue).isEqualTo(0)\n        assertThat(model.exampleCount).isEqualTo(0)\n      }\n\n      exampleRepository.setExampleFlowValue(5)\n\n      awaitItem().let { model -> assertThat(model.exampleValue).isEqualTo(5) }\n\n      // There is a 1 milli delay within presenter before updating count.\n      advanceTimeBy(1.milliseconds)\n\n      awaitItem().let { model ->\n        assertThat(model.exampleValue).isEqualTo(5)\n        assertThat(model.exampleCount).isEqualTo(1)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/template/navigation/NavigationHeaderPresenterTest.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.test.Test\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.molecule.test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass NavigationHeaderPresenterTest {\n\n  @Test\n  fun `correctly process and emit model when Clicked event is triggered`() = runTest {\n    NavigationHeaderPresenterImpl().test(this) {\n      awaitItem().let { model ->\n        assertThat(model.clickedCount).isEqualTo(0)\n        model.onEvent(NavigationHeaderPresenter.Event.Clicked)\n      }\n\n      awaitItem().let { model ->\n        assertThat(model.clickedCount).isEqualTo(1)\n        model.onEvent(NavigationHeaderPresenter.Event.Clicked)\n      }\n\n      awaitItem().let { model -> assertThat(model.clickedCount).isEqualTo(2) }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/template/navigation/NavigationPresenterImplTest.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport androidx.compose.runtime.Composable\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport kotlin.test.Test\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.molecule.test\nimport software.amazon.app.platform.template.templates.AppTemplate\n\nclass NavigationPresenterImplTest {\n\n  @Test\n  fun `correct template and presenter models are returned`() = runTest {\n    val presenter =\n      NavigationPresenterImpl(\n        navigationHeaderPresenter = FakeNavigationHeaderPresenter(),\n        navigationDetailPresenter = FakeNavigationDetailPresenter(),\n      )\n\n    presenter.test(this) {\n      awaitItem().let { template ->\n        assertThat(template).isInstanceOf<AppTemplate.HeaderDetailTemplate>()\n        (template as? AppTemplate.HeaderDetailTemplate)?.let { headerDetailTemplate ->\n          assertThat(headerDetailTemplate.header).isInstanceOf<NavigationHeaderPresenter.Model>()\n          assertThat(headerDetailTemplate.detail).isInstanceOf<NavigationDetailPresenter.Model>()\n        }\n      }\n    }\n  }\n\n  private class FakeNavigationDetailPresenter : NavigationDetailPresenter {\n    @Composable\n    override fun present(input: Unit): NavigationDetailPresenter.Model =\n      NavigationDetailPresenter.Model(exampleValue = 5, exampleCount = 1)\n  }\n\n  private class FakeNavigationHeaderPresenter : NavigationHeaderPresenter {\n    @Composable\n    override fun present(input: Unit): NavigationHeaderPresenter.Model =\n      NavigationHeaderPresenter.Model(clickedCount = 0) {}\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/public/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidKmpLibrary)\n  alias(libs.plugins.kotlinMultiplatform)\n}\n\nappPlatform {\n  enableModuleStructure(true)\n  enableMoleculePresenters(true)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidLibrary {\n    compileSdk = libs.versions.android.compileSdk.get().toInt()\n    minSdk = libs.versions.android.minSdk.get().toInt()\n\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n\n    browser()\n  }\n\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(libs.kotlinx.coroutines.core)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/public/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/ExampleRepository.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport kotlinx.coroutines.flow.StateFlow\n\n/**\n * Interface of an example repository to show how to correctly contribute, inject, and use within\n * presenters.\n */\ninterface ExampleRepository {\n  val exampleStateFlow: StateFlow<Int>\n\n  fun setExampleFlowValue(value: Int)\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/public/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationDetailPresenter.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/** Presenter responsible for the state of the main content area beneath the navigation header. */\ninterface NavigationDetailPresenter : MoleculePresenter<Unit, NavigationDetailPresenter.Model> {\n  data class Model(val exampleValue: Int, val exampleCount: Int) : BaseModel\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/public/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationHeaderPresenter.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/**\n * Presenter responsible for the state of the top navigation bar (header).\n *\n * This typically controls high-level UI elements such as titles, toggle buttons, or contextual\n * actions that affect the overall screen.\n */\ninterface NavigationHeaderPresenter : MoleculePresenter<Unit, NavigationHeaderPresenter.Model> {\n  data class Model(val clickedCount: Int, val onEvent: (Event) -> Unit) : BaseModel\n\n  /** Events that can be triggered by the UI layer (Renderer) and processed by the Presenter. */\n  sealed interface Event {\n    data object Clicked : Event\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/public/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/NavigationPresenter.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/**\n * A presenter that hosts other presenters and returns their models. For that reason this presenter\n * doesn't have its own [BaseModel] type and returns [BaseModel].\n */\ninterface NavigationPresenter : MoleculePresenter<Unit, BaseModel>\n"
  },
  {
    "path": "blueprints/starter/navigation/testing/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidKmpLibrary)\n  alias(libs.plugins.kotlinMultiplatform)\n}\n\nappPlatform {\n  enableModuleStructure(true)\n  enableMoleculePresenters(true)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidLibrary {\n    compileSdk = libs.versions.android.compileSdk.get().toInt()\n    minSdk = libs.versions.android.minSdk.get().toInt()\n\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n\n    browser()\n  }\n\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(libs.kotlinx.coroutines.core)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/navigation/testing/src/commonMain/kotlin/software/amazon/app/platform/template/navigation/FakeExampleRepository.kt",
    "content": "package software.amazon.app.platform.template.navigation\n\nimport kotlinx.coroutines.flow.MutableStateFlow\n\n/**\n * Fake implementation of [ExampleRepository], which is useful in unit tests.\n *\n * This class is part of the `:testing` module and shared with other modules.\n */\nclass FakeExampleRepository(\n  override val exampleStateFlow: MutableStateFlow<Int> = MutableStateFlow(0)\n) : ExampleRepository {\n\n  override fun setExampleFlowValue(value: Int) {\n    exampleStateFlow.value = value\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/settings.gradle.kts",
    "content": "pluginManagement {\n  repositories {\n    google()\n    mavenCentral()\n    gradlePluginPortal()\n  }\n}\n\ndependencyResolutionManagement {\n  repositories {\n    google()\n    mavenCentral()\n  }\n}\n\nrootProject.name = \"Template\"\n\ninclude(\":app\")\ninclude(\":navigation:impl\")\ninclude(\":navigation:public\")\ninclude(\":navigation:testing\")\ninclude(\":templates:impl\")\ninclude(\":templates:public\")\n"
  },
  {
    "path": "blueprints/starter/templates/impl/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidKmpLibrary)\n  alias(libs.plugins.kotlinMultiplatform)\n}\n\nappPlatform {\n  enableComposeUi(true)\n  enableModuleStructure(true)\n  enableMetro(true)\n  enableMoleculePresenters(true)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidLibrary {\n    compileSdk = libs.versions.android.compileSdk.get().toInt()\n    minSdk = libs.versions.android.minSdk.get().toInt()\n\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n\n    browser()\n  }\n\n  sourceSets {\n    commonMain {\n      dependencies {\n        implementation(libs.compose.material)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/template/templates/ComposeAppTemplateRenderer.kt",
    "content": "package software.amazon.app.platform.template.templates\n\nimport androidx.compose.animation.ExperimentalSharedTransitionApi\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.safeDrawing\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\n\n/**\n * A Compose renderer implementation for templates used in the sample application.\n *\n * [rendererFactory] is used to get the [software.amazon.app.platform.renderer.Renderer] for the\n * [software.amazon.app.platform.presenter.BaseModel] wrapped in the template.\n */\n@OptIn(ExperimentalSharedTransitionApi::class)\n@Inject\n@ContributesRenderer\nclass ComposeAppTemplateRenderer(private val rendererFactory: RendererFactory) :\n  ComposeRenderer<AppTemplate>() {\n  @Composable\n  override fun Compose(model: AppTemplate) {\n    MaterialTheme {\n      Box(Modifier.Companion.windowInsetsPadding(WindowInsets.Companion.safeDrawing)) {\n        when (model) {\n          is AppTemplate.FullScreenTemplate -> FullScreen(model)\n          is AppTemplate.HeaderDetailTemplate -> HeaderDetail(model)\n        }\n      }\n    }\n  }\n\n  @Composable\n  private fun FullScreen(template: AppTemplate.FullScreenTemplate) {\n    val renderer = rendererFactory.getComposeRenderer(template.model)\n    renderer.renderCompose(template.model)\n  }\n\n  @Composable\n  private fun HeaderDetail(template: AppTemplate.HeaderDetailTemplate) {\n    Column {\n      Row(Modifier.Companion.weight(1f)) {\n        rendererFactory.getComposeRenderer(template.header).renderCompose(template.header)\n      }\n      Row(Modifier.Companion.weight(5f)) {\n        rendererFactory.getComposeRenderer(template.detail).renderCompose(template.detail)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/templates/public/build.gradle.kts",
    "content": "@file:OptIn(ExperimentalWasmDsl::class)\n\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\n\nplugins {\n  alias(libs.plugins.appPlatform)\n  alias(libs.plugins.androidKmpLibrary)\n  alias(libs.plugins.kotlinMultiplatform)\n}\n\nappPlatform {\n  enableModuleStructure(true)\n  enableMetro(true)\n  enableMoleculePresenters(true)\n}\n\nkotlin {\n  jvm(\"desktop\") {\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  androidLibrary {\n    compileSdk = libs.versions.android.compileSdk.get().toInt()\n    minSdk = libs.versions.android.minSdk.get().toInt()\n\n    compilerOptions {\n      jvmTarget.set(JvmTarget.JVM_11)\n    }\n  }\n\n  iosArm64()\n  iosSimulatorArm64()\n\n  wasmJs {\n    outputModuleName = project.path.removePrefix(\":\").replace(\":\", \"-\")\n\n    browser()\n  }\n}\n"
  },
  {
    "path": "blueprints/starter/templates/public/src/commonMain/kotlin/software/amazon/app/platform/template/templates/AppTemplate.kt",
    "content": "package software.amazon.app.platform.template.templates\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.template.Template\n\n/** All [Template]s implemented in the sample application. */\nsealed interface AppTemplate : Template {\n  /** A template that hosts a single model, which should rendered as full-screen element. */\n  data class FullScreenTemplate(\n    /** The model to be rendered fullscreen. */\n    val model: BaseModel\n  ) : AppTemplate\n\n  data class HeaderDetailTemplate(val header: BaseModel, val detail: BaseModel) : AppTemplate\n}\n"
  },
  {
    "path": "blueprints/starter/templates/public/src/commonMain/kotlin/software/amazon/app/platform/template/templates/AppTemplatePresenter.kt",
    "content": "package software.amazon.app.platform.template.templates\n\nimport androidx.compose.runtime.Composable\nimport dev.zacsweers.metro.Assisted\nimport dev.zacsweers.metro.AssistedFactory\nimport dev.zacsweers.metro.AssistedInject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.presenter.template.ModelDelegate\nimport software.amazon.app.platform.presenter.template.toTemplate\n\n/**\n * A presenter that wraps any other presenter and turns the emitted models from the other presenter\n * into [AppTemplate]s.\n *\n * Inject [Factory] to create a new instance of [AppTemplatePresenter].\n */\n@AssistedInject\nclass AppTemplatePresenter(@Assisted private val rootPresenter: MoleculePresenter<Unit, *>) :\n  MoleculePresenter<Unit, AppTemplate> {\n  @Composable\n  override fun present(input: Unit): AppTemplate {\n    @Suppress(\"RemoveEmptyParenthesesFromLambdaCall\")\n    return returningCompositionLocalProvider(\n      // Add local composition providers if needed.\n    ) {\n      rootPresenter.present(Unit).toTemplate<AppTemplate> { AppTemplate.FullScreenTemplate(it) }\n    }\n  }\n\n  /** A factory to instantiate a new [AppTemplatePresenter] instance. */\n  @AssistedFactory\n  fun interface Factory {\n    /**\n     * Create a new [AppTemplatePresenter]. The given [presenter] will be wrapped and its models are\n     * transformed into a [AppTemplate] with [AppTemplate.FullScreenTemplate] as default. The given\n     * [presenter] can override the template by either returning [AppTemplate] directly or making\n     * its [BaseModel] type implement [ModelDelegate].\n     */\n    fun createAppTemplatePresenter(rootPresenter: MoleculePresenter<Unit, *>): AppTemplatePresenter\n  }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": " plugins {\n     id 'software.amazon.app.platform.root'\n }\n"
  },
  {
    "path": "buildSrc/build.gradle",
    "content": "//file:noinspection UnnecessaryQualifiedReference\nplugins {\n    id 'java-gradle-plugin'\n    alias libs.plugins.kotlin.jvm\n    alias libs.plugins.ktfmt\n    alias libs.plugins.build.config\n}\n\nbuildConfig {\n    buildConfigField(String, 'APP_PLATFORM_GROUP', property('GROUP'))\n}\n\nktfmt {\n    googleStyle()\n    trailingCommaManagementStrategy.set(com.ncorti.ktfmt.gradle.TrailingCommaManagementStrategy.COMPLETE)\n    removeUnusedImports.set(true)\n}\n\njava {\n    sourceCompatibility = libs.versions.jvm.buildsrc.get()\n    targetCompatibility = libs.versions.jvm.buildsrc.get()\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(libs.versions.jvm.buildsrc.get()))\n    }\n\n    explicitApi()\n}\n\ngradlePlugin {\n    plugins {\n        appPlatformAppPlugin {\n            id = \"software.amazon.app.platform.app\"\n            displayName = \"App Platform App Gradle Plugin\"\n            implementationClass = \"software.amazon.app.platform.gradle.buildsrc.AppPlugin\"\n            description = \"The Gradle convention plugin for app modules.\"\n        }\n        appPlatformLibPlugin {\n            id = \"software.amazon.app.platform.lib\"\n            displayName = \"App Platform Library Gradle Plugin\"\n            implementationClass = \"software.amazon.app.platform.gradle.buildsrc.LibraryPlugin\"\n            description = \"The Gradle convention plugin for library modules.\"\n        }\n        appPlatformJvmLibPlugin {\n            id = \"software.amazon.app.platform.lib.jvm\"\n            displayName = \"App Platform JVM Library Gradle Plugin\"\n            implementationClass = \"software.amazon.app.platform.gradle.buildsrc.JvmLibraryPlugin\"\n            description = \"The Gradle convention plugin for JVM library modules.\"\n        }\n        appPlatformRootPlugin {\n            id = \"software.amazon.app.platform.root\"\n            displayName = \"App Platform Root Gradle Plugin\"\n            implementationClass = \"software.amazon.app.platform.gradle.buildsrc.RootPlugin\"\n            description = \"The Gradle convention plugin for the root module.\"\n        }\n    }\n}\n\ndependencies {\n    implementation libs.android.gradle.plugin.api\n    implementation libs.android.gradle.plugin.asProvider()\n\n    implementation libs.compose.gradle.plugin\n    implementation libs.kotlin.gradle.plugin.api\n    implementation libs.kotlin.multiplatform.gradle.plugin\n    implementation libs.kotlin.compose.gradle.plugin\n    implementation libs.kotlinx.binaryCompatibilityValidator\n    runtimeOnly libs.kotlin.gradle.plugin.asProvider()\n\n    // This is needed to reference KspExperimental for experimental features.\n    compileOnly libs.ksp.api\n    implementation libs.ksp.gradle.plugin\n\n    implementation libs.graphviz.java\n    implementation libs.kotlin.hierarchy.plugin\n    implementation libs.ktfmt.gradle.plugin\n    implementation libs.maven.publish.gradle.plugin\n    implementation libs.metro.gradle.plugin\n    implementation libs.detekt.gradle.plugin\n    implementation \"$GROUP:gradle-plugin:$VERSION_NAME\"\n}\n\ntasks.register('release') {\n    dependsOn('build', 'check', 'ktfmtCheck')\n}\n"
  },
  {
    "path": "buildSrc/settings.gradle",
    "content": "includeBuild('../gradle-plugin') {\n    dependencySubstitution {\n        substitute module(\"$GROUP:gradle-plugin\") using project(':')\n    }\n}\n\ndependencyResolutionManagement {\n    repositories {\n        mavenCentral()\n        google()\n        gradlePluginPortal()\n\n        maven {\n            url = \"https://central.sonatype.com/repository/maven-snapshots/\"\n        }\n    }\n\n    versionCatalogs {\n        libs {\n            from files('../gradle/libs.versions.toml')\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/AppPlatformExtension.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport javax.inject.Inject\nimport org.gradle.api.Project\nimport org.gradle.api.model.ObjectFactory\nimport org.gradle.api.provider.Property\nimport software.amazon.app.platform.gradle.AppPlatformExtension as AppPlatformExtensionGradlePlugin\nimport software.amazon.app.platform.gradle.buildsrc.BaseAndroidPlugin.Companion.enableInstrumentedTests\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.enableCompose\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.enableKotlinInject\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.enableMetro\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.enableMolecule\nimport software.amazon.app.platform.gradle.buildsrc.SdkPlugin.publishSdk\n\n@Suppress(\"unused\")\npublic open class AppPlatformExtension\n@Inject\nconstructor(objects: ObjectFactory, private val project: Project) {\n  private val enableCompose: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enableCompose(enabled: Boolean) {\n    enableCompose.set(enabled)\n    enableCompose.disallowChanges()\n\n    if (enabled) {\n      project.enableCompose()\n    }\n  }\n\n  internal fun isComposeEnabled(): Property<Boolean> = enableCompose\n\n  private val enableKotlinInject: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enableKotlinInject(enabled: Boolean) {\n    enableKotlinInject.set(enabled)\n    enableKotlinInject.disallowChanges()\n\n    if (enabled) {\n      project.enableKotlinInject()\n    }\n  }\n\n  internal fun isKotlinInjectEnabled(): Property<Boolean> = enableKotlinInject\n\n  private val enableMetro: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enableMetro(enabled: Boolean) {\n    enableMetro.set(enabled)\n    enableMetro.disallowChanges()\n\n    if (enabled) {\n      project.enableMetro()\n    }\n  }\n\n  internal fun isMetroEnabled(): Property<Boolean> = enableMetro\n\n  private val enableMolecule: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enableMolecule(enabled: Boolean) {\n    enableMolecule.set(enabled)\n    enableMolecule.disallowChanges()\n\n    if (enabled) {\n      project.enableMolecule()\n    }\n  }\n\n  internal fun isMoleculeEnabled(): Property<Boolean> = enableMolecule\n\n  private val enablePublishing: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enablePublishing(enabled: Boolean) {\n    enablePublishing.set(enabled)\n    enablePublishing.disallowChanges()\n\n    if (enabled) {\n      project.publishSdk()\n    }\n  }\n\n  internal fun isPublishingEnabled(): Property<Boolean> = enablePublishing\n\n  private val kotlinWarningsAsErrors: Property<Boolean> =\n    objects\n      .property(Boolean::class.java)\n      .convention(\n        project.provider {\n          project.ci || project.gradle.taskGraph.hasTask(\"${project.path}:release\")\n        }\n      )\n\n  public fun kotlinWarningsAsErrors(enabled: Boolean) {\n    kotlinWarningsAsErrors.set(enabled)\n    kotlinWarningsAsErrors.finalizeValueOnRead()\n  }\n\n  internal fun isKotlinWarningsAsErrors(): Property<Boolean> = kotlinWarningsAsErrors\n\n  private val enableInstrumentedTests: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  public fun enableInstrumentedTests(enabled: Boolean) {\n    if (enableInstrumentedTests.get() == enabled) {\n      return\n    }\n\n    enableInstrumentedTests.set(enabled)\n    enableInstrumentedTests.disallowChanges()\n\n    if (enabled) {\n      project.enableInstrumentedTests()\n    }\n  }\n\n  internal fun isInstrumentedTestsEnabled(): Property<Boolean> = enableInstrumentedTests\n\n  internal companion object {\n    val Project.appPlatformBuildSrc: AppPlatformExtension\n      get() = extensions.getByType(AppPlatformExtension::class.java)\n\n    val Project.appPlatformGradlePlugin: AppPlatformExtensionGradlePlugin\n      get() = extensions.getByType(AppPlatformExtensionGradlePlugin::class.java)\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/AppPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport kotlin.math.max\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.util.internal.VersionNumber\nimport org.jetbrains.compose.desktop.DesktopExtension\nimport org.jetbrains.compose.desktop.application.dsl.TargetFormat\nimport org.jetbrains.kotlin.gradle.ExperimentalWasmDsl\nimport org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig\nimport software.amazon.app.platform.gradle.AppPlatformPlugin\nimport software.amazon.app.platform.gradle.buildsrc.AppPlugin.App.Companion.app\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.composeMultiplatform\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.kmpExtension\nimport software.amazon.app.platform.gradle.isAppModule\nimport software.amazon.app.platform.gradle.isRobotsModule\nimport software.amazon.app.platform.gradle.isTestingModule\n\npublic open class AppPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.plugins.apply(Plugins.ANDROID_APP)\n\n    target.plugins.apply(BasePlugin::class.java)\n    target.plugins.apply(KmpPlugin::class.java)\n    target.plugins.apply(BaseAndroidPlugin::class.java)\n\n    target.configureAndroidSettings()\n    target.makeSingleVariant()\n    target.addDependencies()\n    target.configureWasm()\n\n    target.plugins.withId(Plugins.COMPOSE_MULTIPLATFORM) { target.configureDesktopApp() }\n  }\n\n  private fun Project.configureAndroidSettings() {\n    android.defaultConfig.minSdk = 26\n  }\n\n  private fun Project.makeSingleVariant() {\n    // Disable the release build type in the app module. We only need one build type\n    // and everything else is overhead.\n    androidComponents.beforeVariants { variant ->\n      if (variant.buildType != \"debug\") {\n        variant.enable = false\n      }\n    }\n  }\n\n  private fun Project.addDependencies() {\n    // iOS exports these dependencies for the iOS Framework and requires them to be added as\n    // \"api\" dependency to the project.\n    allExportedDependencies().forEach { dependency ->\n      kmpExtension.sourceSets.getByName(\"commonMain\").dependencies { api(dependency) }\n    }\n  }\n\n  @OptIn(ExperimentalWasmDsl::class)\n  private fun Project.configureWasm() {\n    // For development use the Gradle task 'wasmJsBrowserDevelopmentRun'.\n    //\n    // Release builds are built with 'wasmJsBrowserDistribution'. To test the release run\n    // 'npx http-server' from the folder 'sample/app/build/dist/wasmJs/productionExecutable'.\n\n    // Keep references to the Project outside of the lambdas below, otherwise this will break\n    // the configuration cache.\n    val jsFileName = app.jsFileName\n    val outputName = safePathString\n\n    kmpExtension.wasmJs {\n      browser {\n        outputModuleName.set(outputName)\n        commonWebpackConfig {\n          it.outputFileName = jsFileName\n          it.devServer = it.devServer ?: KotlinWebpackConfig.DevServer()\n        }\n      }\n      binaries.executable()\n    }\n  }\n\n  internal companion object {\n    fun Project.allExportedDependencies(): Set<Any> {\n      return AppPlatformPlugin.exportedDependencies()\n        .plus(\n          project(app.rootProjectPath)\n            .subprojects\n            .filter { it.subprojects.isEmpty() }\n            .filter { !it.isRobotsModule() && !it.isTestingModule() && !it.isAppModule() }\n        )\n    }\n\n    fun Project.configureDesktopApp() {\n      composeMultiplatform.extensions.getByType(DesktopExtension::class.java).application.apply {\n        mainClass = app.desktopMainFile\n\n        nativeDistributions.targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)\n        nativeDistributions.packageName = \"software.amazon.app.platform.demo\"\n\n        // During development the major version is 0, e.g. '0.0.1'. DMG must use a\n        // major version equal or greater than 1:\n        //\n        // Illegal version for 'Dmg': '0.0.1' is not a valid build version.\n        val version = VersionNumber.parse(versionName)\n        nativeDistributions.packageVersion =\n          VersionNumber(max(1, version.major), version.minor, version.patch, null).toString()\n      }\n    }\n  }\n\n  internal enum class App(val rootProjectPath: String) {\n    RECIPES(\":recipes\"),\n    SAMPLE(\":sample\");\n\n    val iosFrameworkName: String = rootProjectPath.substring(1).capitalize() + \"App\"\n    val jsFileName: String = rootProjectPath.substring(1) + \"-app.js\"\n    val desktopMainFile: String =\n      \"software.amazon.app.platform.${rootProjectPath.substring(1)}.MainKt\"\n\n    companion object {\n      val Project.app: App\n        get() {\n          check(isAppModule())\n          return when (path) {\n            \":recipes:app\" -> RECIPES\n            \":sample:app\" -> SAMPLE\n            else -> throw NotImplementedError()\n          }\n        }\n    }\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/BaseAndroidPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport com.android.build.api.dsl.ApplicationExtension\nimport com.android.build.api.dsl.LibraryExtension\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\npublic open class BaseAndroidPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.configureAndroid()\n  }\n\n  private fun Project.configureAndroid() {\n    val android = android\n\n    android.compileSdk = libs.findVersion(\"android.compileSdk\").get().requiredVersion.toInt()\n    android.defaultConfig.minSdk = libs.findVersion(\"android.minSdk\").get().requiredVersion.toInt()\n\n    when (android) {\n      is LibraryExtension -> {\n        android.lint.targetSdk = libs.findVersion(\"android.targetSdk\").get().requiredVersion.toInt()\n        android.testOptions.targetSdk =\n          libs.findVersion(\"android.targetSdk\").get().requiredVersion.toInt()\n        android.defaultConfig.multiDexEnabled = true\n      }\n\n      is ApplicationExtension -> {\n        android.defaultConfig {\n          targetSdk = libs.findVersion(\"android.targetSdk\").get().requiredVersion.toInt()\n          multiDexEnabled = true\n\n          applicationId = \"software.amazon.app.platform.demo\"\n          versionCode = 1\n          versionName = this@configureAndroid.versionName\n        }\n      }\n    }\n\n    android.packaging.resources.excludes += \"/META-INF/{AL2.0,LGPL2.1}\"\n    android.buildTypes.getByName(\"release\").isMinifyEnabled = false\n\n    android.compileOptions.sourceCompatibility = javaVersion\n    android.compileOptions.targetCompatibility = javaVersion\n\n    android.testOptions.unitTests {\n      // Disable including Android resources in tests. None of our modules need them and it avoids\n      // running into issues with Gradle 9: https://issuetracker.google.com/issues/411739086\n      isIncludeAndroidResources = false\n\n      isReturnDefaultValues = true\n    }\n\n    android.lint {\n      warningsAsErrors = true\n      htmlReport = true\n      disable +=\n        setOf(\n          \"GradleDependency\",\n          \"ObsoleteLintCustomCheck\",\n          \"NewerVersionAvailable\",\n          \"AndroidGradlePluginVersion\",\n          \"OldTargetApi\",\n        )\n    }\n\n    releaseTask.configure { it.dependsOn(\"lintDebug\") }\n  }\n\n  internal companion object {\n    internal fun Project.enableInstrumentedTests() {\n      releaseTask.configure {\n        it.dependsOn(\"assembleDebugAndroidTest\")\n        it.dependsOn(\"emulatorCheck\")\n      }\n\n      android.defaultConfig {\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        testInstrumentationRunnerArguments += \"clearPackageData\" to \"true\"\n      }\n\n      android.testOptions.execution = \"ANDROIDX_TEST_ORCHESTRATOR\"\n\n      dependencies.add(\n        \"androidTestUtil\",\n        libs.findLibrary(\"androidx.test.orchestrator\").get().get().toString(),\n      )\n      dependencies.add(\n        \"androidTestImplementation\",\n        libs.findLibrary(\"androidx.test.runner\").get().get().toString(),\n      )\n      dependencies.add(\n        \"androidTestImplementation\",\n        libs.findLibrary(\"androidx.test.rules\").get().get().toString(),\n      )\n      dependencies.add(\n        \"androidTestImplementation\",\n        libs.findLibrary(\"androidx.test.junit\").get().get().toString(),\n      )\n      dependencies.add(\n        \"androidTestImplementation\",\n        libs.findLibrary(\"kotlin.test\").get().get().toString(),\n      )\n      dependencies.add(\n        \"androidTestImplementation\",\n        libs.findLibrary(\"assertk\").get().get().toString(),\n      )\n\n      @Suppress(\"UnstableApiUsage\")\n      android.testOptions.managedDevices.localDevices.create(\"emulator\") {\n        // Use device profiles you typically see in Android Studio.\n        it.device = \"Pixel 3\"\n        it.apiLevel = 30\n        it.require64Bit = true\n        it.systemImageSource = \"aosp-atd\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/BasePlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport buildSrc.BuildConfig.APP_PLATFORM_GROUP\nimport com.android.build.gradle.internal.tasks.factory.dependsOn\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.tasks.testing.Test\nimport org.jetbrains.kotlin.gradle.targets.web.yarn.BaseYarnRootExtension\nimport org.jetbrains.kotlin.gradle.targets.web.yarn.CommonYarnPlugin\nimport software.amazon.app.platform.gradle.buildsrc.AppPlatformExtension.Companion.appPlatformGradlePlugin\n\npublic open class BasePlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.createReleaseTask()\n    target.configureDependencySubstitution()\n\n    // We're dogfooding our published Gradle plugin in the :app module. The extension names\n    // are conflicting, therefore use another name than \"appPlatform\".\n    target.extensions.create(\"appPlatformBuildSrc\", AppPlatformExtension::class.java)\n\n    target.addAppPlatformGradlePlugin()\n    target.runTestsInHeadlessMode()\n    target.configureLogOutput()\n    target.upgradeYarnDependencies()\n  }\n\n  private fun Project.createReleaseTask() {\n    tasks.register(\"release\")\n  }\n\n  private fun Project.runTestsInHeadlessMode() {\n    // Otherwise the java icon keeps popping up in the system tray while running tests.\n    tasks.withType(Test::class.java).configureEach {\n      it.systemProperty(\"java.awt.headless\", \"true\")\n    }\n  }\n\n  private fun Project.configureLogOutput() {\n    if (ci) {\n      tasks.withType(Test::class.java).configureEach { testTask ->\n        testTask.testLogging {\n          it.showExceptions = true\n          it.showCauses = true\n          it.showStackTraces = true\n          it.showStandardStreams = true\n        }\n      }\n    }\n  }\n\n  private fun Project.configureDependencySubstitution() {\n    // In some modules we apply the App Platform Gradle plugin, which adds dependencies to\n    // these pre-built binaries. Here we tell Gradle to replace the pre-built binaries with\n    // the Gradle modules and build the code on the fly. See settings.gradle for more details\n    // as well.\n    val substitutions =\n      mapOf(\n        \"${APP_PLATFORM_GROUP}:di-common-public\" to \":di-common:public\",\n        \"${APP_PLATFORM_GROUP}:kotlin-inject-public\" to \":kotlin-inject:public\",\n        \"${APP_PLATFORM_GROUP}:kotlin-inject-contribute-impl-code-generators\" to\n          \":kotlin-inject-extensions:contribute:impl-code-generators\",\n        \"${APP_PLATFORM_GROUP}:kotlin-inject-contribute-public\" to\n          \":kotlin-inject-extensions:contribute:public\",\n        \"${APP_PLATFORM_GROUP}:kotlin-inject-impl\" to \":kotlin-inject:impl\",\n        \"${APP_PLATFORM_GROUP}:ksp-common-public\" to \":ksp-common:public\",\n        \"${APP_PLATFORM_GROUP}:metro-public\" to \":metro:public\",\n        \"${APP_PLATFORM_GROUP}:metro-impl\" to \":metro:impl\",\n        \"${APP_PLATFORM_GROUP}:metro-contribute-impl-compiler-plugin\" to\n          \":metro-extensions:contribute:impl-compiler-plugin\",\n        \"${APP_PLATFORM_GROUP}:metro-contribute-impl-code-generators\" to\n          \":metro-extensions:contribute:impl-code-generators\",\n        \"${APP_PLATFORM_GROUP}:presenter-public\" to \":presenter:public\",\n        \"${APP_PLATFORM_GROUP}:presenter-molecule-public\" to \":presenter-molecule:public\",\n        \"${APP_PLATFORM_GROUP}:presenter-molecule-impl\" to \":presenter-molecule:impl\",\n        \"${APP_PLATFORM_GROUP}:presenter-molecule-testing\" to \":presenter-molecule:testing\",\n        \"${APP_PLATFORM_GROUP}:renderer-public\" to \":renderer:public\",\n        \"${APP_PLATFORM_GROUP}:renderer-android-view-public\" to \":renderer-android-view:public\",\n        \"${APP_PLATFORM_GROUP}:renderer-compose-multiplatform-public\" to\n          \":renderer-compose-multiplatform:public\",\n        \"${APP_PLATFORM_GROUP}:robot-public\" to \":robot:public\",\n        \"${APP_PLATFORM_GROUP}:robot-compose-multiplatform-public\" to\n          \":robot-compose-multiplatform:public\",\n        \"${APP_PLATFORM_GROUP}:robot-internal-public\" to \":robot-internal:public\",\n        \"${APP_PLATFORM_GROUP}:scope-public\" to \":scope:public\",\n        \"${APP_PLATFORM_GROUP}:scope-testing\" to \":scope:testing\",\n      )\n\n    plugins.withId(Plugins.MAVEN_PUBLISH) {\n      check(path in substitutions.values) {\n        \"Forgot to setup dependency substitution for $path. Add a mapping in the \" +\n          \"substitution collection.\"\n      }\n    }\n\n    configurations.configureEach { configuration ->\n      configuration.resolutionStrategy.dependencySubstitution { substitution ->\n        substitutions.forEach { (module, project) ->\n          substitution.substitute(substitution.module(module)).using(substitution.project(project))\n        }\n      }\n    }\n  }\n\n  private fun Project.addAppPlatformGradlePlugin() {\n    if (!isRoot) {\n      plugins.apply(Plugins.APP_PLATFORM)\n\n      plugins.withIds(Plugins.KOTLIN_MULTIPLATFORM, Plugins.KOTLIN_JVM) {\n        appPlatformGradlePlugin.enableModuleStructure(true)\n\n        releaseTask.dependsOn(\"checkModuleStructureDependencies\")\n      }\n    }\n  }\n\n  private fun Project.upgradeYarnDependencies() {\n    plugins.withType(CommonYarnPlugin::class.java).configureEach {\n      with(extensions.getByType(BaseYarnRootExtension::class.java)) {\n        // Force the newer version due to https://github.com/amzn/app-platform/security/dependabot/5\n        resolution(\"webpack-dev-server\", \"5.2.1\")\n        // Force the newer version due to https://github.com/amzn/app-platform/security/dependabot/8\n        resolution(\"on-headers\", \"1.1.0\")\n        // Force the newer version due to\n        // https://github.com/amzn/app-platform/security/dependabot/10\n        resolution(\"tmp\", \"0.2.4\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/Gradle.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport com.android.build.api.dsl.CommonExtension\nimport com.android.build.api.variant.AndroidComponentsExtension\nimport java.util.Locale\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.artifacts.VersionCatalog\nimport org.gradle.api.artifacts.VersionCatalogsExtension\nimport org.gradle.api.plugins.PluginContainer\nimport org.gradle.api.tasks.TaskProvider\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport software.amazon.app.platform.gradle.moduleType\n\ninternal val Project.libs: VersionCatalog\n  get() = extensions.getByType(VersionCatalogsExtension::class.java).named(\"libs\")\n\ninternal val Project.ci\n  get() = providers.gradleProperty(\"CI\").isPresent || System.getenv(\"CI\") != null\n\ninternal val Project.javaVersion\n  get() = JavaVersion.toVersion(libs.findVersion(\"jvm.compatibility\").get().requiredVersion)\ninternal val Project.javaTarget\n  get() = JvmTarget.fromTarget(javaVersion.toString())\n\ninternal val Project.safePathString: String\n  get() = path.replace(':', '-').substring(1)\n\ninternal val Project.isKmpModule: Boolean\n  get() = plugins.hasPlugin(Plugins.KOTLIN_MULTIPLATFORM)\ninternal val Project.isRoot: Boolean\n  get() = path == \":\"\n\ninternal val Project.android: CommonExtension<*, *, *, *, *, *>\n  get() = extensions.getByType(CommonExtension::class.java)\n\ninternal val Project.androidComponents: AndroidComponentsExtension<*, *, *>\n  get() = extensions.getByType(AndroidComponentsExtension::class.java)\n\ninternal val Project.releaseTask: TaskProvider<Task>\n  get() = tasks.named(\"release\")\n\ninternal val Project.versionName: String\n  get() = requireNotNull(property(\"VERSION_NAME\")).toString()\n\ninternal fun Project.useTestDependenciesInMain(): Boolean {\n  return moduleType.useTestDependenciesInMain || path.startsWith(\":robot\")\n}\n\ninternal fun PluginContainer.withIds(vararg pluginIds: String, action: (Plugin<*>) -> Unit) {\n  pluginIds.forEach { id -> withId(id) { action(it) } }\n}\n\ninternal fun String.capitalize(): String = replaceFirstChar {\n  if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString()\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/JvmLibraryPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.plugins.JavaPluginExtension\nimport org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension\nimport software.amazon.app.platform.gradle.buildsrc.AppPlatformExtension.Companion.appPlatformBuildSrc\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.configureKtfmt\n\npublic open class JvmLibraryPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.plugins.apply(BasePlugin::class.java)\n    target.plugins.apply(Plugins.KOTLIN_JVM)\n\n    target.configureKotlin()\n    target.configureTests()\n    target.configureCoroutines()\n    target.configureKtfmt()\n  }\n\n  private fun Project.configureKotlin() {\n    dependencies.add(\n      \"api\",\n      dependencies.platform(libs.findLibrary(\"kotlin.bom\").get().get().toString()),\n    )\n\n    extensions.getByType(KotlinJvmProjectExtension::class.java).compilerOptions {\n      allWarningsAsErrors.set(appPlatformBuildSrc.isKotlinWarningsAsErrors())\n\n      jvmTarget.set(javaTarget)\n    }\n\n    with(extensions.getByType(JavaPluginExtension::class.java)) {\n      sourceCompatibility = javaVersion\n      targetCompatibility = javaVersion\n    }\n  }\n\n  private fun Project.configureTests() {\n    releaseTask.configure { task -> task.dependsOn(\"test\") }\n\n    dependencies.add(\"testImplementation\", libs.findLibrary(\"kotlin.test\").get().get().toString())\n    dependencies.add(\"testImplementation\", libs.findLibrary(\"assertk\").get().get().toString())\n  }\n\n  private fun Project.configureCoroutines() {\n    dependencies.add(\"implementation\", libs.findLibrary(\"coroutines.core\").get().get().toString())\n    dependencies.add(\n      \"testImplementation\",\n      libs.findLibrary(\"coroutines.test\").get().get().toString(),\n    )\n    dependencies.add(\"testImplementation\", libs.findLibrary(\"turbine\").get().get().toString())\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/KmpPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport com.google.devtools.ksp.gradle.KspExtension\nimport com.ncorti.ktfmt.gradle.KtfmtExtension\nimport com.ncorti.ktfmt.gradle.TrailingCommaManagementStrategy\nimport guru.nidi.graphviz.engine.Format\nimport io.github.terrakok.KmpHierarchyConfig\nimport io.gitlab.arturbosch.detekt.Detekt\nimport io.gitlab.arturbosch.detekt.DetektCreateBaselineTask\nimport io.gitlab.arturbosch.detekt.extensions.DetektExtension\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.dsl.DependencyHandler\nimport org.gradle.api.plugins.ExtensionAware\nimport org.gradle.api.tasks.SourceTask\nimport org.jetbrains.compose.ComposeExtension\nimport org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension\nimport org.jetbrains.kotlin.gradle.plugin.NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME\nimport org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME\nimport software.amazon.app.platform.gradle.buildsrc.AppPlatformExtension.Companion.appPlatformBuildSrc\nimport software.amazon.app.platform.gradle.buildsrc.Platform.Companion.allPlatforms\n\npublic open class KmpPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.plugins.apply(Plugins.KOTLIN_MULTIPLATFORM)\n\n    target.configureCommonKotlin()\n    target.configureCoroutines()\n    target.configureKtfmt()\n    target.configureTests()\n    target.configureDetekt()\n\n    target.addExtraSourceSets()\n    target.configureHierarchyPlugin()\n  }\n\n  private fun Project.configureCommonKotlin() {\n    kmpExtension.applyDefaultHierarchyTemplate()\n\n    dependencies.add(\n      \"commonMainApi\",\n      dependencies.platform(libs.findLibrary(\"kotlin.bom\").get().get().toString()),\n    )\n\n    // Only for tests.\n    kmpExtension.sourceSets\n      .getByName(\"commonTest\")\n      .languageSettings\n      .optIn(\"kotlinx.coroutines.ExperimentalCoroutinesApi\")\n\n    kmpExtension.compilerOptions {\n      freeCompilerArgs.add(\"-Xannotation-default-target=param-property\")\n\n      // Unfortunately, we cannot set this to true. It produces warnings for generated code,\n      // which cannot be excluded.\n      extraWarnings.set(false)\n\n      allWarningsAsErrors.set(appPlatformBuildSrc.isKotlinWarningsAsErrors())\n    }\n\n    kmpExtension.targets.configureEach { target ->\n      target.compilations.configureEach { compilation ->\n        compilation.compileTaskProvider.configure { task ->\n          with(task.compilerOptions) {\n            if (\"test\" in task.name.lowercase() || path == \":internal:testing\") {\n              freeCompilerArgs.add(\"-Xexpect-actual-classes\")\n              freeCompilerArgs.add(\"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi\")\n            }\n\n            // We need to rename the KLib library for iOS to avoid duplicate names. By\n            // default project.name is used, which conflicts with our module structure\n            // where many modules are named \"public\" or \"impl\". If that happens during\n            // compilation only code from one module is found.\n            //\n            // There is currently no DSL to set the KLib name. For more details see\n            // https://youtrack.jetbrains.com/issue/KT-38719\n            // https://youtrack.jetbrains.com/issue/KT-38892\n            if (target.targetName != \"js\" && target.targetName != \"wasmJs\") {\n              // Note this doesn't work on JS/WASMJS yet due to\n              // https://youtrack.jetbrains.com/issue/KT-71362\n              freeCompilerArgs.add(\"-module-name\")\n              freeCompilerArgs.add(\"$safePathString.${compilation.compilationName}\")\n            }\n          }\n        }\n      }\n    }\n\n    allPlatforms().forEach { platform -> platform.configurePlatform() }\n  }\n\n  private fun Project.configureCoroutines() {\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(libs.findLibrary(\"coroutines.core\").get().get().toString())\n    }\n\n    testingSourceSets.forEach { sourceSetName ->\n      kmpExtension.sourceSets.getByName(sourceSetName).dependencies {\n        // Use api for main source sets (testing utility modules) so downstream modules\n        // get transitive access. Use implementation for test source sets since api is\n        // deprecated there in Kotlin 2.3.\n        val isTestSourceSet = sourceSetName.contains(\"Test\", ignoreCase = true)\n        if (isTestSourceSet) {\n          implementation(libs.findLibrary(\"coroutines.test\").get().get().toString())\n          implementation(libs.findLibrary(\"turbine\").get().get().toString())\n        } else {\n          api(libs.findLibrary(\"coroutines.test\").get().get().toString())\n          api(libs.findLibrary(\"turbine\").get().get().toString())\n        }\n      }\n    }\n\n    allPlatforms().forEach { platform -> platform.configureCoroutines() }\n  }\n\n  private fun Project.configureTests() {\n    testingSourceSets.forEach { sourceSetName ->\n      kmpExtension.sourceSets.getByName(sourceSetName).dependencies {\n        val isTestSourceSet = sourceSetName.contains(\"Test\", ignoreCase = true)\n        if (isTestSourceSet) {\n          implementation(kotlin(\"test\"))\n          implementation(libs.findLibrary(\"assertk\").get().get().toString())\n        } else {\n          api(kotlin(\"test\"))\n          api(libs.findLibrary(\"assertk\").get().get().toString())\n        }\n      }\n    }\n\n    releaseTask.configure { task ->\n      task.dependsOn(allPlatforms().mapNotNull { it.unitTestTaskName })\n    }\n  }\n\n  private fun Project.addExtraSourceSets() {\n    val platforms = allPlatforms()\n    if (platforms.any { it is Platform.Ios } && platforms.any { it is Platform.DesktopPlatform }) {\n      setOf(\"Main\", \"Test\").forEach { suffix ->\n        val common = kmpExtension.sourceSets.getByName(\"common$suffix\")\n\n        val appleAndDesktop = kmpExtension.sourceSets.create(\"appleAndDesktop$suffix\")\n        appleAndDesktop.dependsOn(common)\n\n        kmpExtension.sourceSets.named(\"apple$suffix\").configure { it.dependsOn(appleAndDesktop) }\n        kmpExtension.sourceSets.named(\"desktop$suffix\").configure { it.dependsOn(appleAndDesktop) }\n\n        val noWasmJs = kmpExtension.sourceSets.create(\"noWasmJs$suffix\")\n        noWasmJs.dependsOn(common)\n\n        appleAndDesktop.dependsOn(noWasmJs)\n        kmpExtension.sourceSets.named(\"native$suffix\").configure { it.dependsOn(noWasmJs) }\n        if (suffix == \"Main\") {\n          kmpExtension.sourceSets.named(\"android$suffix\").configure { it.dependsOn(noWasmJs) }\n        } else {\n          kmpExtension.sourceSets.named(\"androidUnit$suffix\").configure { it.dependsOn(noWasmJs) }\n        }\n      }\n    }\n  }\n\n  private fun Project.configureHierarchyPlugin() {\n    plugins.apply(Plugins.KOTLIN_HIERARCHY)\n\n    (extensions.getByType(KotlinMultiplatformExtension::class.java) as ExtensionAware)\n      .extensions\n      .getByType(KmpHierarchyConfig::class.java)\n      .run {\n        formats(Format.PNG, Format.SVG)\n        withTestHierarchy = true\n      }\n  }\n\n  internal companion object {\n    val Project.kmpExtension: KotlinMultiplatformExtension\n      get() = extensions.getByType(KotlinMultiplatformExtension::class.java)\n\n    val Project.composeMultiplatform: ComposeExtension\n      get() = extensions.getByType(ComposeExtension::class.java)\n\n    fun Project.enableCompose() {\n      plugins.apply(Plugins.COMPOSE_COMPILER)\n      plugins.apply(Plugins.COMPOSE_MULTIPLATFORM)\n\n      val composeVersion = libs.findVersion(\"compose.multiplatform\").get().requiredVersion\n\n      kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n        implementation(\"org.jetbrains.compose.runtime:runtime:$composeVersion\")\n        implementation(\"org.jetbrains.compose.foundation:foundation:$composeVersion\")\n      }\n\n      allPlatforms().forEach { platform -> platform.configureCompose() }\n    }\n\n    fun Project.enableKotlinInject() {\n      enableKsp()\n\n      val kspExtension = extensions.getByType(KspExtension::class.java)\n\n      // Disable this processor, because we implement our own version in order to support the\n      // Scoped interface.\n      kspExtension.arg(\n        \"software.amazon.lastmile.kotlin.inject.anvil.processor.\" + \"ContributesBindingProcessor\",\n        \"disabled\",\n      )\n\n      if (isKmpModule) {\n        kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n          implementation(libs.findLibrary(\"kotlin.inject.runtime\").get().get().toString())\n          implementation(libs.findLibrary(\"kotlin.inject.anvil.runtime\").get().get().toString())\n          implementation(\n            libs.findLibrary(\"kotlin.inject.anvil.runtime.optional\").get().get().toString()\n          )\n\n          if (path != \":di-common:public\" && path != \":kotlin-inject:public\") {\n            implementation(project(\":di-common:public\"))\n            implementation(project(\":kotlin-inject:public\"))\n            if (!path.startsWith(\":kotlin-inject-extensions:contribute:\")) {\n              implementation(project(\":kotlin-inject-extensions:contribute:public\"))\n            }\n          }\n        }\n      } else {\n        dependencies.add(\n          \"implementation\",\n          libs.findLibrary(\"kotlin.inject.runtime\").get().get().toString(),\n        )\n        dependencies.add(\n          \"implementation\",\n          libs.findLibrary(\"kotlin.inject.anvil.runtime\").get().get().toString(),\n        )\n        dependencies.add(\n          \"implementation\",\n          libs.findLibrary(\"kotlin.inject.anvil.runtime.optional\").get().get().toString(),\n        )\n        if (path != \":di-common:public\" && path != \":kotlin-inject:public\") {\n          dependencies.add(\"implementation\", project(\":di-common:public\"))\n          dependencies.add(\"implementation\", project(\":kotlin-inject:public\"))\n          if (!path.startsWith(\":kotlin-inject-extensions:contribute:\")) {\n            dependencies.add(\n              \"implementation\",\n              project(\":kotlin-inject-extensions:contribute:public\"),\n            )\n          }\n        }\n      }\n\n      fun DependencyHandler.addKspProcessorDependencies(kspConfigurationName: String) {\n        add(kspConfigurationName, libs.findLibrary(\"kotlin.inject.ksp\").get().get().toString())\n        add(\n          kspConfigurationName,\n          libs.findLibrary(\"kotlin.inject.anvil.compiler\").get().get().toString(),\n        )\n\n        // Avoid creating a circular dependency.\n        if (\n          path != \":di-common:public\" &&\n            path != \":kotlin-inject:public\" &&\n            !path.startsWith(\":kotlin-inject-extensions:contribute:\")\n        ) {\n          add(kspConfigurationName, project(\":kotlin-inject-extensions:contribute:public\"))\n          add(\n            kspConfigurationName,\n            project(\":kotlin-inject-extensions:contribute:impl-code-generators\"),\n          )\n        }\n      }\n\n      if (isKmpModule) {\n        kmpExtension.targets.configureEach {\n          if (it.name != \"metadata\") {\n            dependencies.addKspProcessorDependencies(\"ksp${it.name.capitalize()}\")\n            dependencies.addKspProcessorDependencies(\"ksp${it.name.capitalize()}Test\")\n          }\n        }\n      } else {\n        dependencies.addKspProcessorDependencies(\"ksp\")\n      }\n    }\n\n    fun Project.enableMetro() {\n      plugins.apply(Plugins.METRO)\n\n      val useMetroKsp =\n        providers\n          .gradleProperty(\"app.platform.metro.ksp\")\n          .map(String::toBoolean)\n          .orElse(false)\n          .get()\n\n      if (useMetroKsp) {\n        enableMetroKsp()\n      } else {\n        enableMetroCompilerPlugin()\n      }\n    }\n\n    private fun Project.enableMetroKsp() {\n      enableKsp()\n\n      if (isKmpModule) {\n        kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n          implementation(project(\":di-common:public\"))\n          implementation(project(\":metro:public\"))\n        }\n      } else {\n        dependencies.add(\"implementation\", project(\":metro:public\"))\n      }\n\n      fun DependencyHandler.addKspProcessorDependencies(kspConfigurationName: String) {\n        add(kspConfigurationName, project(\":metro-extensions:contribute:impl-code-generators\"))\n      }\n\n      if (isKmpModule) {\n        kmpExtension.targets.configureEach {\n          if (it.name != \"metadata\") {\n            dependencies.addKspProcessorDependencies(\"ksp${it.name.capitalize()}\")\n            dependencies.addKspProcessorDependencies(\"ksp${it.name.capitalize()}Test\")\n          }\n        }\n      } else {\n        dependencies.addKspProcessorDependencies(\"ksp\")\n      }\n    }\n\n    private fun Project.enableMetroCompilerPlugin() {\n      if (isKmpModule) {\n        kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n          implementation(project(\":di-common:public\"))\n          implementation(project(\":metro:public\"))\n        }\n      } else {\n        dependencies.add(\"implementation\", project(\":metro:public\"))\n      }\n\n      fun DependencyHandler.addCompilerPluginDependencies() {\n        add(\n          PLUGIN_CLASSPATH_CONFIGURATION_NAME,\n          project(\":metro-extensions:contribute:impl-compiler-plugin\"),\n        )\n        add(\n          NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME,\n          project(\":metro-extensions:contribute:impl-compiler-plugin\"),\n        )\n      }\n\n      plugins.withId(Plugins.KOTLIN_MULTIPLATFORM) { dependencies.addCompilerPluginDependencies() }\n      plugins.withId(Plugins.KOTLIN_JVM) { dependencies.addCompilerPluginDependencies() }\n    }\n\n    private fun Project.enableKsp() {\n      plugins.apply(Plugins.KSP)\n    }\n\n    fun Project.enableMolecule() {\n      plugins.apply(Plugins.COMPOSE_COMPILER)\n      kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n        implementation(libs.findLibrary(\"molecule.runtime\").get().get().toString())\n      }\n    }\n\n    fun Project.configureKtfmt() {\n      plugins.apply(Plugins.KTFMT)\n\n      extensions.getByType(KtfmtExtension::class.java).apply {\n        googleStyle()\n        trailingCommaManagementStrategy.set(TrailingCommaManagementStrategy.COMPLETE)\n        removeUnusedImports.set(true)\n      }\n\n      releaseTask.configure { releaseTask -> releaseTask.dependsOn(\"ktfmtCheck\") }\n    }\n\n    private fun Project.configureDetekt() {\n      plugins.apply(Plugins.DETEKT)\n\n      fun SourceTask.configureDefaultDetektTask() {\n        // The :detekt task in a multiplatform project doesn't do anything, it has no\n        // sources configured. Instead, the Detekt plugin creates a Gradle task for each\n        // source set, which then need to be called manually. This is annoying and tedious.\n        //\n        // We make the default :detekt task analyze all .kt files, which is faster,\n        // because only a single task runs, and we avoid all the wiring.\n        setSource(layout.files(\"src\"))\n        exclude(\"**/*.kts\")\n        exclude(\"**/api/**\")\n        exclude(\"**/build/**\")\n        exclude(\"**/detekt/**\")\n      }\n\n      // Make Detekt use the right version of Java\n      tasks.withType(Detekt::class.java).configureEach { detekt ->\n        detekt.jvmTarget = javaVersion.toString()\n\n        if (detekt.name == \"detekt\") {\n          detekt.configureDefaultDetektTask()\n        }\n      }\n      tasks.withType(DetektCreateBaselineTask::class.java).configureEach {\n        it.jvmTarget = javaVersion.toString()\n\n        if (it.name == \"detektBaseline\") {\n          it.configureDefaultDetektTask()\n        }\n      }\n      with(extensions.getByType(DetektExtension::class.java)) {\n        // From the Groovy DSL at https://detekt.github.io/detekt/gradle.html#groovy-dsl-3\n        // This produces baselines named \"detekt-baseline.xml\"\n        baseline = file(\"detekt/detekt-baseline.xml\")\n        // Config overrides\n        config.from(rootProject.file(\"gradle/detekt-config.yml\"))\n        buildUponDefaultConfig = true\n      }\n\n      releaseTask.configure { releaseTask -> releaseTask.dependsOn(\"detekt\") }\n    }\n\n    private val Project.testingSourceSets\n      get() = buildList {\n        add(\"commonTest\")\n        if (useTestDependenciesInMain()) {\n          add(\"commonMain\")\n        }\n      }\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/LibraryPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\npublic open class LibraryPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.plugins.apply(BasePlugin::class.java)\n\n    target.plugins.apply(Plugins.ANDROID_LIBRARY)\n    target.plugins.apply(KmpPlugin::class.java)\n    target.plugins.apply(BaseAndroidPlugin::class.java)\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/Platform.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport org.gradle.api.Project\nimport org.gradle.api.plugins.JavaPluginExtension\nimport org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget\nimport software.amazon.app.platform.gradle.buildsrc.AppPlugin.App.Companion.app\nimport software.amazon.app.platform.gradle.buildsrc.AppPlugin.Companion.allExportedDependencies\nimport software.amazon.app.platform.gradle.buildsrc.KmpPlugin.Companion.kmpExtension\nimport software.amazon.app.platform.gradle.isAppModule\n\ninternal sealed interface Platform {\n  val unitTestTaskName: String?\n\n  fun configurePlatform()\n\n  fun configureCoroutines() = Unit\n\n  fun configureCompose() = Unit\n\n  abstract class Native : Platform {\n    protected abstract val project: Project\n  }\n\n  abstract class Ios : Native() {\n\n    abstract val target: KotlinNativeTarget\n\n    override fun configurePlatform() {\n      target.binaries.framework {\n        baseName =\n          if (project.isAppModule()) {\n            project.app.iosFrameworkName\n          } else {\n            project.safePathString.capitalize()\n          }\n        isStatic = project.isAppModule()\n\n        if (project.isAppModule()) {\n          project.allExportedDependencies().forEach { dependency -> export(dependency) }\n        }\n      }\n    }\n  }\n\n  private class AndroidPlatform(private val project: Project) : Platform {\n\n    override val unitTestTaskName: String = \"testDebugUnitTest\"\n\n    override fun configurePlatform() {\n      project.kmpExtension.androidTarget().compilerOptions { jvmTarget.set(project.javaTarget) }\n\n      project.android.sourceSets.getByName(\"main\").apply {\n        project\n          .file(\"src/androidMain/AndroidManifest.xml\")\n          .takeIf { it.exists() }\n          ?.let { manifest.srcFile(it) }\n        project.file(\"src/androidMain/res\").takeIf { it.exists() }?.let { res.srcDirs(it) }\n        project\n          .file(\"src/commonMain/resources\")\n          .takeIf { it.exists() }\n          ?.let { resources.srcDirs(it) }\n      }\n    }\n  }\n\n  class DesktopPlatform(private val project: Project) : Platform {\n\n    override val unitTestTaskName: String = \"desktopTest\"\n\n    override fun configurePlatform() {\n      project.kmpExtension.jvm(\"desktop\").compilerOptions { jvmTarget.set(project.javaTarget) }\n\n      with(project.extensions.getByType(JavaPluginExtension::class.java)) {\n        sourceCompatibility = project.javaVersion\n        targetCompatibility = project.javaVersion\n      }\n    }\n\n    override fun configureCoroutines() {\n      project.kmpExtension.sourceSets.getByName(\"desktopMain\").dependencies {\n        implementation(project.libs.findLibrary(\"coroutines.swing\").get().get().toString())\n      }\n    }\n\n    override fun configureCompose() {\n      val composeVersion = project.libs.findVersion(\"compose.multiplatform\").get().requiredVersion\n\n      project.kmpExtension.sourceSets.getByName(\"desktopMain\").dependencies {\n        implementation(\n          \"org.jetbrains.compose.desktop:desktop-jvm-${currentOsTarget()}:$composeVersion\"\n        )\n      }\n\n      project.kmpExtension.sourceSets.getByName(\"desktopTest\").dependencies {\n        implementation(\"org.jetbrains.compose.ui:ui-test-junit4:$composeVersion\")\n        implementation(\n          \"org.jetbrains.compose.desktop:desktop-jvm-${currentOsTarget()}:$composeVersion\"\n        )\n      }\n    }\n\n    private fun currentOsTarget(): String {\n      val os = System.getProperty(\"os.name\").lowercase()\n      val arch = System.getProperty(\"os.arch\").lowercase()\n      return when {\n        os.contains(\"mac\") || os.contains(\"darwin\") ->\n          if (arch.contains(\"aarch64\") || arch.contains(\"arm\")) \"macos-arm64\" else \"macos-x64\"\n        os.contains(\"win\") ->\n          if (arch.contains(\"aarch64\") || arch.contains(\"arm\")) \"windows-arm64\" else \"windows-x64\"\n        else -> if (arch.contains(\"aarch64\") || arch.contains(\"arm\")) \"linux-arm64\" else \"linux-x64\"\n      }\n    }\n  }\n\n  private abstract class Linux : Platform {\n\n    abstract val project: Project\n    abstract val target: KotlinNativeTarget\n\n    override fun configurePlatform() {\n      target.binaries { sharedLib { baseName = project.safePathString.capitalize() } }\n    }\n  }\n\n  private class LinuxArm64(override val project: Project) : Linux() {\n    // Tests aren't supported, because the KMP Gradle plugin doesn't generate the Gradle tasks.\n    override val unitTestTaskName: String? = null\n\n    override val target: KotlinNativeTarget by lazy { project.kmpExtension.linuxArm64() }\n  }\n\n  private class LinuxX64(override val project: Project) : Linux() {\n    override val unitTestTaskName = \"linuxX64Test\"\n\n    override val target: KotlinNativeTarget by lazy { project.kmpExtension.linuxX64() }\n  }\n\n  private class IosSimulatorArm64(override val project: Project) : Ios() {\n\n    override val unitTestTaskName: String = \"iosSimulatorArm64Test\"\n\n    override val target: KotlinNativeTarget by lazy { project.kmpExtension.iosSimulatorArm64() }\n  }\n\n  private class IosArm64(override val project: Project) : Ios() {\n\n    override val unitTestTaskName: String? = null\n\n    override val target: KotlinNativeTarget by lazy { project.kmpExtension.iosArm64() }\n  }\n\n  private class Wasm(private val project: Project) : Platform {\n    override val unitTestTaskName: String = \"wasmJsTest\"\n\n    override fun configurePlatform() {\n      @Suppress(\"OPT_IN_USAGE\")\n      project.kmpExtension.wasmJs { browser { outputModuleName.set(project.safePathString) } }\n    }\n  }\n\n  companion object {\n\n    private val projectsUsingCompose =\n      setOf(\":renderer-compose-multiplatform:public\", \":robot-compose-multiplatform:public\") +\n        AppPlugin.App.entries.map { it.rootProjectPath }\n\n    fun Project.allPlatforms(): Set<Platform> = buildSet {\n      // Always add Android. It's our most important platform and buildable in all\n      // environments (locally and CI)\n      add(AndroidPlatform(project = this@allPlatforms))\n\n      // Android-only modules have \"android\" in their name and don't need other\n      // platforms.\n      if (\"android\" !in path.lowercase()) {\n        add(DesktopPlatform(project = this@allPlatforms))\n\n        add(IosSimulatorArm64(project = this@allPlatforms))\n        add(IosArm64(project = this@allPlatforms))\n\n        add(Wasm(project = this@allPlatforms))\n\n        // Compose Multiplatform does not support Linux, so exclude these modules.\n        if (projectsUsingCompose.none { path.startsWith(it) }) {\n          add(LinuxArm64(project = this@allPlatforms))\n          add(LinuxX64(project = this@allPlatforms))\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/Plugins.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\ninternal object Plugins {\n  const val ANDROID_APP = \"com.android.application\"\n  const val ANDROID_LIBRARY = \"com.android.library\"\n  const val APP_PLATFORM = \"software.amazon.app.platform\"\n  const val BINARY_COMPAT_VALIDATOR = \"org.jetbrains.kotlinx.binary-compatibility-validator\"\n  const val COMPOSE_COMPILER = \"org.jetbrains.kotlin.plugin.compose\"\n  const val COMPOSE_MULTIPLATFORM = \"org.jetbrains.compose\"\n  const val DETEKT = \"io.gitlab.arturbosch.detekt\"\n  const val KOTLIN_MULTIPLATFORM = \"org.jetbrains.kotlin.multiplatform\"\n  const val KOTLIN_HIERARCHY = \"io.github.terrakok.kmp-hierarchy\"\n  const val KOTLIN_JVM = \"org.jetbrains.kotlin.jvm\"\n  const val KSP = \"com.google.devtools.ksp\"\n  const val KTFMT = \"com.ncorti.ktfmt.gradle\"\n  const val MAVEN_PUBLISH = \"com.vanniktech.maven.publish\"\n  const val METRO = \"dev.zacsweers.metro\"\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/RootPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\npublic open class RootPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.plugins.apply(BasePlugin::class.java)\n  }\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/SdkPlugin.kt",
    "content": "package software.amazon.app.platform.gradle.buildsrc\n\nimport com.vanniktech.maven.publish.MavenPublishBaseExtension\nimport kotlinx.validation.ApiValidationExtension\nimport org.gradle.api.Project\nimport org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension\nimport software.amazon.app.platform.gradle.ModuleStructurePlugin.Companion.artifactId\n\ninternal object SdkPlugin {\n  fun Project.publishSdk() {\n    mavenPublishing()\n    configureBinaryCompatibility()\n    configureExplicitApi()\n  }\n\n  private fun Project.mavenPublishing() {\n    // This plugin will add Gradle tasks to generate a source and javadoc .jar files, to\n    // generate the .pom file and to publish the binaries in the local maven repository and\n    // other repositories when needed.\n    plugins.apply(Plugins.MAVEN_PUBLISH)\n\n    // :presenter:public  -> ${group}:presenter-public:${version}\n    // :presenter:impl    -> ${group}:presenter-impl:${version}\n    // :presenter:testing -> ${group}:presenter-testing:${version}\n    val parent = requireNotNull(parent)\n    val artifactId =\n      when {\n        parent.name == \"contribute\" && parent.parent?.name == \"kotlin-inject-extensions\" -> {\n          // Change the artifact ID, because \"contribute\" alone is a weird name.\n          artifactId(libraryName = \"kotlin-inject-contribute\")\n        }\n        parent.name == \"contribute\" && parent.parent?.name == \"metro-extensions\" -> {\n          // Change the artifact ID, because \"contribute\" alone is a weird name.\n          artifactId(libraryName = \"metro-contribute\")\n        }\n        else -> {\n          artifactId()\n        }\n      }\n    mavenPublish.coordinates(artifactId = artifactId)\n    mavenPublish.pom { pom ->\n      pom.name.set(\n        \"App Platform ${\n        artifactId.split('-')\n          .joinToString(separator = \" \", prefix = \"\", postfix = \"\") { it.capitalize() }\n      }\"\n      )\n    }\n  }\n\n  private fun Project.configureBinaryCompatibility() {\n    // This plugin ensures that binary changes are committed as a human readable text file\n    // in the repository.\n    plugins.apply(Plugins.BINARY_COMPAT_VALIDATOR)\n\n    releaseTask.configure { it.dependsOn(\"apiCheck\") }\n\n    val apiValidation = extensions.getByType(ApiValidationExtension::class.java)\n\n    // Klib doesn't work in CI right now and this creates mismatch between local and CI builds.\n    // Disable the experimental feature for now.\n    @Suppress(\"OPT_IN_USAGE\")\n    apiValidation.klib.enabled = false\n\n    // These packages only contain generated code that is picked up by compiler plugins.\n    // They don't need to be part of the API dumps.\n    apiValidation.ignoredPackages +=\n      setOf(\"app.platform.inject\", \"amazon.lastmile.inject\", \"metro.hints\")\n  }\n\n  private fun Project.configureExplicitApi() {\n    extensions.getByType(KotlinBaseExtension::class.java).explicitApi()\n  }\n\n  private val Project.mavenPublish: MavenPublishBaseExtension\n    get() = extensions.getByType(MavenPublishBaseExtension::class.java)\n}\n"
  },
  {
    "path": "di-common/public/api/android/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/ContributesRenderer : java/lang/annotation/Annotation {\n\tpublic abstract fun includeSealedSubtypes ()Z\n\tpublic abstract fun modelType ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/robot/ContributesRobot : java/lang/annotation/Annotation {\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/presenter/PresenterCoroutineScope : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/DefaultCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/IoCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/MainCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\n"
  },
  {
    "path": "di-common/public/api/desktop/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/ContributesRenderer : java/lang/annotation/Annotation {\n\tpublic abstract fun includeSealedSubtypes ()Z\n\tpublic abstract fun modelType ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/robot/ContributesRobot : java/lang/annotation/Annotation {\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/presenter/PresenterCoroutineScope : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/DefaultCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/IoCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/scope/coroutine/MainCoroutineDispatcher : java/lang/annotation/Annotation {\n}\n\n"
  },
  {
    "path": "di-common/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainImplementation libs.metro.runtime\n}\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/inject/ContributesRenderer.kt",
    "content": "package software.amazon.app.platform.inject\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.reflect.KClass\nimport software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation\n\n/**\n * Used to contribute a renderer to our global registry of renderers that can be looked up by our\n * runtime. E.g. given this renderer:\n * ```\n * @ContributesRenderer\n * class IncrementRenderer : Renderer<IncrementPresenter.Model>()\n * ```\n *\n * This annotation would generated following component interface for kotlin-inject:\n * ```\n * @ContributesTo(RendererScope::class)\n * interface IncrementRendererComponent {\n *     @Provides\n *     @IntoMap\n *     fun provideIncrementRendererIncrementPresenterModel(\n *         renderer: () -> IncrementRenderer,\n *     ): Pair<KClass<out BaseModel>, () -> Renderer<*>> = IncrementPresenter.Model::class to renderer\n *\n *     @Provides\n *     fun provideIncrementRenderer(): IncrementRenderer = IncrementRenderer()\n * }\n * ```\n *\n * Or following graph for Metro:\n * ```\n * @ContributesTo(RendererScope::class)\n * interface IncrementRendererGraph {\n *     @Provides\n *     @IntoMap\n *     @RendererKey(IncrementPresenter.Model::class)\n *     fun provideIncrementRendererIncrementPresenterModel(\n *         renderer: Provider<IncrementRenderer>,\n *     ): Renderer<*> = renderer()\n *\n *     @Provides\n *     fun provideIncrementRenderer(): IncrementRenderer = IncrementRenderer()\n * }\n * ```\n *\n * Although strongly discouraged, your renderer is allowed to have an `@Inject constructor`. The\n * only valid use case is for injecting other renderers returned by the `RendererFactory`.\n *\n * ```\n * @Inject\n * @ContributesRenderer\n * class IncrementRenderer(\n *     private val rendererFactory: RendererFactory\n * ) : Renderer<IncrementPresenter.Model>() {\n * ```\n *\n * If the model type is a sealed hierarchy, then for each explicit type a binding method will be\n * generated.\n */\n@Target(CLASS)\n@ContributingAnnotation\npublic annotation class ContributesRenderer(\n  /**\n   * The class reference to the model class. Usually, it doesn't need to be specified and can be\n   * implied by the super type of the renderer.\n   */\n  val modelType: KClass<*> = Unit::class,\n\n  /**\n   * If the `Model` class is a sealed hierarchy and this value is `true` (the default), then this\n   * renderer will be responsible for rendering all other sealed subtypes as well.\n   */\n  val includeSealedSubtypes: Boolean = true,\n)\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/inject/robot/ContributesRobot.kt",
    "content": "package software.amazon.app.platform.inject.robot\n\nimport kotlin.reflect.KClass\nimport software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation\n\n/**\n * Robots must be annotated with `@ContributesRobot`. The annotation will generate the necessary\n * code to provide the robot in the dependency graph and allow us to retrieve the robot through the\n * `robot<AbcRobot> { }` function.\n *\n * ```\n * @ContributesRobot(AppScope::class)\n * class AbcRobot : Robot {\n *     ...\n * }\n * ```\n *\n * It's supported to inject dependencies in the constructor. For this the class must be annotated\n * with `@Inject`:\n * ```\n * @Inject\n * @ContributesRobot(AppScope::class)\n * class AbcRobot(\n *     val someDependency: Dependency,\n * ) : Robot() {\n *     ...\n * }\n * ```\n *\n * **ATTENTION:** Only `AppScope` is supported for now.\n */\n@ContributingAnnotation\npublic annotation class ContributesRobot(\n  /** The scope in which to include this contributed binding. */\n  val scope: KClass<*>\n)\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/PresenterCoroutineScope.kt",
    "content": "package software.amazon.app.platform.presenter\n\nimport dev.zacsweers.metro.Qualifier as MetroQualifier\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier as KiQualifier\nimport software.amazon.app.platform.scope.coroutine.MainCoroutineDispatcher\n\n/**\n * A qualifier to identify the coroutine scope used to run presenters. This scope is commonly\n * injected when converting a `Flow` to a `StateFlow`, see `stateInPresenter` for more details.\n *\n * This scope uses the [MainCoroutineDispatcher] by default, because presenters produce state for\n * the UI and computing their models should have the highest priority.\n *\n * Never cancel this scope yourself, otherwise the application comes to a halt.\n */\n@KiQualifier\n@MetroQualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class PresenterCoroutineScope\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/DefaultCoroutineDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport dev.zacsweers.metro.Qualifier as MetroQualifier\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier as KiQualifier\n\n/** Qualifier for the default dispatcher in the app scope. */\n@KiQualifier\n@MetroQualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class DefaultCoroutineDispatcher\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/IoCoroutineDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport dev.zacsweers.metro.Qualifier as MetroQualifier\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier as KiQualifier\n\n/** Qualifier for the IO dispatcher in the app scope. */\n@KiQualifier\n@MetroQualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class IoCoroutineDispatcher\n"
  },
  {
    "path": "di-common/public/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/MainCoroutineDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport dev.zacsweers.metro.Qualifier as MetroQualifier\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier as KiQualifier\n\n/** Qualifier for the main dispatcher in the app scope. */\n@KiQualifier\n@MetroQualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class MainCoroutineDispatcher\n"
  },
  {
    "path": "docs/di.md",
    "content": "# DI Framework\n\n!!! note\n\n    App Platform provides support for [Metro](https://zacsweers.github.io/metro) and\n    [kotlin-inject-anvil](https://github.com/amzn/kotlin-inject-anvil) as dependency injection\n    frameworks. Metro is the recommended default, while `kotlin-inject-anvil` remains available as\n    the alternative and for existing codebases. Both frameworks are compile-time injection\n    frameworks and ready for Kotlin Multiplatform. They verify correctness of the object graph at\n    build time and avoid crashes at runtime.\n\n    Enabling dependency injection is an opt-in feature through the Gradle DSL. The default value is `false`.\n    ```groovy\n    appPlatform {\n      enableMetro true\n      enableKotlinInject true\n    }\n    ```\n\n!!! tip\n\n    Start with the [Metro documentation](https://zacsweers.github.io/metro). Reach for the\n    [kotlin-inject-anvil documentation](https://github.com/amzn/kotlin-inject-anvil) when you are\n    maintaining the alternative path or migrating older code. App Platform makes heavy use of\n    `@ContributesBinding` and `@ContributesTo` annotations to decompose and assemble components /\n    object graphs.\n\n## Metro\n\n!!! note\n\n    Metro is an opt-in feature through the Gradle DSL. The default value is `false`.\n    ```groovy\n    appPlatform {\n      enableMetro true\n    }\n    ```\n\n### Dependency graph\n\nDependency graphs are added as a service to the `Scope` class and can be obtained using the `metroDependencyGraph()`\nextension function:\n\n```kotlin\nscope.metroDependencyGraph<AppGraph>()\n```\n\nIn modularized projects, final graphs are defined in the `:app` modules, because the object graph has to\nknow about all features of the app. It is strongly recommended to create an object graph in each platform specific\nfolder to provide platform specific types.\n\n=== \"Android\"\n\n    ```kotlin title=\"androidMain\"\n    @DependencyGraph(AppScope::class)\n    interface AndroidAppGraph {\n      @DependencyGraph.Factory\n      fun interface Factory {\n        fun create(\n          @Provides application: Application,\n          @Provides rootScopeProvider: RootScopeProvider,\n        ): AndroidAppGraph\n      }\n    }\n    ```\n\n=== \"iOS\"\n\n    ```kotlin title=\"iosMain\"\n    @DependencyGraph(AppScope::class)\n    interface IosAppGraph {\n      @DependencyGraph.Factory\n      fun interface Factory {\n        fun create(\n          @Provides uiApplication: UIApplication,\n          @Provides rootScopeProvider: RootScopeProvider,\n        ): IosAppGraph\n      }\n    }\n    ```\n\n=== \"Desktop\"\n\n    ```kotlin title=\"desktopMain\"\n    @DependencyGraph(AppScope::class)\n    interface DesktopAppGraph {\n      @DependencyGraph.Factory\n      fun interface Factory {\n        fun create(@Provides rootScopeProvider: RootScopeProvider): DesktopAppGraph\n      }\n    }\n    ```\n\n=== \"WasmJs\"\n\n    ```kotlin title=\"wasmJsMain\"\n    @DependencyGraph(AppScope::class)\n    interface WasmJsAppGraph {\n      @DependencyGraph.Factory\n      fun interface Factory {\n        fun create(@Provides rootScopeProvider: RootScopeProvider): WasmJsAppGraph\n      }\n    }\n    ```\n\n### Platform implementations\n\nMetro makes it simple to provide platform specific implementations for abstract APIs without needing\nto use `expect / actual` declarations or any specific wiring. Since the final object graphs live in the platform\nspecific source folders, all contributions for a platform are automatically picked up. Platform specific\nimplementations can use and inject types from the platform.\n\n```kotlin title=\"commonMain\"\ninterface LocationProvider\n```\n\n=== \"Android\"\n\n    ```kotlin title=\"androidMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class AndroidLocationProvider(\n      val application: Application,\n    ) : LocationProvider\n    ```\n\n=== \"iOS\"\n\n    ```kotlin title=\"iosMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class IosLocationProvider(\n      val uiApplication: UIApplication,\n    ) : LocationProvider\n    ```\n\n=== \"Desktop\"\n\n    ```kotlin title=\"desktopMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class DesktopLocationProvider(\n      ...\n    ) : LocationProvider\n    ```\n\n=== \"WasmJs\"\n\n    ```kotlin title=\"wasmJsMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class WasmLocationProvider(\n      ...\n    ) : LocationProvider\n    ```\n\nOther common code within `commonMain` can safely inject and use `LocationProvider`.\n\n### Injecting dependencies\n\nIt's recommended to rely on constructor injection as much as possible, because it removes boilerplate and makes\ntesting easier. But it some cases it's required to get a dependency from an object graph where constructor injection\nis not possible, e.g. in a static context or types created by the platform. In this case a contributed object graph\ninterface with access to the `Scope` help:\n\n```kotlin title=\"androidMain\"\nclass MainActivityViewModel(application: Application) : AndroidViewModel(application) {\n\n  private val graph = (application as RootScopeProvider).rootScope.metroDependencyGraph<Graph>()\n  private val templateProvider = graph.templateProviderFactory.createTemplateProvider()\n\n  @ContributesTo(AppScope::class)\n  interface Graph {\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n```\n\nThis sample shows an Android `ViewModel` that doesn't use constructor injection. Instead, the `Scope` is retrieved\nfrom the `Application` class and the Metro object graph is found through the `metroDependencyGraph()` function.\n\n??? example \"Sample\"\n\n    The `ViewModel` example comes from the [sample app](https://github.com/amzn/app-platform/blob/main/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt).\n    `ViewModels` can use constructor injection, but this requires more setup. This approach of using a graph\n    interface was simpler and faster.\n\n    Another example where this approach is handy is in [`NavigationPresenterImpl`](https://github.com/amzn/app-platform/blob/main/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt).\n    This class waits for the user scope to be available and then optionally retrieves the `Presenter` that is part\n    of the user graph. Constructor injection cannot be used, because `NavigationPresenterImpl` is part of the app\n    scope and cannot inject dependencies from the user scope, which is a child scope of app scope. This would violate\n    dependency inversion rules.\n\n    ```kotlin hl_lines=\"17\"\n    @ContributesTo(UserScope::class)\n    interface UserGraph {\n      val userPresenter: UserPagePresenter\n    }\n\n    @Composable\n    override fun present(input: Unit): BaseModel {\n      val scope = getUserScope()\n      if (scope == null) {\n        // If no user is logged in, then show the logged in screen.\n        val presenter = remember { loginPresenter() }\n        return presenter.present(Unit)\n      }\n\n      // A user is logged in. Use the user graph to get an instance of UserPagePresenter, which is only\n      // part of the user scope.\n      val userPresenter = remember(scope) { scope.metroDependencyGraph<UserGraph>().userPresenter }\n      return userPresenter.present(Unit)\n    }\n    ```\n\n### Default bindings\n\nApp Platform provides a few defaults that can be injected, including a `CoroutineScope` and `CoroutineDispatchers`.\n\n```kotlin\n@Inject\nclass SampleClass(\n  @ForScope(AppScope::class) appScope: CoroutineScope,\n\n  @IoCoroutineDispatcher ioDispatcher: CoroutineDispatcher,\n  @DefaultCoroutineDispatcher defaultDispatcher: CoroutineDispatcher,\n  @MainCoroutineDispatcher mainDispatcher: CoroutineDispatcher,\n)\n```\n\n!!! info \"CoroutineScope\"\n\n    The `CoroutineScope` uses the IO dispatcher by default. The qualifier `@ForScope(AppScope::class)` is needed to\n    allow other scopes to have their own `CoroutineScope`. For example, the sample app provides a `CoroutineScope`\n    [for the user scope](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserComponent.kt),\n    which gets canceled when the user scope gets destroyed. The `CoroutineScope` for the user scope uses the qualifier\n    `@ForScope(UserScope::class)\n\n    ```kotlin\n    /**\n     * Provides the [CoroutineScopeScoped] for the user scope. This is a single instance for the user\n     * scope.\n     */\n    @Provides\n    @SingleIn(UserScope::class)\n    @ForScope(UserScope::class)\n    fun provideUserScopeCoroutineScopeScoped(\n      @IoCoroutineDispatcher dispatcher: CoroutineDispatcher\n    ): CoroutineScopeScoped {\n      return CoroutineScopeScoped(dispatcher + SupervisorJob() + CoroutineName(\"UserScope\"))\n    }\n\n    /**\n     * Provides the [CoroutineScope] for the user scope. A new child scope is created every time an\n     * instance is injected so that the parent cannot be canceled accidentally.\n     */\n    @Provides\n    @ForScope(UserScope::class)\n    fun provideUserCoroutineScope(\n      @ForScope(UserScope::class) userScopeCoroutineScopeScoped: CoroutineScopeScoped\n    ): CoroutineScope {\n      return userScopeCoroutineScopeScoped.createChild()\n    }\n    ```\n\n!!! info \"CoroutineDispatcher\"\n\n    It's recommended to inject `CoroutineDispatcher` through the constructor instead of using `Dispatcher.*`. This\n    allows to easily swap them within unit tests to remove concurrency and improve stability.\n\n### `@ContributesScoped`\n\n!!! warning\n\n    Metro uses `@ContributesScoped` for `Scoped` integrations. `kotlin-inject-anvil` achieves a\n    similar result by repurposing `@ContributesBinding` with a custom code generator.\n\nThe [`Scoped`](scope.md#scoped) interface is used to notify implementations when a `Scope` gets created and destroyed.\n\n```kotlin\nclass AndroidLocationProvider : LocationProvider, Scoped {\n\n  override fun onEnterScope(scope: Scope) {\n    ...\n  }\n    \n  override fun onExitScope() {\n    ...\n  }\n}\n```\nThe implementation class `AndroidLocationProvider` needs to be bound to the super type `LocationProvider` and use\nmulti-bindings for the `Scoped` interface. This is a lot of boilerplate to write that be auto-generated using\n`@ContributesScoped` instead. When using `@ContributesScoped`, all bindings are generated and `@ContributesBinding`\ndoesn't need to be added. A typical implementation looks like this:\n\n```kotlin hl_lines=\"3\"\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass AndroidLocationProvider : LocationProvider, Scoped\n```\n\nSee the documentation for [`Scoped`](scope.md#scoped) for more details.\n\n### Missing integrations\n\nMetro already supports almost all App Platform specific custom extensions that previously existed\nfor `kotlin-inject-anvil`, including `@ContributesRenderer` and `@ContributesRobot`. The remaining\ngap is support for `@ContributesRealImpl` and `@ContributesMockImpl`, which still needs a Metro\nequivalent.\n\n### Migrating to Metro from kotlin-inject-anvil\n\nMetro and `kotlin-inject-anvil` are conceptually very similar. Since Metro is the recommended\ndefault, migrating existing `kotlin-inject-anvil` code is usually mostly mechanical. Errors will be\nreported at compile time and not runtime.\n\nSteps could like this. [PR/173](https://github.com/amzn/app-platform/pull/173) highlights this migration for the\n`:sample` application.\n\n* It's strongly recommended to use the latest Kotlin and Metro version. Metro is a compiler plugin and tied to the compiler to a certain degree.\n* Enable Metro in the Gradle DSL:\n```groovy\nappPlatform {\n    enableMetro true\n}\n```\n* Change kotlin-inject specific imports to Metro:\n```\nme.tatarka.inject.annotations.IntoSet -> dev.zacsweers.metro.IntoSet\nme.tatarka.inject.annotations.Provides -> dev.zacsweers.metro.Provides\nsoftware.amazon.lastmile.kotlin.inject.anvil.AppScope -> dev.zacsweers.metro.AppScope\nsoftware.amazon.lastmile.kotlin.inject.anvil.ContributesTo -> dev.zacsweers.metro.ContributesTo\nsoftware.amazon.lastmile.kotlin.inject.anvil.ForScope -> dev.zacsweers.metro.ForScope\nsoftware.amazon.lastmile.kotlin.inject.anvil.SingleIn -> dev.zacsweers.metro.SingleIn\n```\n* Update the final kotlin-inject components to Metro. The Metro docs explain the API very well. E.g. this component had to adopt a factory:\n```kotlin\n// Old:\n@Component\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class DesktopAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) :\n  DesktopAppComponentMerged\n\n// New:\n@DependencyGraph(AppScope::class)\ninterface DesktopAppComponent {\n  @DependencyGraph.Factory\n  fun interface Factory {\n    fun create(@Provides rootScopeProvider: RootScopeProvider): DesktopAppComponent\n  }\n}\n```\n* Change usages of `addKotlinInjectComponent()` to `addMetroDependencyGraph()` and usages of `kotlinInjectComponent()` to `metroDependencyGraph()`.\n\n## kotlin-inject-anvil\n\n!!! note\n\n    This section documents the supported alternative path. Prefer the Metro section above for new\n    App Platform code.\n\n    `kotlin-inject-anvil` is an opt-in feature through the Gradle DSL. The default value is `false`.\n    ```groovy\n    appPlatform {\n      enableKotlinInject true\n    }\n    ```\n\n### Component\n\nComponents are added as a service to the `Scope` class and can be obtained using the `kotlinInjectComponent()` extension\nfunction:\n\n```kotlin\nscope.kotlinInjectComponent<AppComponent>()\n```\n\nIn modularized projects, final components are defined in the `:app` modules, because the object graph has to\nknow about all features of the app. It is strongly recommended to create a component in each platform specific\nfolder to provide platform specific types.\n\n=== \"Android\"\n\n    ```kotlin title=\"androidMain\"\n    @SingleIn(AppScope::class)\n    @MergeComponent(AppScope::class)\n    abstract class AndroidAppComponent(\n      @get:Provides val application: Application,\n      @get:Provides val rootScopeProvider: RootScopeProvider,\n    )\n    ```\n\n=== \"iOS\"\n\n    ```kotlin title=\"iosMain\"\n    @SingleIn(AppScope::class)\n    @MergeComponent(AppScope::class)\n    abstract class IosAppComponent(\n      @get:Provides val uiApplication: UIApplication,\n      @get:Provides val rootScopeProvider: RootScopeProvider,\n    )\n    ```\n\n=== \"Desktop\"\n\n    ```kotlin title=\"desktopMain\"\n    @SingleIn(AppScope::class)\n    @MergeComponent(AppScope::class)\n    abstract class DesktopAppComponent(\n      @get:Provides val rootScopeProvider: RootScopeProvider\n    )\n    ```\n\n=== \"WasmJs\"\n\n    ```kotlin title=\"wasmJsMain\"\n    @MergeComponent(AppScope::class)\n    @SingleIn(AppScope::class)\n    abstract class WasmJsAppComponent(\n      @get:Provides val rootScopeProvider: RootScopeProvider\n    )\n    ```\n\n\n### Platform implementations\n\n`kotlin-inject-anvil` makes it simple to provide platform specific implementations for abstract APIs without needing\nto use `expect / actual` declarations or any specific wiring. Since the final components live in the platform specific\nsource folders, all contributions for a platform are automatically picked up. Platform specific implementations can\nuse and inject types from the platform.\n\n```kotlin title=\"commonMain\"\ninterface LocationProvider\n```\n\n=== \"Android\"\n\n    ```kotlin title=\"androidMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class AndroidLocationProvider(\n      val application: Application,\n    ) : LocationProvider\n    ```\n\n=== \"iOS\"\n\n    ```kotlin title=\"iosMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class IosLocationProvider(\n      val uiApplication: UIApplication,\n    ) : LocationProvider\n    ```\n\n=== \"Desktop\"\n\n    ```kotlin title=\"desktopMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class DesktopLocationProvider(\n      ...\n    ) : LocationProvider\n    ```\n\n=== \"WasmJs\"\n\n    ```kotlin title=\"wasmJsMain\"\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class WasmLocationProvider(\n      ...\n    ) : LocationProvider\n    ```\n\nOther common code within `commonMain` can safely inject and use `LocationProvider`.\n\n### Injecting dependencies\n\nIt's recommended to rely on constructor injection as much as possible, because it removes boilerplate and makes\ntesting easier. But it some cases it's required to get a dependency from a component where constructor injection\nis not possible, e.g. in a static context or types created by the platform. In this case a contributed component\ninterface with access to the `Scope` help:\n\n```kotlin title=\"androidMain\"\nclass MainActivityViewModel(application: Application) : AndroidViewModel(application) {\n\n  private val component = (application as RootScopeProvider).rootScope.kotlinInjectComponent<Component>()\n  private val templateProvider = component.templateProviderFactory.createTemplateProvider()\n\n  @ContributesTo(AppScope::class)\n  interface Component {\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n```\n\nThis sample shows an Android `ViewModel` that doesn't use constructor injection. Instead, the `Scope` is retrieved\nfrom the `Application` class and the `kotlin-inject-anvil` component is found through the `kotlinInjectComponent()` function.\n\n??? example \"Sample\"\n\n    The `ViewModel` example comes from the [sample app](https://github.com/amzn/app-platform/blob/main/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt).\n    `ViewModels` can use constructor injection, but this requires more setup. This approach of using a component\n    interface was simpler and faster.\n\n    Another example where this approach is handy is in [`NavigationPresenterImpl`](https://github.com/amzn/app-platform/blob/main/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt).\n    This class waits for the user scope to be available and then optionally retrieves the `Presenter` that is part\n    of the user component. Constructor injection cannot be used, because `NavigationPresenterImpl` is part of the app\n    scope and cannot inject dependencies from the user scope, which is a child scope of app scope. This would violate\n    dependency inversion rules.\n\n    ```kotlin hl_lines=\"17\"\n    @ContributesTo(UserScope::class)\n    interface UserComponent {\n      val userPresenter: UserPagePresenter\n    }\n\n    @Composable\n    override fun present(input: Unit): BaseModel {\n      val scope = getUserScope()\n      if (scope == null) {\n        // If no user is logged in, then show the logged in screen.\n        val presenter = remember { loginPresenter() }\n        return presenter.present(Unit)\n      }\n\n      // A user is logged in. Use the user component to get an instance of UserPagePresenter, which is only\n      // part of the user scope.\n      val userPresenter = remember(scope) { scope.kotlinInjectComponent<UserComponent>().userPresenter }\n      return userPresenter.present(Unit)\n    }\n    ```\n\n### Default bindings\n\nApp Platform provides a few defaults that can be injected, including a `CoroutineScope` and `CoroutineDispatchers`.\n\n```kotlin\n@Inject\nclass SampleClass(\n  @ForScope(AppScope::class) appScope: CoroutineScope,\n\n  @IoCoroutineDispatcher ioDispatcher: CoroutineDispatcher,\n  @DefaultCoroutineDispatcher defaultDispatcher: CoroutineDispatcher,\n  @MainCoroutineDispatcher mainDispatcher: CoroutineDispatcher,\n)\n```\n\n!!! info \"CoroutineScope\"\n\n    The `CoroutineScope` uses the IO dispatcher by default. The qualifier `@ForScope(AppScope::class)` is needed to\n    allow other scopes to have their own `CoroutineScope`. For example, the sample app provides a `CoroutineScope`\n    [for the user scope](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserComponent.kt),\n    which gets canceled when the user scope gets destroyed. The `CoroutineScope` for the user scope uses the qualifier\n    `@ForScope(UserScope::class)\n\n    ```kotlin\n    /**\n     * Provides the [CoroutineScopeScoped] for the user scope. This is a single instance for the user\n     * scope.\n     */\n    @Provides\n    @SingleIn(UserScope::class)\n    @ForScope(UserScope::class)\n    fun provideUserScopeCoroutineScopeScoped(\n      @IoCoroutineDispatcher dispatcher: CoroutineDispatcher\n    ): CoroutineScopeScoped {\n      return CoroutineScopeScoped(dispatcher + SupervisorJob() + CoroutineName(\"UserScope\"))\n    }\n\n    /**\n     * Provides the [CoroutineScope] for the user scope. A new child scope is created every time an\n     * instance is injected so that the parent cannot be canceled accidentally.\n     */\n    @Provides\n    @ForScope(UserScope::class)\n    fun provideUserCoroutineScope(\n      @ForScope(UserScope::class) userScopeCoroutineScopeScoped: CoroutineScopeScoped\n    ): CoroutineScope {\n      return userScopeCoroutineScopeScoped.createChild()\n    }\n    ```\n\n!!! info \"CoroutineDispatcher\"\n\n    It's recommended to inject `CoroutineDispatcher` through the constructor instead of using `Dispatcher.*`. This\n    allows to easily swap them within unit tests to remove concurrency and improve stability.\n\n\n## Metro vs `kotlin-inject-anvil`\n\nMetro supports all features of `kotlin-inject-anvil` and `kotlin-inject`, produces more efficient code, provides \nbetter error messages and compiles much faster. Metro is the recommended default for new projects,\nwhile `kotlin-inject-anvil` remains the supported alternative when you need compatibility with\nexisting code. We strongly recommend using Metro for new projects and migrating existing projects\nsoon.\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# FAQ\n\n#### How can I incrementally adopt App Platform?\n\nApp Platform offers many recommendations and best practices and hardly enforces any principles, e.g.\nit’s possible to adopt the concept of the module structure without the `Scope` class or `Presenters`.\n`Presenters` can be used without Compose UI. This and the fact that App Platform is extensible allows\nfor an incremental adoption. Apps can leverage the concepts and the framework without migrating all code at\nonce.\n\nFor example, instead of going all in on the unidirectional dataflow, Android apps can start adopting `Presenters` and\n`Renderers` on an Activity by Activity or Fragment by Fragment basis. Today we recommend starting\nnew App Platform code with Metro. Earlier, our Android app initially used\n[Dagger 2](https://dagger.dev/) and [Anvil](https://github.com/square/anvil) as dependency\ninjection framework and later made it interop with `kotlin-inject-anvil` before switching fully.\n\n\n#### Can I use [Dagger 2](https://dagger.dev/) or any other DI framework?\n\nIt depends, but likely yes. App Platform recommends [Metro](di.md) as the default DI framework because\nit supports Kotlin Multiplatform, verifies the dependency graph at compile time, and is the direction\nthe framework docs and examples assume.\n\n[kotlin-inject-anvil](https://github.com/amzn/kotlin-inject-anvil) remains supported as the\nalternative, especially for existing codebases or when you need compatibility with older App Platform\nexamples.\n\nDagger 2 is more challenging, because it only supports Android and JVM application. Metro is the\nrecommended default today, though App Platform started on Android with Dagger 2 and we first\nbridged those Dagger 2 components with `kotlin-inject-anvil` for interop.\n\n\n#### How does App Platform compare to [Circuit](https://slackhq.github.io/circuit/)?\n\nCircuit shares certain aspects with App Platform in regards to implementing the unidirectional dataflow,\ne.g. presenters and decoupling UI. How `Screens` with Circuit work vs how App Platform relies on composing presenters\nand renderers is different.\n\nApp Platform goes further and has feature that Circuit doesn't provide, e.g. the module structure, the strong\nemphasis on fakes and robots.\n\nAt Amazon we built App Platform months before Circuit was released in 2022 and at this point there was no reason for\nus to migrate off of App Platform and to Circuit.\n\n!!! note \"Help needed\"\n\n    Help from the community for a more in-depth comparison is needed.\n\n\n#### Is App Platform used in production by Amazon?\n\nApp Platform was developed within the Amazon Delivery organization and is used to share code between several\napplications and platforms. Public products include the [in-vehicle delivery app](https://www.youtube.com/watch?v=0T_zvUEqsD4),\n[Amazon Flex for Android and iOS](https://flex.amazon.com/) and the Linux based\n[Vision-Assisted Package Retrieval](https://www.aboutamazon.com/news/transportation/amazon-vapr-delivery-van-packages).\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nsocial:\n  cards_layout_options:\n    title: Application framework for KMP\n---\n\n# App Platform\n\n## Introduction\n\n![App Platform](images/app-platform-logo.png){ align=left width=\"150\"  }\n\nThe App Platform is a lightweight application framework for state and memory management suitable\nfor Kotlin Multiplatform projects, in particular Android, iOS, JVM, native and Web. It makes the\ndependency inversion (1) and dependency injection (DI) design patterns first class principles to develop\nfeatures and support the variety of platforms. The UI layer is entirely decoupled from the business logic,\nwhich allows different application targets to change the look and feel.\n{ .annotate }\n\n1.  Dependency inversion means that high-level APIs don’t depend on low-level details and low-level details only import other high-level APIs.\n\nApp Platform pushes for code reuse by sharing APIs and implementations, while making it easy to leverage\nplatform strengths and changing app or device specific behavior when needed. The framework helps you to get started\nwriting Kotlin Multiplatform effectively.\n\n=== \"Web (clickable)\"\n    <iframe src=\"web/sample/app/build/dist/wasmJs/productionExecutable/index.html\" width=\"300px\" height=\"600px\" frameborder=\"1\"></iframe>\n\n=== \"Android\"\n    ![Android screenshot](images/Android.png){ width=\"300\" }\n\n=== \"iOS\"\n    ![iOS screenshot](images/iOS.png){ width=\"300\" }\n\n=== \"Desktop\"\n    ![Desktop screenshot](images/Desktop.png){ width=\"300\" }\n\n=== \"Web Recipe App\"\n    <iframe src=\"web/recipes/app/build/dist/wasmJs/productionExecutable/index.html\" width=\"300px\" height=\"600px\" frameborder=\"1\"></iframe>\n\n## Overview\n\nApp Platform combines several features as a single framework. While all of them are optional, together they help\nto implement recommended best practices and design patterns.\n\n### Module Structure\n\nThe [module structure](module-structure.md) helps to separate APIs from implementations. This prevents leaking\nimplementation details, forces developers to think about strong APIs and reduces build times. Checks for the correct\nusage of the module structure are implemented in the Gradle plugin.\n\n### Dependency Injection\n\nApp Platform provides first-class support for [Metro](di.md#metro) and\n[kotlin-inject-anvil](di.md#kotlin-inject-anvil) as dependency injection solutions. Metro is the\nrecommended default, but these frameworks aren't enforced and you can bring your own (1).\n{ .annotate }\n\n1.  Today App Platform recommends [Metro](https://zacsweers.github.io/metro) for new work.\n    Historically, the very first versions at Amazon used [Dagger 2](https://dagger.dev/) and\n    [Anvil](https://github.com/square/anvil), and later migrated to\n    [kotlin-inject-anvil](https://github.com/amzn/kotlin-inject-anvil).\n\n### Scopes\n\n[`Scopes`](scope.md) are essential in our architecture. They define the boundary our software components\noperate in. A scope is a space with a well-defined lifecycle that can be created and torn down. App Platform\nprovides hooks to create your own scopes with easy callbacks, integration for dependency injection\nframeworks and `CoroutineScopes`.\n\n### Presenters\n\n[Presenters](presenter.md) are implemented using [Molecule](https://github.com/cashapp/molecule). Writing business and\nnavigation logic using *Compose* is significantly easier than chaining `Flows`.\n\n### UI\n\nThe UI layer is fully decoupled using [Renderers](renderer.md). [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/)\nis fully supported out of the box. For Android there is seamless interop with Android `Views` (1).\n{ .annotate }\n\n1.  We have a mix of both UI frameworks on Android.\n\n### Testing\n\nFakes for unit and device tests are essential and integral part of our architecture. There are many\n[test helpers](testing.md) to setup fakes for core components such as `Scopes`. We like using\n[Turbine](https://github.com/cashapp/turbine/) for verifying the reactive behavior of our `Presenters`.\nThanks to *Compose Multiplatform*, `Renderers` [can be tested](renderer.md#unit-tests) in isolation for iOS\nand Desktop.\n\n### Integration\n\nThe [Gradle plugin](setup.md) comes with a convenient DSL to take care of many necessary configurations, e.g. it sets\nup the *Compose* compiler for *Molecule* and *Compose Multiplatform*. It configures KSP and integrates\n*Metro* or *kotlin-inject-anvil* for each platform. It sets the Android namespace and artifact ID when the module\nstructure is enabled.\n\n## Getting Started\n\nApp Platform gives you a working Kotlin Multiplatform setup out of the box, with support for Android, iOS, Desktop, and Web (WASM). The fastest way to get started is by using the [blueprints/starter](https://github.com/amzn/app-platform/tree/main/blueprints/starter) project — a fully functional example app that already uses App Platform and applies everything the platform provides, including the module structure, dependency injection, scopes, presenters, and renderers.\n\n### Copy the Starter App\n\nTo begin a new project:\n\n```bash\ngit clone https://github.com/amzn/app-platform.git\ncp -r app-platform/blueprints/starter my-kmp-app\ncd my-kmp-app\n```\n\nThe starter blueprint comes preconfigured with App Platform and is ready to build and run across all supported targets.\n\n### Build and Run\n\nThe starter project includes a detailed [README](https://github.com/amzn/app-platform/blob/main/blueprints/starter/README.md) with instructions for building and running the app on each platform:\n\n- Android\n- iOS\n- Desktop\n- Web (WASM)\n\nFollow the steps in that README to get your app running locally.\n\n## License\n\n```\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "docs/module-structure.md",
    "content": "# Module Structure\n\n!!! note\n\n    Using the module structure is an opt-in feature through the Gradle DSL. The default value is `false` and\n    this feature has to be enabled for each module.\n    ```groovy\n    appPlatform {\n      enableModuleStructure true\n    }\n    ```\n\n!!! tip\n\n    [`:impl`](module-structure.md#impl) modules are usually imported by the final [`:app`](module-structure.md#app)\n    modules. This also applies to App Platform itself. This Gradle option imports all necessary `:impl` modules for\n    enabled features.\n    ```groovy\n    appPlatform {\n      addImplModuleDependencies true\n    }\n    ```\n\n!!! example \"Sample\"\n\n    App Platform itself and the [sample app](https://github.com/amzn/app-platform/tree/main/sample) use the module\n    structure to separate APIs from implementations. The sample app highlights how we structure code and make use\n    of the various module types.\n\n## Dependency inversion\n\nDependency inversion means that high-level APIs don’t depend on low-level details and low-level details\nonly import other high-level APIs. It significantly reduces coupling between components. Dependency\ninversion can be implemented on different levels, e.g. in code and in the module structure.\n\n### Kotlin code\n\nDependency inversion implemented in Kotlin code refers to having abstractions in place instead of\nrelying on concrete implementations. Imagine this example:\n\n```kotlin\nclass AccountProvider(\n  private val database: SqliteDatabase,\n  ...\n) {\n  val currentAccount: StateFlow<Account> = ...\n\n  fun updateCurrentAccount(account: Account) {\n    ...\n  }\n}\n\nclass ChangeAccountHandler(\n  private val accountProvider: AccountProvider\n) {\n\n  private fun onAccountChanged(account: Account) {\n    accountProvider.updateCurrentAccount(account)\n    ...\n  }\n}\n```\n\n`ChangeAccountHandler` has a strong dependency on `AccountProvider`. This is problematic in multiple ways.\nEvolving `AccountProvider` is challenging, because implementation details are easily leaked and become\npart of the public API. Every dependency from `AccountProvider` is exposed to consumers, e.g. `ChangeAccountHandler`\nknows that `AccountProvider` uses Sqlite for its implementation, a detail which should be hidden and makes\ndependency graphs unnecessarily large. `ChangeAccountHandler` is hard to test. One has to spin up a Sqlite database\nin a unit test environment in order to instantiate `AccountProvider` and pass it as argument to\n`ChangeAccountHandler`.\n\nA much better approach is introducing abstract APIs:\n\n```kotlin\ninterface AccountProvider {\n  val currentAccount: StateFlow<Account>\n\n  fun updateCurrentAccount(account: Account)\n}\n\nclass SqliteAccountProvider(\n  private val database: SqliteDatabase\n  ...\n) : AccountProvider {\n\n  @VisibleForTesting\n  val allAccounts: List<Account> = ...\n\n  ...\n}\n```\n\nThe interface `AccountProvider` solves the mentioned shortcomings. `SqliteAccountProvider` can change and\nfor example expose more fields (`allAccounts` in this sample) for verifications in unit tests without anyone\nknowing as the interface doesn’t need to be updated. Sqlite is a pure implementation detail and no consumer\nof `AccountProvider` has to know about it. This allows us to easily swap the implementation for a fake\n`AccountProvider` together with fake data in a unit test for `ChangeAccountHandler`.\n\nBreaking the dependency serves an additional purpose especially in Kotlin Multiplatform when\nimplementations have platform dependencies:\n\n```kotlin\n// commonMain\ninterface SqlDriver\n\n// androidMain\nclass AndroidSqlDriver(context: Context) : SqlDriver\n\n// iosMain\nclass NativeSqlDriver() : SqlDriver\n```\n\nNotice how the Android implementation has a strong dependency on the Android runtime through the `Context`\nclass. Relying on interfaces / abstract classes together with dependency injection is the\n[preferred way](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-connect-to-apis.html#dependency-injection-framework) (1)\nover `expect / actual` functions to implement dependency inversion as this approach allows platform specific changes.\n{ .annotate }\n\n1.  When you use a DI framework, you inject all of the dependencies through this framework. The same logic applies to handling platform dependencies. We recommend continuing to use DI if you already have it in your project, rather than using the expected and actual functions manually. This way, you can avoid mixing two different ways of injecting dependencies.\n\n### Gradle modules\n\nThe App Platform separates APIs from implementations by splitting the code in separate Gradle modules. The same\nrecommendation applies not only to other core libraries but also feature code due to the many benefits such as\nsmaller dependency graphs, lower coupling and a simple mechanism to replace dependencies with fakes.\n\nImagine having two implementations of the shared interface `LocationProvider` for two applications\n*Delivery App* and *Navigation App*:\n\n```kotlin\ninterface LocationProvider {\n  val location: StateFlow<Location>\n}\n\nclass DeliveryAppLocationProvider(\n  private val dataLayer: DeliveryAppDataLayer,\n  ...\n) : LocationProvider {..}\n\nclass NavigationAppLocationProvider(\n  private val application: NavigationApplication,\n  ...\n) : LocationProvider {..}\n```\n\nIf both classes live in the same module, then the shared Gradle module must depend on modules belonging to\n*Delivery* and *Navigation* App at the same time. This is not ideal, because then the *Delivery App* would\nautomatically depend on code from the *Navigation App* and the *Navigation App* on *Delivery App* code through\na transitive dependency as highlighted in the diagram below.\n\n```mermaid\n%%{init: {'themeCSS': '.label { font-family: monospace; }'}}%%\ngraph TD\n  delivery-platform[\"`:delivery-platform`\"]\n  navigation-platform[\"`:navigation-platform`\"]\n  location[\"`**:location**\n  *DeliveryAppLocationProvider*\n  *NavigationAppLocationProvider*`\"]\n  delivery-app[\"`:delivery-app`\"]\n  navigation-app[\"`:navigation-app`\"]\n\n  delivery-platform --> location\n  navigation-platform --> location\n  location --> delivery-app\n  location --> navigation-app\n```\n\nTo avoid the issue of the transitive dependencies, concrete implementation classes `DeliveryAppLocationProvider`\nand `NavigationAppLocationProvider` could be moved into the final respective application packages `:delivery-app`\nand `:navigation-app`.\n\n```mermaid\n%%{init: {'themeCSS': '.label { font-family: monospace; }'}}%%\ngraph TD\n  delivery-platform[\"`:delivery-platform`\"]\n  location[\"`:location`\"]\n  navigation-platform[\"`:navigation-platform`\"]\n  delivery-app[\"`**:delivery-app**\n  *DeliveryAppLocationProvider*`\"]\n  navigation-app[\"`**:navigation-app**\n  *NavigationAppLocationProvider*`\"]\n\n  delivery-platform --> delivery-app\n  navigation-platform --> navigation-app\n  location --> delivery-app\n  location --> navigation-app\n```\n\nHowever, this would be a bad approach from a modularization standpoint. The app modules would become\nlarger and larger over time and the many classes within it would have a low cohesion level. Build times get\nlonger roughly linear to the size of the module, because individual build steps such as Kotlin compilation\ncan’t be parallelized.\n\nInstead, a similar approach to [dependency inversion in Kotlin code](module-structure.md#kotlin-code)\nis applied to modules. The shared package can be split into a public API and implementation sub-module:\n\n```mermaid\n%%{init: {'themeCSS': '.label { font-family: monospace; }'}}%%\ngraph TD\n  delivery-platform[\"`:delivery-platform`\"]\n  location-public[\"`:location:public`\"]\n  navigation-platform[\"`:navigation-platform`\"]\n  location-impl-delivery[\"`**:location:impl-delivery**\n  *DeliveryAppLocationProvider*`\"]\n  location-impl-navigation[\"`**:location:impl-navigation**\n  *NavigationAppLocationProvider*`\"]\n  delivery-app[\"`:delivery-app`\"]\n  navigation-app[\"`:navigation-app`\"]\n\n  delivery-platform --> location-impl-delivery\n  navigation-platform --> location-impl-navigation\n  location-public --> location-impl-delivery\n  location-public --> location-impl-navigation\n  location-impl-delivery --> delivery-app\n  location-impl-navigation --> navigation-app\n```\n\nBy cleanly separating shared code in `:public` modules from implementations in `:impl` modules we break\ndependencies in our build graph. `DeliveryAppLocationProvider` and `NavigationAppLocationProvider` provide a\nseparate implementation for each application target of the shared API, have dependencies on each individual\nplatform and yet don’t leak any implementation details nor platform APIs.\n\n## Module rules\n\nIn order to follow the dependency inversion principle correctly the most important rule in this module structure\nis that no other module but the final application module is allowed to depend on `:impl` modules. `:public`\nmodules on the other hand are widely shared and can be imported by any other module.\n\n![Forbidden dependency](images/module-structure-forbidden-dep.png){ width=\"600\" }\n\nA library always comes with a single `:public` module for shared code. There can be zero, one or more `:impl`\nmodules, e.g. when dependency inversion isn’t needed, then the `:impl` module is redundant. When the implementation\ncan be shared between all apps, then only a single `:impl` module is needed. When there are multiple different\nimplementations for different applications, then multiple `:impl` modules are required like in the example above.\nTo make code easier to discover, it’s recommended to put all Gradle modules into the same sub module.\n\nThis module structure reduces coupling between libraries and increases cohesion within modules, which are two\ndesired attributes in a modularized codebase. `:impl` modules can change and be modified without impacting any\nother library. Our build dependency graph stays flat and all `:impl` modules can be compiled and assembled in\nparallel.\n\nThe `:public / :impl` module split is recommended whenever dependency inversion is needed for code, because of\nall the benefits mentioned above. The split becomes more natural over time and the benefit increases. Rare\nexceptions are when dependency inversion isn’t applied such as for sharing utilities like extension functions,\nUI components or test helpers.\n\n## Module types\n\nBeyond `:public` and `:impl` modules, there are further optional module types:\n\n![Module types](images/module-structure-types.png){ width=\"600\" }\n\n### `:public`\n\n`:public` modules contain the code that should be shared and reused by other modules and libraries.\nAPIs (interfaces) usually live in `:public` modules, but also code where dependency inversion isn’t applied\nsuch as static utilities, extension functions and UI components.\n\n### `:impl`\n\n`:impl` modules contain the concrete implementations of the API from `:public` modules. A library can have\nzero or more `:impl` modules. If a library contains multiple `:impl` modules, then they’re suffixed with a name,\ne.g. `:login:impl-amazon` and `:login:impl-google`.\n\n### `:internal`\n\n`:internal` modules are used when code should be shared between multiple `:impl` modules of the same library,\nbut the code should not be exposed through the `:public` module. This code is *internal* to this library.\n\n### `:testing`\n\n`:testing` modules provide a mechanism to share utilities or fake implementations for tests with other libraries.\n`:testing` modules are allowed to be imported as test dependency by any other module type and are never added\nto the runtime classpath. Even its own `:public` module can reuse the code from the `:testing` module for its tests.\n\n### `:robots`\n\n`:*-robots` modules help implementing the robot pattern for UI tests and make them shareable. Robots must know\nabout concrete implementations, therefore they usually depend on an `:impl` module, but don't expose this `:impl`\nmodule on the compile classpath. `:robot` modules are only imported and reused for UI tests and are never\nadded as dependency to the runtime classpath of a module similar to `:testing` modules.\n\n### `:app`\n\n`:app` modules refer to the final application, where all feature implementations are imported and assembled\nas a single binary. Therefore, `:app` modules are allowed to depend on `:impl` modules of all imported libraries\nand features.\n\n## Example\n\nA more complex dependency graph could look like this:\n\n![Module structure example](images/module-structure-example.png)\n\nThis example highlights many of the more frequently used dependencies. Notice that the impl modules\n`:location:impl-delivery` and `:location:impl-navigation` both depend on the internal module `:location:internal`\nto share some implementations, but non-shared code lives in each `:impl` module. The `:impl` modules import\napplication specific code `:delivery-app-platform:public` and `:navigation-app-platform:public` safely without\nleaking the code to the wrong app. Further, `:location:impl-navigation` imports and uses `:navigation:public`,\nbut neither the other impl module `:location:impl-delivery` nor its public module `:location:public` need to\nknow about this dependency or depend on it.\n\nThe second library `:navigation:public`, which imports `:location:public`, reuses testing module `:location:testing`\nfor its unit tests. This saves boilerplate to setup fake implementations of the shared APIs from `:location:public`\nand discourages using mocking frameworks.\n\nThe app `:navigation-app` imports its specific impl module `:location:impl-navigation`. It also reuses the\nrobots from the `:location:impl-navigation-robots` module for its UI tests, further reducing strong dependencies\non concrete implementations and favoring reusability.\n\n## Gradle setup\n\nUsing the module structure is an opt-in feature through the Gradle DSL. The default value is `false` and\nthis feature has to be enabled for each module.\n\n```groovy\nappPlatform {\n  enableModuleStructure true\n}\n```\n\nWith this setting enabled, several checks and features are enabled:\n\n* App Platform ensures that the Gradle module follows the naming convention, e.g. it's named `:public` or `:impl`.\n* Default dependencies are added, e.g. an `:impl` module imports its `:public` module by default, or `:impl-robots` imports its `:impl` module by default.\n* An [Android namespace](https://developer.android.com/build/configure-app-module#set-namespace) is set [automatically](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/ModuleStructurePlugin.kt#L90-L110) if it hasn't been configured yet.\n* A Gradle task `:checkModuleStructureDependencies` is registered, which verifies that module structure dependency rules are followed. The `:check` Gradle task automatically depends on `:checkModuleStructureDependencies`.\n* A consistent API for an [`Project.artifactId`](https://github.com/amzn/app-platform/blob/main/gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/ModuleStructurePlugin.kt#L125-L135) is available, e.g. for `:my-module:public` it would return `my-module-public`.\n\n??? example \"Sample\"\n\n    The sample application doesn't set the Android namespace anywhere. Instead, it relies on the default from\n    App Platform, e.g. the `:sample:templates:impl` module uses this generated namespace for its `R` class:\n\n    ```kotlin\n    software.amazon.app.platform.sample.templates.impl.R\n    ```\n\n    App Platform uses the `Project.artifactId()` API for its own modules. Publishing using the\n    [Gradle Maven Publish Plugin](https://vanniktech.github.io/gradle-maven-publish-plugin/) is configured\n    [here](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/buildSrc/src/main/kotlin/software/amazon/app/platform/gradle/buildsrc/SdkPlugin.kt#L16-L34).\n\n    ```kotlin\n    private fun mavenPublishing(project: Project) {\n      plugins.apply(Plugins.MAVEN_PUBLISH)\n\n      project.extensions\n        .getByType(MavenPublishBaseExtension::class.java)\n        .coordinates(artifactId = project.artifactId())\n    }\n    ```\n"
  },
  {
    "path": "docs/presenter.md",
    "content": "# Presenter\n\n!!! note\n\n    While App Platform has a generic `Presenter` interface to remove coupling, we strongly recommend using\n    `MoleculePresenter` for implementations. `MoleculePresenters` are an opt-in feature through the Gradle DSL.\n    The default value is `false`.\n\n    ```groovy\n    appPlatform {\n      enableMoleculePresenters true\n    }\n    ```\n\n## Unidirectional dataflow\n\nApp Platform implements the unidirectional dataflow pattern to decouple business logic from UI rendering. Not only\ndoes this allow for better testing of business logic and provides clear boundaries, but individual apps can also\nshare more code and change the look and feel when needed.\n\n## `MoleculePresenter`\n\nIn the unidirectional dataflow pattern events and state only travel into one direction through a single stream.\nState is produced by `Presenters` and can be observed through a reactive stream:\n\n```kotlin\ninterface Presenter<ModelT : BaseModel> {\n  val model: StateFlow<ModelT>\n}\n```\n\n`Presenters` can be implemented in many ways as long as they can be converted to this interface. App Platform\nprovides and recommends the implementation using [Molecule](https://github.com/cashapp/molecule) since it provides\nmany advantages. Molecule is a library that turns a `@Composable` function into a `StateFlow`. It leverages the\ncore of [Compose](https://developer.android.com/compose) without bringing in Compose UI as dependency. The primary\nuse case of Compose is handling, creating and modifying tree-like data structures, which is a natural fit for\nUI frameworks. Molecule reuses Compose to handle state management and state transitions to implement business\nlogic in the form of `@Composable` functions with all the benefits that Compose provides.\n\nThe [MoleculePresenter](https://github.com/amzn/app-platform/blob/main/presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculePresenter.kt)\ninterface looks like this:\n\n```kotlin\ninterface MoleculePresenter<InputT : Any, ModelT : BaseModel> {\n  @Composable\n  fun present(input: InputT): ModelT\n}\n```\n\n[`Models`](https://github.com/amzn/app-platform/blob/main/presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/BaseModel.kt)\nrepresent the state of a `Presenter`. Usually, they’re implemented as immutable, inner data classes of the `Presenter`.\nUsing sealed hierarchies is a good practice to allow to differentiate between different states:\n\n```kotlin\ninterface LoginPresenter : MoleculePresenter<Model> {\n  sealed interface Model : BaseModel {\n    data object LoggedOut : Model\n\n    data class LoggedIn(\n      val user: User,\n    ) : Model\n  }\n}\n```\n\nNotice that it’s recommended even for `Presenters` to follow the dependency inversion principle. `LoginPresenter` is\nan interface and there can be multiple implementations.\n\n??? example \"Sample\"\n\n    The sample application follows the same principle of dependency inversion. E.g. the API of the\n    [`LoginPresenter`](https://github.com/amzn/app-platform/blob/main/sample/login/public/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenter.kt)\n    is part of the `:public` module, while the implementation [`LoginPresenterImpl`](https://github.com/amzn/app-platform/blob/main/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt)\n    lives in the `:impl` module. This abstraction is used in tests, where [`FakeLoginPresenter`](https://github.com/amzn/app-platform/blob/main/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt#L45-L49)\n    simplifies the test setup of classes relying on `LoginPresenter`.\n\nObservers of the state of a `Presenter`, such as the UI layer, communicate back to the `Presenter` through events.\nEvents are sent through a lambda in the `Model`, which the `Presenter` must provide:\n\n```kotlin hl_lines=\"16\"\ninterface LoginPresenter : MoleculePresenter<Unit, Model> {\n\n  sealed interface Event {\n    data object Logout : Event\n\n    data class ChangeName(\n      val newName: String,\n    ) : Event\n  }\n\n  sealed interface Model : BaseModel {\n    data object LoggedOut : Model\n\n    data class LoggedIn(\n      val user: User,\n      val onEvent: (Event) -> Unit,\n    ) : Model\n  }\n}\n```\n\nA concrete implementation of `LoginPresenter` could look like this:\n\n```kotlin\n@Inject\n@ContributesBinding(AppScope::class)\nclass AmazonLoginPresenter : LoginPresenter {\n  @Composable\n  fun present(input: Unit): Model {\n    ..\n    return if (user != null) {\n      LoggedIn(user = user) { event ->\n        when (event) {\n          is Logout -> ..\n          is ChangeName -> ..\n        }\n      }\n    } else {\n      LoggedOut\n    }\n  }\n}\n```\n\n!!! note\n\n    `MoleculePresenters` are never singletons. While they use Metro or `kotlin-inject-anvil` for constructor injection and\n    automatically bind the concrete implementation to an API using `@ContributesBinding`, they don't use the\n    `@SingleIn` annotation. `MoleculePresenters` manage their state in the `@Composable` function with the Compose\n    runtime. Therefore, it's strongly discouraged to have any class properties.\n\n## Model driven navigation\n\n`Presenters` are composable, meaning that one presenter could combine N other presenters into a single stream of\nmodel objects. With that concept in mind we can decompose large presenters into multiple smaller ones. Not only\ndo they become easier to change, maintain and test, but we can also share and reuse presenters between multiple\nscreens if needed. Presenters form a tree with nested presenters. They’re unaware of their parent and communicate\nupwards only through their `Model`.\n\n```kotlin hl_lines=\"14 17\"\nclass OnboardingPresenterImpl(\n  // Make presenters lazy to only instantiate them when they're actually needed.\n  private val lazyLoginPresenter: () -> LoginPresenter,\n  private val lazyRegistrationPresenter: () -> RegistrationPresenter,\n) : OnboardingPresenter {\n\n  @Composable\n  fun present(input: Unit): BaseModel {\n    ...\n    return if (mustRegister) {\n      // Remember the presenter to avoid creating a new one during each\n      // composition (in other words when computing a new model).\n      val registrationPresenter = remember { lazyRegistrationPresenter() }\n      registrationPresenter.present(Unit)\n    } else {\n      val loginPresenter = remember { lazyLoginPresenter() }\n      loginPresenter.present(Unit)\n    }\n  }\n}\n```\n\nNotice how the parent presenter calls the `@Composable` `present()` function from the child presenters like\na regular function to compute their model and return it.\n\n??? example \"Sample\"\n\n    [`NavigationPresenterImpl`](https://github.com/amzn/app-platform/blob/main/sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt)\n    is another example that highlights this principle.\n\n    [`UserPagePresenterImpl`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt)\n    goes a step further. Its `BaseModel` is composed of two sub-models. The `listModel` is even an input for the\n    detail-presenter.\n\n    ```kotlin\n    val listModel = userPageListPresenter.present(UserPageListPresenter.Input(user))\n    return Model(\n      listModel = listModel,\n      detailModel =\n        userPageDetailPresenter.present(\n          UserPageDetailPresenter.Input(user, selectedAttribute = listModel.selectedIndex)\n        ),\n    )\n    ```\n\nThis concept allows us to implement model-driven navigation. By driving the entire UI layer through `Presenters` and\nemitted `Models` navigation becomes a first class API and testable. Imagine having a root presenter implementing a\nback stack that forwards the model of the top most presenter. When the user navigates to a new screen, then the\nroot presenter would add a new presenter to the stack and provide its model object.\n\n```mermaid\n%%{init: {'themeCSS': '.label { font-family: monospace; }'}}%%\ngraph TD\n  login[\"`Login presenter`\"]\n  registration[\"`Register presenter`\"]\n  onboarding[\"`Onboarding presenter`\"]\n  delivery[\"`Delivery presenter`\"]\n  settings[\"`Settings presenter`\"]\n  root[\"`Root presenter`\"]\n  ui[\"`UI Layer`\"]\n\n  login --> onboarding\n  registration --> onboarding\n  onboarding --> root\n  delivery --> root\n  settings --> root\n  root --> ui\n\n  style ui stroke:#0f0\n```\n\nIn the example above, the root presenter would forward the model of the onboarding, delivery or settings presenter\nto the UI layer. The onboarding presenter as shown in the code example can either call the login or registration\npresenter based on a condition. With Molecule calling a child presenter is as easy as invoking a function.\n\n## Parent child communication\n\nWhile the pattern isn’t used frequently, parent presenters can provide input to their child presenters. The\nreturned model from the child presenter can be used further to change the control flow.\n\n```kotlin\ninterface ChildPresenter : MoleculePresenter<Input, Model> {\n  data class Input(\n    val argument: String,\n  )\n}\n\nclass ParentPresenterImpl(\n  private val lazyChildPresenter: () -> ChildPresenter\n) : ParentPresenter {\n\n  @Composable\n  fun present(input: Unit) {\n    val childPresenter = remember { lazyChildPresenter() }\n    val childModel = childPresenter.present(Input(argument = \"abc\"))\n\n    return if (childModel...) ...\n  }\n}\n```\n\nThis mechanism is favored less, because it only allows for direct parent to child presenter interactions and\nbecomes hard to manage for deeply nested hierarchies. More often a service object is injected instead, which\nis used by the multiple presenters:\n\n```kotlin hl_lines=\"8 12 20 25 28\"\ninterface AccountManager {\n  val currentAccount: StateFlow<Account>\n\n  fun mustRegister(): Boolean\n}\n\nclass AmazonLoginPresenter(\n  private val accountManager: AccountManager\n): LoginPresenter {\n  @Composable\n  fun present(input: Unit): Model {\n    val account by accountManager.currentAccount.collectAsState()\n    ...\n  }\n}\n\nclass OnboardingPresenterImpl(\n  private val lazyLoginPresenter: () -> LoginPresenter,\n  private val lazyRegistrationPresenter: () -> RegistrationPresenter,\n  private val accountManager: AccountManager,\n) : OnboardingPresenter {\n\n  @Composable\n  fun present(input: Unit): BaseModel {\n    val account by accountManager.currentAccount.collectAsState()\n    ...\n\n    return if (accountManager.mustRegister()) {\n      val registrationPresenter = remember { lazyRegistrationPresenter() }\n      registrationPresenter.present(Unit)\n    } else {\n      val loginPresenter = remember { lazyLoginPresenter() }\n      loginPresenter.present(Unit)\n    }\n  }\n}\n```\n\nThis example shows how `AccountManager` holds state and is injected into multiple presenters instead of relying\non presenter inputs.\n\n## Launching\n\n`MoleculePresenters` can inject other presenters and call their `present()` function inline. If you are already in a\ncomposable UI context, then you can simply call the presenter to compute the model:\n\n```kotlin\nfun mainViewController(): UIViewController = ComposeUIViewController {\n  val presenter = remember { LoginPresenter() }\n  val model = presenter.present(Unit)\n  ...\n}\n```\n\nIn this example the `LoginPresenter` model is computed from an iOS Compose Multiplatform function.\n\nIn other scenarios a composable context may not be available and it's necessary to turn the `@Composable` functions\ninto a `StateFlow` for consumption.\n\n[`MoleculeScope`](https://github.com/amzn/app-platform/blob/main/presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculeScope.kt)\nhelps to turn a `MoleculePresenter` into a `Presenter`, which then exposes a `StateFlow`:\n\n```kotlin\nval stateFlow = moleculeScope\n  .launchMoleculePresenter(\n    presenter = myPresenter,\n    input = Unit,\n  )\n  .model\n```\n\n!!! warning\n\n    `MoleculeScope` wraps a `CoroutineScope`. The presenter keeps running, recomposing and producing new models\n    until the `MoleculeScope` is canceled. If the `MoleculeScope` is never canceled, then presenters leak and will\n    cause issues later.\n\n    Use [`MoleculeScopeFactory`](https://github.com/amzn/app-platform/blob/main/presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory.kt)\n    to create a new `MoleculeScope` instance and call `cancel()` when you don't need it anymore.\n\n    On Android an implementation using `ViewModels` may look like this:\n\n    ```kotlin\n    class MainActivityViewModel(\n      moleculeScopeFactory: MoleculeScopeFactory,\n      myPresenter: MyPresenter,\n    ) : ViewModel() {\n\n      private val moleculeScope = moleculeScopeFactory.createMoleculeScope()\n\n      // Expose the models for consumption.\n      val models = moleculeScope\n        .launchMoleculePresenter(\n          presenter = myPresenter,\n          input = Unit\n        )\n        .models\n\n      override fun onCleared() {\n        moleculeScope.cancel()\n      }\n    }\n    ```\n\n!!! info\n\n    By default `MoleculeScope` uses the main thread for running presenters and\n    [`RecompositionMode.ContextClock`](https://github.com/cashapp/molecule/blob/trunk/molecule-runtime/src/commonMain/kotlin/app/cash/molecule/RecompositionMode.kt),\n    meaning a new model is produced only once per UI frame and further changes are conflated.\n\n    This behavior can be changed by creating a custom `MoleculeScope`, e.g. tests make use of this:\n\n    ```kotlin\n    fun TestScope.moleculeScope(\n      coroutineContext: CoroutineContext = EmptyCoroutineContext\n    ): MoleculeScope {\n      val scope = backgroundScope + CoroutineName(\"TestMoleculeScope\") + coroutineContext\n\n      return MoleculeScope(scope, RecompositionMode.Immediate)\n    }\n    ```\n\n## Testing\n\nA [`test()`](https://github.com/amzn/app-platform/blob/main/presenter-molecule/testing/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/TestPresenter.kt)\nutility function is provided to make testing `MoleculePresenters` easy using the [Turbine](https://github.com/cashapp/turbine/)\nlibrary:\n\n```kotlin\nclass LoginPresenterImplTest {\n\n  @Test\n  fun `after 1 second the user is logged in after pressing the login button`() = runTest {\n    val userManager = FakeUserManager()\n\n    LoginPresenterImpl(userManager).test(this) {\n      val firstModel = awaitItem()\n      ...\n    }\n  }\n}\n```\n\nThe `test()` function uses the `TestScope.backgroundScope` to run the presenter.\n\n??? example \"Sample\"\n\n    The sample application implements multiple tests for its presenters, e.g.\n    [`LoginPresenterImplTest`](https://github.com/amzn/app-platform/blob/main/sample/login/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImplTest.kt),\n    [`NavigationPresenterImplTest`](https://github.com/amzn/app-platform/blob/main/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt)\n    and [`UserPagePresenterImplTest`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImplTest.kt).\n\n## Back gestures\n\n`Presenters` support back gestures with a similar API in terms of syntax and semantic to Compose Multiplatform. Any\n`Presenter` can call these functions:\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  BackHandlerPresenter {\n    // Handle a back press.  \n  }\n\n  PredictiveBackHandlerPresenter { progress: Flow<BackEventCompat> ->\n    // code for gesture back started\n    try {\n      progress.collect { backevent ->\n        // code for progress\n      }\n      // code for completion\n    } catch (e: CancellationException) {\n      // code for cancellation\n    }\n  }  \n}\n```\n\n!!! warning\n\n    Notice `Presenter` suffix in these function names. These functions should not be confused with `BackHandler {}` and\n    `PredictiveBackHandler {}` coming from Compose Multiplatform or Compose UI Android, which would fail at runtime \n    when called from a `Presenter`.\n\nCalling these functions requires `BackGestureDispatcherPresenter` to be setup as composition local. This is usually\ndone from the root presenter in your hierarchy. An instance of `BackGestureDispatcherPresenter` is provided by App\nPlatform in the application scope and can be injected:\n\n```kotlin hl_lines=\"3 7 8 9\"\n@Inject\nclass RootPresenter(\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    return returningCompositionLocalProvider(\n      LocalBackGestureDispatcherPresenter provides backGestureDispatcherPresenter\n    ) {\n      // Call other child presenters.\n    }\n  }\n}\n```\n\nThe last step is to forward back gestures from the UI layer to `Presenters` to invoke the callbacks in the\n`Presenters`. Here again it's recommended to do this from within the root `Renderer`:\n\n```kotlin hl_lines=\"4 8\"\n@Inject\n@ContributesRenderer\nclass RootPresenterRenderer(\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n) : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    backGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()\n\n    // Call other child renderers.\n  }\n}\n```\n\nA similar built-in integration is provided for Android Views. There it's recommended to call this function from each\nAndroid `Activity`:\n\n```kotlin hl_lines=\"6\"\nclass MainActivity : ComponentActivity() {\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    backGestureDispatcherPresenter.forwardBackPressEventsToPresenters(this)\n    // ...\n  }\n}\n```\n\nUnit tests verifying the behavior of a `Presenter` using the back handler APIs need to provide the composition local\nas well. This can be achieved by wrapping the `Presenter` with `withBackGestureDispatcher()`:\n\n```kotlin\nclass MyPresenterTest {\n\n  @Test\n  fun `test back handler`() = runTest {\n    val presenter = MyPresenter()\n\n    presenter.withBackGestureDispatcher().test(this) {\n      // Verify the produced models from the presenter.\n    }\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The `BackHandlerPresenter {}` call has been integrated in the sample application with this recommended setup. All\n    necessary changes are part of this [commit](https://github.com/amzn/app-platform/pull/84/commits/a807a5673973eae26940cd1130dad836cb3dbd43).\n\n    The same setup has been integrated in the recipes app part of this [commit](https://github.com/amzn/app-platform/pull/82/commits/fce1b3fbc0b2683ec6a93a499694f914bac34b18)\n    as well. \n\n## Compose runtime\n\nOne of the major benefits of using Compose through Molecule is how the framework turns reactive streams such as\n`Flow` and `StateFlow` into imperative code, which then becomes easier to understand, write and maintain.\nComposable functions have a lifecycle, they enter a composition (the presenter starts to be used) and leave\na composition (the presenter is no longer used). Properties can be made reactive and trigger creating a\nnew `Model` whenever they change.\n\n### Lifecycle\n\nThis example contains two child presenters:\n\n```kotlin\nclass OnboardingPresenterImpl(\n  private val lazyLoginPresenter: () -> LoginPresenter,\n  private val lazyRegistrationPresenter: () -> RegistrationPresenter,\n) : OnboardingPresenter {\n\n  @Composable\n  fun present(input: Unit): BaseModel {\n    ...\n    return if (mustRegister) {\n      val registrationPresenter = remember { lazyRegistrationPresenter() }\n      registrationPresenter.present(Unit)\n    } else {\n      val loginPresenter = remember { lazyLoginPresenter() }\n      loginPresenter.present(Unit)\n    }\n  }\n}\n```\n\nOn the first composition, when `OnboardingPresenterImpl.present()` is called for the first time, the lifecycle of\n`OnboardingPresenterImpl` starts. Let’s assume `mustRegister` is true, then `RegistrationPresenter` gets called\nand its lifecycle starts as well. In the example when `mustRegister` switches to false, then `RegistrationPresenter`\nleaves the composition and its lifecycle ends. `LoginPresenter` enters the composition and its lifecycle starts.\nIf the parent presenter of `OnboardingPresenterImpl` stops calling this presenter, then `OnboardingPresenterImpl`\nand `LoginPresenter` would leave composition and both of their lifecycles end.\n\n### State\n\n[Google’s guide](https://developer.android.com/develop/ui/compose/state) for state management is a good starting\npoint. APIs most often used are [`remember()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#remember(kotlin.Function0)),\n[`mutableStateOf()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#mutableStateOf(kotlin.Any,androidx.compose.runtime.SnapshotMutationPolicy)),\n[`collectAsState()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#(kotlinx.coroutines.flow.StateFlow).collectAsState(kotlin.coroutines.CoroutineContext))\nand [`produceState()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#produceState(kotlin.Any,kotlin.coroutines.SuspendFunction1)).\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  var toggled: Boolean by remember { mutableStateOf(false) }\n\n  return Model(\n    text = if (toggled) \"toggled\" else \"not toggled\",\n  ) {\n    when (it) {\n      is ToggleClicked -> toggled = !toggled\n    }\n  }\n}\n```\n\nIn this example, whenever the Presenter receives the `ToggleClicked` event, then the state `toggled` changes.\nThis triggers a recomposition in the Compose runtime and will call `present()` again to compute a new `Model`.\n\n`Flows` can easily be observed using `collectAsState()`:\n\n```kotlin hl_lines=\"10\"\ninterface AccountManager {\n  val currentAccount: StateFlow<Account>\n}\n\nclass AmazonLoginPresenter(\n  private val accountManager: AccountManager\n): LoginPresenter {\n  @Composable\n  fun present(input: Unit): Model {\n    val account: Account by accountManager.currentAccount.collectAsState()\n    ...\n  }\n}\n```\n\nWhenever the `currentAccount` Flow emits a new `Account`, then the Compose runtime will trigger a recomposition\nand a new `Model` will be computed.\n\n### Side effects\n\nIt’s recommended to read [Google’s guide](https://developer.android.com/jetpack/compose/side-effects). Since\ncomposable functions come with a lifecycle, async operations can safely be launched and get automatically torn\ndown when the `Presenter` leaves the composition. Commonly used APIs are\n[`LaunchedEffect()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#LaunchedEffect(kotlin.Any,kotlin.coroutines.SuspendFunction1)),\n[`DisposableEffect()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#DisposableEffect(kotlin.Any,kotlin.Function1))\nand [`rememberCoroutineScope()`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#rememberCoroutineScope(kotlin.Function0)).\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  LaunchedEffect(key) {\n    // This is within a CoroutineScope and suspending functions can\n    // be called:\n    flowOf(1, 2, 3).collect { ... }\n  }\n}\n```\n\nIf the `key` changes between compositions, then a new coroutine is launched and the previous one canceled. For more\ndetails see [here](https://developer.android.com/jetpack/compose/side-effects#launchedeffect).\n\nThis is an example for how one would use `rememberCoroutineScope()`:\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  val coroutineScope = rememberCoroutineScope()\n\n  return Model() {\n    when (it) {\n      is OnClick -> coroutineScope.launch { ... }\n    }\n  }\n}\n```\n\nWhen the `Presenter` leaves composition, then all jobs launched by this coroutine scope get canceled. For more\ndetails see [here](https://developer.android.com/jetpack/compose/side-effects#remembercoroutinescope).\n\n## Recipes\n\nThere are common scenarios you may encounter when using `Presenters`.\n\n!!! info\n\n    The recipes below are not part of the App Platform API and we look for feedback. The solutions are either\n    implemented in the Recipes or Sample app. Please let us know if these solutions work for you or which use cases\n    you're missing.\n\n    The [Recipes app](index.md#web-recipe-app) and [Sample app](index.md#web-clickable) can be tested in the browser.\n\n### Save `Presenter` state\n\n`Presenters` can make full use of the Compose runtime, e.g. using `remember { }` and `mutableStateOf()`. But when a\n`Presenter` leaves the composition and no longer is part of the hierarchy, then it loses its state and would be called\nwith the initial state the next time.\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  val showLogin = ...\n\n  val model = if (showLogin) {\n    loginPresenter.present(Unit)\n  } else {\n    registerPresenter.present(Unit)\n  }\n\n  return model\n}\n```\n\nTake this function for example. Every time `showLogin` is toggled then either `loginPresenter` or `registerPresenter`\nis called with their initial state. These presenters only remember their state, if `showLogin` doesn't change.\n\nThe Compose runtime provides `rememberSaveable { }` and `SaveableStateHolder` as solution to save and restore instance\nstate within a process or across process death. The Recipes app\n[ported `SaveableStateHolder`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/saveable/ReturningSaveableStateHolder.kt)\nto work for `@Composable` functions that must return a value. `Presenters` wrapped with a\n`ReturningSaveableStateHolder` can use `rememberSaveable { }` to restore state even after they weren't part of the\nhierarchy anymore:\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  val showLogin = ...\n    \n  val saveableStateHolder = rememberReturningSaveableStateHolder()\n\n  val presenter = if (showLogin) loginPresenter else registerPresenter\n\n  return saveableStateHolder.SaveableStateProvider(key = presenter) {\n    presenter.present(Unit)\n  }\n}\n```\n\nState wrapped in `rememberSaveable { }` in `LoginPresenter` and `RegisterPresenter` will be preserved no\nmatter how often `showLogin` is toggled.\n\n### `Presenter` backstack\n\nWith `Presenters` it's easy to implement model driven navigation. Which `Presenter` is shown on screen is part of the\nbusiness logic.\n\n```kotlin\n@Composable\nfun present(input: Unit): Model {\n  val showLogin = ...\n\n  val model = if (showLogin) {\n    loginPresenter.present(Unit)\n  } else {\n    registerPresenter.present(Unit)\n  }\n\n  return model\n}\n```\n\nThis pattern can be generalized:\n\n```kotlin\ninterface NavigationManager {\n  val currentPresenter: StateFlow<MoleculePresenter<Unit, BaseModel>>\n\n  fun navigateTo(presenter: MoleculePresenter<Unit, BaseModel>)\n}\n\n@Inject\nclass NavigationPresenter(val navigationManager: NavigationManager) : MoleculePresenter<Unit, BaseModel> {\n\n  @Compose\n  fun present(input: Unit): BaseModel {\n    val presenter by navigationManager.currentPresenter.collectAsState()\n    return presenter.present(Unit)\n  }\n}\n```\n\nThis solution always shows the `Presenter` for which `navigateTo()` was called last. This function can be called from\nanywhere in the app.\n\nAnother solution is a backstack of `Presenters`, where `Presenters` can be pushed to the stack and the top\nmost `Presenter` can be popped from the stack. The Recipes app\n[implemented this navigation pattern](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/PresenterBackstackScope.kt)\nwith an easy to use `presenterBackstack { }` function:\n\n```kotlin\nclass CrossSlideBackstackPresenter(\n  private val initialPresenter: MoleculePresenter<Unit, out BaseModel>\n) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    return presenterBackstack(initialPresenter) { model ->\n      // Pop the top presenter on a back press event.\n      BackHandlerPresenter(enabled = lastBackstackChange.value.backstack.size > 1) {\n        pop()\n      }\n\n      Model(delegate = model, backstackScope = this)\n    }\n  }\n}\n```\n\n`presenterBackstack { }` provides\n[PresenterBackstackScope](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/PresenterBackstackScope.kt),\nwhich allows you to `push()` and `pop()` presenters.\n[Child presenters](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/presenter/BackstackChildPresenter.kt#L38)\nwrapped in this function get access to this scope using a composition local:\n\n```kotlin\n@Composable\noverride fun present(input: Unit): Model {\n  val backstack = checkNotNull(LocalBackstackScope.current)\n  ...\n\n  return Model() {\n    when (it) {\n      Event.AddPresenterToBackstack -> backstack.push(BackstackChildPresenter())\n    }\n  }\n}\n```\n\n[`CrossSlideBackstackPresenter`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/CrossSlideBackstackPresenter.kt)\nfrom the Recipe app goes one step further and integrates the `BackHandlerPresenter { }` API to pop presenters from the\nstack when the back button is pressed. Its\n[`Renderer`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/CrossSlideBackstackRenderer.kt)\nimplements a slide animation whenever a presenter is pushed to the stack or popped from the stack.\n\n### `CompositionLocal`\n\nBoth the `BackHandlerPresenter { }` integration for back button presses and the backstack recipe for navigation leverage\n[Compose's `CompositionLocal` feature](https://developer.android.com/develop/ui/compose/compositionlocal#creating).\nThis is a powerful mechanism to provide state from a parent presenter to nested child presenters even deep down in\nthe stack without relying on the `Input` parameter of presenters or providing\ndependencies through the constructor. Another benefit is that `CompositionLocals` are embedded in the presenter tree\nand multiple instances can be provided for different parts of the tree or even be overridden, e.g. a parent presenter\nmay use a backstack, but then a child presenter may provide its own backstack for its child presenters.\n\nA common implementation may look like this:\n\n```kotlin\nclass YourType\n\npublic val LocalYourType: ProvidableCompositionLocal<YourType?> = compositionLocalOf { null }\n\nclass ParentPresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val yourType = remember { YourType() }\n\n    return returningCompositionLocalProvider(\n      LocalYourType provides yourType\n    ) {\n      // ... call child presenters\n    }\n  }\n}\n\nclass ChildPresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val yourType = checkNotNull(LocalYourType.current)\n    ...\n  }\n}\n```\n\nWhile `CompositionLocals` are powerful, their biggest downsides are unit tests. In a unit test for `ChildPresenter`\na value for `LocalYourType.current` must be provided, otherwise the call will throw an exception.\n\n### App Bar\n\nThe Recipes app implements an app bar for all its screens and allows child presenters to change the content.\n\nThere are multiple ways to implement the app bar and decompose the different screen elements. One way is using\n[Templates](template.md), where one slot in the template is reserved for the app bar model. A specific `Presenter`\ncould be responsible for providing this model:\n\n```kotlin\nsealed interface SampleAppTemplate : Template {\n\n  data class FullScreenTemplate(\n    val appBarModel: AppBarModel\n    val content: BaseModel,\n  ) : SampleAppTemplate\n}\n\nclass SampleAppTemplatePresenter(\n  private val appBarPresenter: AppBarPresenter,\n  private val rootPresenter: MoleculePresenter<Unit, BaseModel>,\n) : MoleculePresenter<Unit, SampleAppTemplate> {\n  @Composable\n  fun present(input: Unit): SampleAppTemplate {\n    val contentModel = rootPresenter.present(Unit)\n\n    return contentModel.toTemplate { model ->\n      val appBarModel = appBarPresenter.present(Unit)\n      FullScreenTemplate(appBarModel, contentModel)\n    }\n  }\n}\n```\n\nThe `SampleAppTemplateRenderer` has access to `appBarModel` from the `FullScreenTemplate` and can use the model\nto configure the app bar UI.\n\nThe Recipe app has chosen a different implementation, where any `BaseModel` class from a `Presenter` can implement the\nspecific [`AppBarConfigModel`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/appbar/AppBarConfigModel.kt)\ninterface, which provides the configuration for the app bar. Implementing this interface is optional:\n\n```kotlin\nclass MenuPresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    ...\n  }\n\n  data class Model(\n    private val menuItems: List<AppBarConfig.MenuItem>,\n  ) : BaseModel, AppBarConfigModel {\n    override fun appBarConfig(): AppBarConfig {\n      return AppBarConfig(title = \"Menu items\", menuItems = menuItems)\n    }\n  }\n}\n```\n\nIf a `BaseModel` implementing `AppBarConfigModel` bubbles all the way up to the\n[`RootPresenter`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/template/RootPresenter.kt),\nthen the `BaseModel` from the child `Presenter` will provide the config for the `Template` or otherwise the\n`RootPresenter` will provide a default:\n\n```kotlin\nreturn contentModel.toTemplate { model ->\n  val appBarConfig =\n    if (model is AppBarConfigModel) {\n      model.appBarConfig().copy(backArrowAction = backArrowAction)\n    } else {\n      AppBarConfig(title = AppBarConfig.DEFAULT.title, backArrowAction = backArrowAction)\n    }\n\n  RecipesAppTemplate.FullScreenTemplate(model, appBarConfig)\n}\n```\n\n### Navigation 3\n\nThe [Navigation 3 library](https://developer.android.com/guide/navigation/navigation-3) can be used with App Platform.\nFor idiomatic navigation App Platform [recommends](presenter.md#model-driven-navigation) handling navigation events in \n`Presenters`. `Presenters` are composable, build a tree and can delegate which `Presenter` is shown on screen to child \n`Presenters`. This is how App Platform implements a unidirectional dataflow. The downside of Navigation 3 is that \nit pushes navigation logic into the Compose UI layer, which is against App Platform's philosophy of handling navigation \nin the business logic. With the right integration strategy, this downside can be mitigated.\n\nThe Recipes app manages the backstack of `Presenters` in the parent \n[`Navigation3HomePresenter`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3HomePresenter.kt)\nand forwards the backstack and options to modify the stack to the `Renderer`. Note that the `Model` is computed for \neach `Presenter` in the backstack:\n\n```kotlin\n@Composable\noverride fun present(input: Unit): Model {\n  val backstack = remember {\n    mutableStateListOf<MoleculePresenter<Unit, out BaseModel>>().apply {\n      // There must be always one element.\n      add(Navigation3ChildPresenter(index = 0, backstack = this))\n    }\n  }\n\n  return Model(backstack = backstack.map { it.present(Unit) }) {\n    when (it) {\n      Event.Pop -> {\n        backstack.removeAt(backstack.size - 1)\n      }\n    }\n  }\n}\n```\n\nThe [`Renderer`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3HomeRenderer.kt) \nwraps the backstack in a `NavDisplay` and forwards back gestures to the `Presenter`. There is a unique `NavEntry`\nfor each position in the stack and the individual `Renderer` for each `Model` is invoked:\n\n```kotlin\n@Inject\n@ContributesRenderer\nclass Navigation3HomeRenderer(private val rendererFactory: RendererFactory) : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    // Use the position of the model in the backstack as key for `NavDisplay`. This way\n    // we can update models without Navigation 3 treating those changes as a new screen.\n    val backstack = model.backstack.mapIndexed { index, _ -> index }\n\n    NavDisplay(\n      backStack = backstack,\n      onBack = { model.onEvent(Event.Pop) },\n      entryProvider = { key ->\n        NavEntry(key) {\n          val model = model.backstack[it]\n          rendererFactory.getComposeRenderer(model).renderCompose(model)\n        }\n      },\n    )\n  }\n}\n```\n\nWith this integration handling of the backstack is managed in the `Presenter` and testable. \n\n??? info \"Alternative integration\"\n\n    If a unidirectional dataflow isn't required, an alternative integration is making each `NavEntry` a unique \n    `Presenter` root and compute the `Model` directly using the `Presenter`. For the reasons mentioned we don't \n    recommend this setup.\n\n    ```kotlin\n    data object List\n    data object Detail\n\n    @Inject\n    @ContributesRenderer\n    class Navigation3Renderer(\n      private val listPresenter: ListPresenter,\n      private val detailPresenter: DetailPresenter,\n      private val rendererFactory: RendererFactory,\n    ) : ComposeRenderer<Model>() {\n      @Composable\n      override fun Compose(model: Model) {\n        val backstack = remember { mutableStateListOf<Any>(List) }\n\n        NavDisplay(\n          backStack = backstack,\n          onBack = { backstack.removeAt(backstack.size - 1) },\n          entryProvider =\n            entryProvider {\n              entry<List> {\n                val model = listPresenter.present(Unit)\n                rendererFactory.getComposeRenderer(model).renderCompose(model)\n              }\n              entry<Detail> {\n                val model = detailPresenter.present(Unit)\n                rendererFactory.getComposeRenderer(model).renderCompose(model)\n              }\n            },\n        )\n      }\n    }\n    ```\n\n### SwiftUI\n\n#### `Presenters` and SwiftUI `Views`\n\nIn iOS it's possible to connect `Presenters` to SwiftUI `Views` so `Presenter` logic can be shared while keeping UI\nnative. The Recipes app demonstrates a [set of Swift APIs](https://github.com/amzn/app-platform/tree/main/recipes/recipesIosApp/recipesIosApp/PresenterViews)\nthat demonstrate how to launch a `Presenter` and render SwiftUI `Views` in the iOS flavor. Note that App Platform \ndoes not provide an API equivalent of SwiftUI `Renderers`. As such, we need to decide how to observe the flow of models \nfrom a given `Presenter` and create `Views` from them.\n\nTo obtain an observable stream of models, `Presenter` can be extended to provide an `AsyncThrowingStream` from the \nmodel `StateFlow`. It's also possible to implement a convenient extension of `Flow` so we can convert any `Flow` to an\n`AsyncThrowingStream`.\n\n```swift\nextension Presenter {\n    func viewModels<Model>(ofType type: Model.Type) -> AsyncThrowingStream<Model, Error> {\n        model\n            .values()\n            .compactMap { $0 as? Model }\n            .asAsyncThrowingStream()\n    }\n}\n\nextension Kotlinx_coroutines_coreFlow {\n    /// The Flows send Any, so we lose type information and need to cast at runtime instead of getting a type-safe compile time check.\n    func values() -> AsyncThrowingStream<Any?, Error> {\n        let collector = Kotlinx_coroutines_coreFlowCollectorImpl<Any?>()\n        collect(collector: collector, completionHandler: collector.onComplete(_:))\n        return collector.values\n    }\n}\n```\n\nGiven a `Model` there are multiple ways to implement association with some SwiftUI `View`. The Recipes app chooses to\ncreate a [`protocol`](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols/) for\nview creation and extend `BaseModel` to create views under the requirement of its conformance:\n\n```swift\nprotocol PresenterViewModel {\n    associatedtype Renderer : View\n    @ViewBuilder @MainActor func makeViewRenderer() -> Self.Renderer\n}\n\nextension BaseModel {\n    @MainActor func getViewRenderer() -> AnyView {\n        guard let viewModel = self as? (any PresenterViewModel) else {\n            assertionFailure(\"ViewModel \\(self) does not conform to `PresenterViewModel`\")\n\n            // This is an implementation detail. If crashing is preferred even in production builds, `fatalError(..)`\n            // can be used instead\n            return AnyView(Text(\"Error, some ViewModel was not implemented!\"))\n        }\n\n        return AnyView(viewModel.makeViewRenderer())\n    }\n}\n```\n\n??? info \"Alternate implementation\"\n\n    We can also create a `View` registry:\n    \n    ```swift\n    public class PresenterViewRegistry {\n        @MainActor private var registry: [ObjectIdentifier: (Any) -> AnyView] = [:]\n    \n        public init(registry: [ObjectIdentifier : (Any) -> AnyView] = [:]) {\n            self.registry = registry\n        }\n    \n        public static var shared: PresenterViewRegistry = PresenterViewRegistry()\n    }\n    \n    @MainActor public extension PresenterViewRegistry {\n        func registerViewForModelType<Model, Content: View>(_ type: Model.Type, makeView: @escaping (Model) -> Content) {\n            let typeID = ObjectIdentifier(Model.self)\n            registry[typeID] = { model in\n                AnyView(makeView(model as! Model))\n            }\n        }\n    \n        func makeViewForModel<Model>(_ model: Model) -> some View {\n            let type = type(of: model as Any)\n            let typeID = ObjectIdentifier(type)\n            if let makeView = registry[typeID] {\n                return makeView(model)\n            }\n            fatalError(\"Could not find view builder for \\(type). Add it to the registry.\")\n        }\n    }\n    ```\n    \n    The registry can be stored in an `Environment` property wrapper. This is similar to how `@ContributesRenderer` works \n    under the hood, though without an equivalent App Platform API the heavy lifting on registration and registry \n    lifecycle management falls to consumers. Due to these reasons we generally recommend to use the protocol setup.\n\n#### Navigation with `Presenters` and SwiftUI\n\nSwiftUI provides [navigation containers](https://developer.apple.com/documentation/swiftui/navigation) to enable\nmovement between different part of an app's view hierarchy. Similar to `Navigation 3`, SwiftUI's navigation containers \npush navigation logic to the UI layer, which is against App Platform's philosophy of handling navigation in business\nlogic. However, to support navigation with SwiftUI `Views` and `Presenters`, it is recommended to integrate with \nSwiftUI's navigation offerings. This SwiftUI keeps the determination of some completed back gesture an implementation\ndetail, and we want ensure that all back events are handled appropriately and the user experience feels truly native.\n\n!!! note\n\n    We provide a recipe for integration with `NavigationStack` for single column navigation based on back gesture. For \n    other kinds of navigation with `NavigationSplitView` or `NavigationLink` it is possible to integrate following our\n    [model driven navigation](https://amzn.github.io/app-platform/presenter/#model-driven-navigation) pattern. However,\n    we don't provide an explicit recipe for it. If you're missing some use cases here, please let us know.\n\nThe Recipes app demonstrates how SwiftUI navigation APIs can be used while following App Platform's philosophy of \nunidirectional data flow. As navigation is a part of business logic, the recipe [implements navigation with \na backstack of `Presenters`](https://github.com/amzn/app-platform/blob/main/recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/swiftui/SwiftUiHomePresenter.kt).\nThe root `Presenter` responsible for the `Presenter` backstack computes the `Model` backstack:\n\n```kotlin\n@Composable\n  override fun present(input: Unit): Model {\n    val backstack = remember {\n      mutableStateListOf<MoleculePresenter<Unit, out BaseModel>>().apply {\n        // There must be always one element.\n        add(SwiftUiChildPresenter(index = 0, backstack = this))\n      }\n    }\n\n    return Model(modelBackstack = backstack.map { it.present(Unit) }) {\n      when (it) {\n        is Event.BackstackModificationEvent -> {\n          val updatedBackstack = it.indicesBackstack.map { index -> backstack[index] }\n\n          backstack.clear()\n          backstack.addAll(updatedBackstack)\n        }\n      }\n    }\n  }\n```\nThe `Presenter` forwards the `Models` and event callbacks to a SwiftUI `View`, which\nintegrates these models with a [`NavigationStack`](https://github.com/amzn/app-platform/blob/main/recipes/recipesIosApp/recipesIosApp/SwiftUI/SwiftUiHomePresenterView.swift).\nNote that to integrate we create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) that is passed\nin to the `NavigationStack`. The `Binding's` value type must conform to `Hashable` and by default `BaseModel` does not\nconform. To resolve this in the recipe we simply represent each `Model` by the index of its position in the `Model`\nbackstack as we do not require more complex identifiers.\n\n```swift\nextension SwiftUiHomePresenter.Model {\n    func pathBinding() -> Binding<[Int]> {\n        .init {\n            // drop the first value of the backstack from the path because that should be the root view\n            Array(self.modelBackstack.indices.dropFirst())\n        } set: { modifiedIndices in\n\n            // the resulting backstack indices the presenter should compute on is the first index (0) that was\n            // dropped as well as the remaining indices post modification\n            let indicesBackstack = [0] + modifiedIndices.map { $0.toKotlinInt() }\n\n            self.onEvent(\n                SwiftUiHomePresenterEventBackstackModificationEvent (\n                    indicesBackstack: indicesBackstack\n                )\n            )\n        }\n    }\n}\n\nprivate struct NavigationStackView: View {\n    var backstack: [BaseModel]\n    var model: SwiftUiHomePresenter.Model\n\n    init(model: SwiftUiHomePresenter.Model) {\n        self.backstack = model.modelBackstack\n        self.model = model\n    }\n\n    var body: some View {\n        NavigationStack(path: model.pathBinding()) {\n            backstack[0].getViewRenderer()\n                .navigationDestination(for: Int.self) { index in\n                    backstack[index].getViewRenderer()\n                }\n        }\n    }\n}\n```\n"
  },
  {
    "path": "docs/renderer.md",
    "content": "# Renderer\n\n!!! note\n\n    App Platform has a generic `Renderer` interface that can be used for multiple UI layer implementations.\n    Compose Multiplatform and Android Views are stable and supported out of the box. However, Compose Multiplatform is\n    an opt-in feature through the Gradle DSL and must be explicitly enabled. The default value is `false`.\n\n    ```groovy\n    appPlatform {\n      enableComposeUi true\n    }\n    ```\n\n## Renderer basics\n\nA [`Renderer`](https://github.com/amzn/app-platform/blob/main/renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/Renderer.kt)\nis the counterpart to a `Presenter`. It consumes `Models` and turns them into UI, which is shown on screen.\n\n```kotlin\ninterface Renderer<in ModelT : BaseModel> {\n  fun render(model: ModelT)\n}\n```\n\nThe `Renderer` interface is rarely used directly, instead platform specific implementations like\n[`ComposeRenderer`](https://github.com/amzn/app-platform/blob/main/renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/ComposeRenderer.kt)\nfor [Compose Multiplatform](https://www.jetbrains.com/compose-multiplatform/) and\n[`ViewRenderer`](https://github.com/amzn/app-platform/blob/main/renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ViewRenderer.kt)\nfor Android are used. App Platform doesn’t provide any other implementations for now, e.g. a SwiftUI or UIKit\nimplementation for iOS is missing.\n\n```kotlin title=\"ComposeRenderer\"\n@ContributesRenderer\nclass LoginRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    if (model.loginInProgress) {\n      CircularProgressIndicator()\n    } else {\n      Text(\"Login\")\n    }\n  }\n}\n```\n\n```kotlin title=\"ViewRenderer\"\n@ContributesRenderer\nclass LoginRenderer : ViewRenderer<Model>() {\n    private lateinit var textView: TextView\n\n    override fun inflate(\n        activity: Activity,\n        parent: ViewGroup,\n        layoutInflater: LayoutInflater,\n        initialModel: Model,\n    ): View {\n        return TextView(activity).also { textView = it }\n    }\n\n    override fun renderModel(model: Model) {\n        textView.text = \"Login\"\n    }\n}\n```\n\n!!! warning\n\n    Note that `ComposeRenderer` like `ViewRenderer` implements the common `Renderer` interface, but calling the\n    `render(model)` function [is an error](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/ComposeRenderer.kt#L52-L58).\n    Instead, `ComposeRenderer` defines its own function to preserve the composable context:\n\n    ```kotlin\n    @Composable\n    fun renderCompose(model: ModelT)\n    ```\n\n    In practice this is less of a concern, because the `render(model)` function is deprecated and hidden and callers\n    only see the `renderCompose(model)` function.\n\nRenderers are composable and can build hierarchies similar to `Presenters`. The parent renderer is responsible for\ncalling `render()` on the child renderer:\n\n```kotlin\ndata class ParentModel(\n  val childModel: ChildModel\n): BaseModel\n\nclass ParentRenderer(\n  private val childRenderer: ChildRenderer\n): Renderer<ParentModel> {\n  override fun render(model: ParentModel) {\n    childRenderer.render(model.childModel)\n  }\n}\n```\n\n!!! note\n\n    Injecting concrete child `Renderers` is possible, but less common. More frequently `RendererFactory` is injected\n    to obtain a `Renderer` instance for a `Model`.\n\nA `Renderer` sends events back to the `Presenter` through the `onEvent` lambda on a Model.\n\n```kotlin hl_lines=\"6\"\n@ContributesRenderer\nclass LoginRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Button(\n      onClick = { model.onEvent(LoginPresenter.Event.Login(\"Demo\")) },\n    ) {\n      Text(\"Login\")\n    }\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The sample app implements multiple `ComposeRenderers`, e.g. [`LoginRenderer`](https://github.com/amzn/app-platform/blob/main/sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRenderer.kt),\n    [`UserPageListRenderer`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListRenderer.kt)\n    and [`UserPageDetailRenderer`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailRenderer.kt).\n\n## `RendererFactory`\n\nHow `Renderers` are initialized depends on [`RendererFactory`](https://github.com/amzn/app-platform/blob/main/renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/RendererFactory.kt),\nwhich only responsibility is to create and cache `Renderers` based on the given model. App Platform comes with three\ndifferent implementations:\n\n[`ComposeRendererFactory`](https://github.com/amzn/app-platform/blob/main/renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/ComposeRendererFactory.kt)\n\n:   `ComposeRendererFactory` is an implementation for Compose Multiplatform and can be used on all supported\n    platforms. It can only create instances of `ComposeRenderer`.\n\n[`AndroidRendererFactory`](https://github.com/amzn/app-platform/blob/main/renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/AndroidRendererFactory.kt)\n\n:   `AndroidRendererFactory` is only suitable for Android. It can be used to create `ViewRenderer` instances and its\n    subtypes. It does not support `ComposeRenderer`. Use `ComposeAndroidRendererFactory` if you need to mix and\n    match `ViewRenderer` with `ComposeRenderer`.\n\n[`ComposeAndroidRendererFactory`](https://github.com/amzn/app-platform/blob/main/renderer-compose-multiplatform/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ComposeAndroidRendererFactory.kt)\n\n:   `ComposeAndroidRendererFactory` is only suitable for Android when using `ComposeRenderer` together with\n    `ViewRenderer`. The factory wraps the Renderers for seamless interop.\n\n### `@ContributesRenderer`\n\nAll factory implementations rely on Metro or `kotlin-inject-anvil` to discover and initialize\nrenderers. When the factory is created, it builds the generated renderer graph or component, whose\nparent is the app graph or component. That generated type lazily provides all renderers using the\nmultibindings feature. To participate in the lookup, renderers must tell Metro or\n`kotlin-inject-anvil` which models they can render. This is done through a generated graph or\ncomponent interface, which is automatically added to the renderer scope by using the\n[`@ContributesRenderer` annotation](https://github.com/amzn/app-platform/blob/main/kotlin-inject-extensions/contribute/public/src/commonMain/kotlin/software/amazon/app/platform/inject/ContributesRenderer.kt).\n\nWhich `Model` type is used for the binding is determined based on the super type. In the following example\n`LoginPresenter.Model` is used.\n\n```kotlin\n@ContributesRenderer\nclass LoginRenderer : ComposeRenderer<LoginPresenter.Model>()\n```\n\n??? info \"Generated code\"\n\n    === \"Metro\"\n    \n        The `@ContributesRenderer` annotation generates following code.\n    \n        ```kotlin\n        @ContributesTo(RendererScope::class)\n        interface LoginRendererGraph {\n          @Provides\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRenderer(): LoginRenderer = LoginRenderer()\n    \n          @Provides\n          @IntoMap\n          @RendererKey(LoginPresenter.Model::class)\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRendererLoginPresenterModel(renderer: Provider<LoginRenderer>): Renderer<*> = renderer()\n    \n          @Provides\n          @IntoMap\n          @ForScope(scope = RendererScope::class)\n          @RendererKey(LoginPresenter.Model::class)\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRendererLoginPresenterModelKey(): KClass<out Renderer<*>> = LoginRenderer::class\n        }\n        ```\n\n    === \"kotlin-inject-anvil\"\n\n        The `@ContributesRenderer` annotation generates following code.\n\n        ```kotlin\n        @ContributesTo(RendererScope::class)\n        interface LoginRendererComponent {\n          @Provides\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRenderer(): LoginRenderer = LoginRenderer()\n\n          @Provides\n          @IntoMap\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRendererLoginPresenterModel(renderer: () -> LoginRenderer): Pair<KClass<out BaseModel>, () -> Renderer<*>> = LoginPresenter.Model::class to renderer\n\n          @Provides\n          @IntoMap\n          @ForScope(scope = RendererScope::class)\n          public fun provideSoftwareAmazonAppPlatformSampleLoginLoginRendererLoginPresenterModelKey(): Pair<KClass<out BaseModel>, KClass<out Renderer<*>>> = LoginPresenter.Model::class to LoginRenderer::class\n        }\n        ```\n\n\n### Creating `RendererFactory`\n\nThe `RendererFactory` should be created and cached in the platform specific UI context, e.g. an iOS `UIViewController`\nor Android `Activity`.\n\n```kotlin title=\"iOS Compose Multiplatform\"\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    // Only a single factory is needed.\n    val rendererFactory = remember { ComposeRendererFactory(rootScopeProvider) }\n    ...\n  }\n```\n\n```kotlin title=\"Android Activity\"\nclass MainActivity : ComponentActivity() {\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_main)\n\n    val rendererFactory =\n      ComposeAndroidRendererFactory(\n        rootScopeProvider = application as RootScopeProvider,\n        activity = this,\n        parent = findViewById(R.id.main_container),\n      )\n    ...\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The sample app uses `ComposeAndroidRendererFactory` in [Android application](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivity.kt#L30-L35)\n    and `ComposeRendererFactory` for [iOS](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt#L40)\n    and [Desktop](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt#L36).\n\n### Creating `Renderers`\n\nBased on a `Model` instance or `Model` type a `RendererFactory` can create a new `Renderer` instance. The\n`getRenderer()` function creates a `Renderer` only once and caches the instance after that. This makes the caller side\nsimpler. Whenever a new `Model` is available get the `Renderer` for the `Model` and render the content on screen.\n\n```kotlin title=\"iOS Compose Multiplatform\"\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    // Only a single factory is needed.\n    val rendererFactory = remember { ComposeRendererFactory(rootScopeProvider) }\n\n    val model = presenter.present(Unit)\n\n    val renderer = factory.getComposeRenderer(model)\n    renderer.renderCompose(model)\n  }\n```\n\n!!! note\n\n    Note that `getRenderer()` for `ComposeRendererFactory` returns a `ComposeRenderer`. For a `ComposeRenderer` the\n    `renderCompose(model)` function must be called and not `render(model)`.\n\n```kotlin title=\"Android Activity\"\nclass MainActivity : ComponentActivity() {\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.activity_main)\n\n    val rendererFactory = ComposeAndroidRendererFactory(...)\n    val models: StateFlow<Model> = ...\n    ...\n\n    lifecycleScope.launch {\n      repeatOnLifecycle(Lifecycle.State.STARTED) {\n        models.collect { model ->\n          val renderer = rendererFactory.getRenderer(model)\n          renderer.render(model)\n        }\n      }\n    }\n  }\n}\n```\n\n### Injecting `RendererFactory`\n\nThe `RendererFactory` is provided in the `RendererComponent`, meaning it can be injected by any `Renderer`. This\nallows you to create child renderers without knowing the concrete type of the model and injecting the child\nrenderers ahead of time:\n\n```kotlin\n@Inject\n@ContributesRenderer\nclass SampleRenderer(\n  private val rendererFactory: RendererFactory\n) : ComposeRenderer<Model>() {\n\n  @Composable\n  override fun Compose(model: Model) {\n    val childRenderer = rendererFactory.getComposeRenderer(model.childModel)\n    childRenderer.renderCompose(model.childModel)\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The sample app injects `RendererFactory` in [`ComposeSampleAppTemplateRenderer`](https://github.com/amzn/app-platform/blob/main/sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt)\n    to create `Renderers` dynamically for unknown `Model` types. There is also an [Android sample implementation](https://github.com/amzn/app-platform/blob/main/sample/templates/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/template/AndroidSampleAppTemplateRenderer.kt).\n\n!!! note\n\n    Whenever a `Renderer` has an injected constructor parameter like `rendererFactory` in the sample above, then\n    the class must be annotated with `@Inject` in addition to `@ContributesRenderer`.\n\n## Android support\n\nAndroid Views are supported out of the box using `ViewRenderer`.\n\n### Compose interop\n\nIf an Android app uses only Compose UI with `ComposeRenderer`, then it can use `ComposeRendererFactory` similar to\niOS and Desktop to create `ComposeRenderer` instances. However, if interop with Android Views is needed, then\n`ComposeAndroidRendererFactory` must be used. `ComposeAndroidRendererFactory` makes it transparent which `Renderer`\nimplementation is used and interop is seamless. A `ComposeRenderer` that has a child `ViewRenderer` wraps the Android\nview within a `AndroidView` composable function call. A `ViewRenderer` that has a child `ComposeRenderer` wraps the\nCompose UI within a `ComposeView` Android View.\n\n```kotlin\nval rendererFactory = ComposeAndroidRendererFactory(...)\n\nval renderer = rendererFactory.getRenderer(model)\nrender.render(model)\n```\n\nIn this example the returned `Renderer` can be a `ComposeRenderer` or `ViewRenderer`, it would not matter and either\nthe Compose UI or Android Views would be rendered on screen. With the seamless interop it becomes easier to migrate\nfrom Android Views to Compose UI by simply migrating renderers one by one.\n\n### `ViewRenderer` subtypes\n\n[`ViewBindingRenderer`](https://github.com/amzn/app-platform/blob/main/renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ViewBindingRenderer.kt).\n\n:   View binding is supported out of the box using `ViewBindingRenderer`.\n\n[`RecyclerViewViewHolderRenderer`](https://github.com/amzn/app-platform/blob/main/renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/RecyclerViewViewHolderRenderer.kt)\n\n:   `RecyclerViewViewHolderRenderer` allows you to implement elements of a `RecyclerView` as a `Renderer`.\n\n## Unit tests\n\n`ComposeRenderer` can easily be tested as unit tests on Desktop and iOS. In particular tests for Desktop are helpful\ndue to the fast build times. Various fake `Models` can be passed to the `Renderer` and the UI state based on the\nmodel verified.\n\nTesting `ComposeRenderer` or `ViewRenderer` for Android requires an Android device or emulator.\n\nThis test runs as a unit test on iOS and Desktop.\n\n```kotlin\nclass LoginRendererTest {\n\n  @Test\n  fun `the login button is rendered when not logging in`() {\n    runComposeUiTest {\n      setContent {\n        val renderer = LoginRenderer()\n        renderer.renderCompose(LoginPresenter.Model(loginInProgress = false) {})\n      }\n\n      onNodeWithTag(\"loginProgress\").assertDoesNotExist()\n      onNodeWithTag(\"loginButton\").assertIsDisplayed()\n    }\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The sample app demonstrates this with the [`LoginRendererTest`](https://github.com/amzn/app-platform/blob/main/sample/login/impl/src/appleAndDesktopTest/kotlin/software/amazon/app/platform/sample/login/LoginRendererTest.kt).\n    To avoid duplicating the test in the `desktopTest` and `iosTest` source folders, the sample app has a custom\n    source set `appleAndDesktop`, which is a shared parent source set for `apple` and `desktop`.\n"
  },
  {
    "path": "docs/scope.md",
    "content": "# Scope\n\n!!! note\n\n    Importing the `Scopes` API is an opt-in feature through the Gradle DSL. The default value is `false`.\n    ```groovy\n    appPlatform {\n      addPublicModuleDependencies true\n    }\n    ```\n\n## Overview\n\nScopes define the boundary our software components operate in. A scope is a space with a well-defined lifecycle\nthat can be created and torn down. Scopes host other objects and can bind them to their lifecycle. Sub-scopes\nor child scopes have the same or a shorter lifecycle as their parent scope.\n\nA leak happens when one scope references another scope with a different lifecycle, e.g. a background thread,\nwhich is started and finishes after a certain amount of time, references an Android `Activity` that is being\ndestroyed while the thread is still running. In this case the thread with the longer lifecycle leaks the\n`Activity` with the shorter lifecycle. Another example is a singleton object, which lives as long as the\napplication process runs, keeping a strong reference to a user object, which should be released after the\nuser session expires.\n\nRelying purely on platform specific scopes is problematic, because these scopes are out of our control.\nWhen the platform decides to destroy one of its scopes, then we need to adjust and tear down our operations.\nThis doesn’t always align with our use cases, e.g. we might want to finish uploading data in the background\nafter the platform scope such as an `Activity` has been destroyed. Further, the platform scopes may not align\nwith how we'd represent logical scopes for our apps, e.g. they often lack a user scope. This forces us to\npush objects and lifecycles into the application scope and this could cause data to leak across sessions and\ntrigger out of memory scenarios.\n\nWe need to be in charge of our own scopes. In simple terms this means having an object that can be created and\ndestroyed.\n\nThe App Platform provides the\n[Scope](https://github.com/amzn/app-platform/blob/main/scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/Scope.kt)\ninterface to implement this concept.\n\n```kotlin title=\"Scope.kt\"\ninterface Scope {\n\n  val name: String\n  val parent: Scope?\n\n  fun buildChild(name: String, builder: (Builder.() -> Unit)? = null): Scope\n  fun children(): Set<Scope>\n\n  fun isDestroyed(): Boolean\n  fun destroy()\n\n  fun register(scoped: Scoped)\n  fun <T : Any> getService(key: String): T?\n}\n```\n\n## Creating a `Scope`\n\nA `Scope` is created through the builder function. The\n[Builder](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/Scope.kt#L57)\nallows you to add services before the Scope is finalized:\n\n```kotlin\nval rootScope = Scope.buildRootScope {\n  addService(\"key\", service)\n}\n```\n\nChild scopes are created using the parent:\n\n```kotlin\nrootScope.buildChild(\"user scope\") {\n  addService(\"child-service\", childService)\n}\n```\n\n??? example \"Sample\"\n\n    The root scope is usually created when the application is launched. The sample application creates its\n    root scope [here](https://github.com/amzn/app-platform/blob/main/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt).\n    This `Scope` is never destroyed and stays alive for the entire app lifetime.\n\n    The sample application has a child scope for the logged in user. This `Scope` is created during\n    [login](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt#L47-L52)\n    and [destroyed](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt#L68)\n    during logout.\n\n    ```kotlin\n    override fun login(userId: Long) {\n      ...\n      val userComponent = userComponentFactory.createUserComponent(user)\n\n      val userScope =\n        rootScopeProvider.rootScope.buildChild(\"user-$userId\") {\n          addKotlinInjectComponent(userComponent)\n          addCoroutineScopeScoped(userComponent.userScopeCoroutineScopeScoped)\n        }\n\n      ...\n\n      userScope.register(userComponent.userScopedInstances)\n    }\n\n    override fun logout() {\n      val currentUserScope = user.value?.scope\n      ...\n      currentUserScope?.destroy()\n    }\n    ```\n\nTests usually leverage the test scope, which comes with better defaults for services such as the coroutine scope:\n\n```kotlin\n@Test\nfun `my test`() = runTest {\n  val scope = Scope.buildTestScope(this)\n}\n\n// Or\n@Test\nfun `my test`() = runTestWithScope { scope ->\n  // `scope` is equivalent to calling `Scope.buildTestScope(this)`.\n}\n```\n\n??? example \"Sample\"\n\n    Classes implementing the `Scoped` interface usually make use of the `runTestWithScope` function in their tests.\n    Notice in [this sample](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/SessionTimeoutTest.kt#L36-L48)\n    how `SessionTimeout`, which implements the `Scoped` interface, is registered in the `Scope`.\n\n    ```kotlin hl_lines=\"7\"\n    @Test\n    fun `on timeout the user is logged out`() = runTestWithScope { scope ->\n      val userManager = FakeUserManager()\n      userManager.login(1L)\n\n      val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n      scope.register(sessionTimeout)\n\n      assertThat(userManager.user.value).isNotNull()\n\n      advanceTimeBy(SessionTimeout.initialTimeout + 1.milliseconds)\n      assertThat(userManager.user.value).isNull()\n    }\n    ```\n\n## Services\n\nA scope can host other objects like an object graph from dependency injection frameworks and a coroutine scope.\nThe latter is especially helpful, because the coroutine scope can be canceled when our logical scope is destroyed\nand all pending operations are torn down. Connecting our scopes with the dependency injection components makes\nour dependency injection setup more flexible, because we’re in charge of instantiating components and can provide\nextra objects like a user ID to the object graph. When a scope is destroyed we release the dependency injection\ncomponent and the memory can be reclaimed by the runtime. DI components and subcomponents form a tree, therefore\nsubcomponents can inject all types that are provided by parent components. The strong recommendation is to align\nthe component tree with the scope hierarchy.\n\nWhile a service can be obtained through the `getService()` function, a more frequent pattern is to rely on\nextension functions for stronger types. Similarly, an extension function on the `Builder` allows us to add a service\nto a `Scope`.\n\n```kotlin\ninterface MyService\n\nprivate const val MY_SERVICE_KEY = \"myService\"\n\nfun Scope.Builder.addMyService(service: MyService) {\n  addService(MY_SERVICE_KEY, service)\n}\n\nfun Scope.myService(): MyService {\n  return checkNotNull(getService<MyService>(MY_SERVICE_KEY))\n}\n```\n\nThe App Platform comes with a coroutine scope service and an integration for\n[Metro](https://zacsweers.github.io/metro) and\n[kotlin-inject-anvil](https://github.com/amzn/kotlin-inject-anvil) as dependency injection\nframeworks. Metro is the recommended default.\n\n```kotlin\nval rootScope = Scope.buildRootScope {\n  addCoroutineScopeScoped(coroutineScope)\n  addMetroDependencyGraph(metroDependencyGraph)\n  addKotlinInjectComponent(kotlinInjectComponent)\n}\n\n// Obtain service.\nrootScope.coroutineScope()\nrootScope.metroDependencyGraph<DefGraph>()\nrootScope.kotlinInjectComponent<AbcComponent>()\n```\n\n!!! warning\n\n    `Scopes` through their service mechanism implement the service locator pattern. With the provided dependency\n    injection framework usually it’s not needed to add custom services and it’s better to rely on dependency\n    injection instead.\n\n### `CoroutineScope`\n\n!!! info\n\n    By default, the IO dispatcher is used for all launched jobs for the provided `CoroutineScope`.\n\n    In tests when using `Scope.buildTestScope()` or `runTestWithScope` the `backgroundScope` is from the `TestScope`\n    is used by default and added to `Scope` instance.\n\nIt's strongly recommended to add a `CoroutineScope` to each `Scope`. App Platform provides a `CoroutineScope`\n[by default for the `AppScope`](https://github.com/amzn/app-platform/blob/main/kotlin-inject/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent.kt).\nIt is important to register this `CoroutineScope` in the created app `Scope` instance in order to cancel the\n`CoroutineScope` in case the `AppScope` ever gets destroyed. The same applies to any child scope.\n\n=== \"Metro\"\n\n    ```kotlin\n    @DependencyGraph(AppScope::class)\n    interface AppGraph {\n      /** The coroutine scope that runs as long as the app scope is alive. */\n      @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped // (1)!\n    }\n    \n    fun createAppScope(appGraph: AppGraph): Scope {\n      return Scope.buildRootScope {\n        addMetroDependencyGraph(appGraph)\n        addCoroutineScopeScoped(appGraph.appScopeCoroutineScopeScoped)\n      }\n    }\n    ```\n\n    1.  `CoroutineScopeScoped` wraps a `CoroutineScope` in a `Scoped` instance. In `onExitScope()` of this instance the\n        `CoroutineScope` will be canceled.\n\n=== \"kotlin-inject-anvil\"\n\n    ```kotlin\n    @SingleIn(AppScope::class)\n    @MergeComponent(AppScope::class)\n    interface AppComponent {\n      /** The coroutine scope that runs as long as the app scope is alive. */\n      @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped // (1)!\n    }\n    \n    fun createAppScope(appComponent: AppComponent): Scope {\n      return Scope.buildRootScope {\n        addKotlinInjectComponent(appComponent)\n        addCoroutineScopeScoped(appComponent.appScopeCoroutineScopeScoped)\n      }\n    }\n    ```\n\n    1.  `CoroutineScopeScoped` wraps a `CoroutineScope` in a `Scoped` instance. In `onExitScope()` of this instance the\n        `CoroutineScope` will be canceled.\n\n\nThe `CoroutineScope` can be injected in classes and used to launch async work. A common pattern is to use the\n`onEnterScope()` function when implementing the `Scoped` interface to launch coroutine jobs:\n\n```kotlin\noverride fun onEnterScope(scope: Scope) {\n  // This job will be automatically canceled when the `scope` gets destroyed.\n  scope.launch { // (1)!\n    someFlow.collect {\n      ...\n    }\n  }\n}\n```\n\n1.  `scope.launch` is a convenience function for `scope.coroutineScope().launch`.\n\nSince the `CoroutineScope` is part of the Metro or `kotlin-inject-anvil` object graph, the `CoroutineScope` can be\ninjected in the constructor as well:\n\n```kotlin\n@Inject\n@SingleIn(AppScope::class)\nclass MyClass(@ForScope(AppScope::class) coroutineScope: CoroutineScope) {\n  init {\n    coroutineScope.launch {\n      ...\n    }\n  }\n}\n```\n\nWhenever a `CoroutineScope` is injected, a new child `CoroutineScope` with its own `Job` is created (the parent `Job`\npoints to the shared `CoroutineScope` `Job`). The prevents consumers from accidentally tearing down all running\ncoroutines when canceling an injected `CoroutineScope`.\n\n```kotlin\noverride fun onEnterScope(scope: Scope) {\n  val myCoroutineScope = scope.coroutineScope()\n\n  myCoroutineScope.launch { ... }\n  myCoroutineScope.launch { ... }\n\n  // This is safe to do and only cancels the two launched jobs and `myCoroutineScope`. It doesn't cancel the\n  // shared `CoroutineScope` hosted within the `scope` object.\n  myCoroutineScope.cancel()\n}\n```\n\n## `Scoped`\n\nService objects can tie themselves to the lifecycle of a scope by implementing the\n[`Scoped`](https://github.com/amzn/app-platform/blob/main/scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/Scoped.kt)\ninterface:\n\n```kotlin\ninterface Scoped {\n    fun onEnterScope(scope: Scope)\n    fun onExitScope()\n}\n```\n\nUsually, we rely on our dependency injection framework to instantiate all `Scoped` instances for a scope. By doing\nso service objects will be automatically created when their corresponding scope is created and receive a callback\nwhen their scope is destroyed. This helps with loose coupling between our service objects. Implementing the `Scoped`\ninterface is a detail, which doesn’t need to be exposed to the API layer:\n\n```kotlin hl_lines=\"5 6 7\"\ninterface LocationProvider {\n  val location: StateFlow<Location>\n}\n\nclass AndroidLocationProvider(\n  private val locationManager: LocationManager\n) : LocationProvider, Scoped {\n\n  private val _location = MutableStateFlow<Location>()\n  override val location get() = _location\n\n  override fun onEnterScope(scope: Scope) {\n    scope.launch {\n      // Observe location updates through LocationManager\n\n      val androidLocation = ...\n      _location.value = androidLocation\n    }\n  }\n}\n```\n\n!!! note\n\n    Note in the example that the concrete implementation class implements the `Scoped` interface and\n    not `LocationProvider`. Being lifecycle aware is an implementation detail.\n\nHow the `Scoped` object is instantiated depends on the dependency injection framework and which scope to use.\nWith Metro, or alternatively `kotlin-inject-anvil`, for the app scope it would be:\n\n=== \"Metro\"\n\n    ```kotlin\n    @Inject // (1)!\n    @SingleIn(AppScope::class) // (2)!\n    @ContributesScoped(AppScope::class) //(3)!\n    class AndroidLocationProvider(\n      ...\n    ) : LocationProvider, Scoped {\n      ...\n    }\n    ```\n\n    1.  This annotation is required to support constructor injection.\n    2.  This annotation ensures that there is only ever a single instance of `AndroidLocationProvider` in the `AppScope`.\n    3.  This annotation ensures that when somebody injects `LocationProvider`, then they get the singleton instance of `AndroidLocationProvider`.\n\n    ??? note \"`@ContributesScoped` will generate and contribute bindings\"\n\n        The `@ContributesScoped` annotation will generate a graph interface with bindings for `LocationProvider`\n        and `Scoped`. The generated interface will be added automatically to the `AppScope`. No further manual step\n        is needed.\n\n        ```kotlin\n        @Binds\n        val AndroidLocationProvider.binds: LocationProvider\n\n        @Binds @IntoSet @ForScope(AppScope::class)\n        val AndroidLocationProvider.bindsScoped: Scoped\n        ```\n\n=== \"kotlin-inject-anvil\"\n\n    ```kotlin\n    @Inject // (1)!\n    @SingleIn(AppScope::class) // (2)!\n    @ContributesBinding(AppScope::class) //(3)!\n    class AndroidLocationProvider(\n      ...\n    ) : LocationProvider, Scoped {\n      ...\n    }\n    ```\n\n    1.  This annotation is required to support constructor injection.\n    2.  This annotation ensures that there is only ever a single instance of `AndroidLocationProvider` in the `AppScope`.\n    3.  This annotation ensures that when somebody injects `LocationProvider`, then they get the singleton instance of `AndroidLocationProvider`.\n\n    ??? note \"`@ContributesBinding` will generate and contribute bindings\"\n\n        The `@ContributesBinding` annotation will generate a component interface with bindings for `LocationProvider`\n        and `Scoped`. The generated interface will be added automatically to the `AppScope`. No further manual step\n        is needed.\n\n        ```kotlin\n        @Provides\n        public fun provideAndroidLocationProvider(androidLocationProvider: AndroidLocationProvider): LocationProvider = androidLocationProvider\n\n        @Provides\n        @IntoSet\n        @ForScope(AppScope::class)\n        fun provideAndroidLocationProviderScoped(androidLocationProvider: AndroidLocationProvider): Scoped = androidLocationProvider\n        ```\n\n\n??? example \"Sample\"\n\n    Another example in the sample app is [`SessionTimeout`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt).\n    This class is part of the `UserScope` and implements the `Scoped` interface. `onEnterScope()` will be called when\n    the user logs in and `onExitScope()` when the user logs out.\n\n    ```kotlin\n    @Inject\n    @SingleIn(UserScope::class)\n    @ContributesScoped(UserScope::class) // Use @ContributesBinding with kotlin-inject-anvil.\n    class SessionTimeout(...) : Scoped {\n\n      override fun onEnterScope(scope: Scope) {\n        // This job will be automatically canceled when the user logs out and the user scope is\n        // destroyed.\n        scope.launch {\n          while (userManager.user.value != null) {\n            ...\n          }\n        }\n\n        scope.launch {\n          ...\n        }\n      }\n    }\n    ```\n\n### Registering `Scoped`\n\nThe dependency injection frameworks like Metro and `kotlin-inject-anvil` are only responsible for creating `Scoped`\ninstances, but don't automatically register them in the `Scope`. This has to be done whenever the `Scope` is created:\n\n=== \"Metro\"\n\n    ```kotlin hl_lines=\"4 16\"\n    @DependencyGraph(AppScope::class)\n    interface AppGraph {\n      /** All [Scoped] instances part of the app scope. */\n      @ForScope(AppScope::class) val appScopedInstances: Set<Scoped>\n    }\n    \n    fun createAppScope(appGraph: AppGraph): Scope {\n      val rootScope =\n        Scope.buildRootScope {\n          addMetroDependencyGraph(appGraph)\n    \n          addCoroutineScopeScoped(appGraph.appScopeCoroutineScopeScoped)\n        }\n    \n      rootScope.register(appGraph.appScopedInstances)\n    \n      return rootScope\n    }\n    ```\n    \n    By calling `appGraph.appScopedInstances` the DI framework instantiates all `Scoped` instances part of the\n    `AppScope`. The `rootScope.register(...)` call will register all of the `Scoped` instances and invoke\n    `onEnterScope(scope)`. When calling `rootScope.destroy()` later at some point, then `onExitScope()` will be\n    called for all `Scoped` instances.\n\n=== \"kotlin-inject-anvil\"\n\n    ```kotlin hl_lines=\"5 16\"\n    @SingleIn(AppScope::class)\n    @MergeComponent(AppScope::class)\n    interface AppComponent {\n      /** All [Scoped] instances part of the app scope. */\n      @ForScope(AppScope::class) val appScopedInstances: Set<Scoped>\n    }\n    \n    fun createAppScope(appComponent: AppComponent): Scope {\n      val rootScope =\n        Scope.buildRootScope {\n          addKotlinInjectComponent(appComponent)\n    \n          addCoroutineScopeScoped(appComponent.appScopeCoroutineScopeScoped)\n        }\n    \n      rootScope.register(appComponent.appScopedInstances)\n    \n      return rootScope\n    }\n    ```\n    \n    By calling `appComponent.appScopedInstances` the DI framework instantiates all `Scoped` instances part of the\n    `AppScope`. The `rootScope.register(...)` call will register all of the `Scoped` instances and invoke\n    `onEnterScope(scope)`. When calling `rootScope.destroy()` later at some point, then `onExitScope()` will be\n    called for all `Scoped` instances.\n\n\n??? example \"Sample\"\n\n    The sample application implements this mechanism for the\n    [`AppScope`](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt#L31-L33)\n    and the [`UserScope`](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt#L58-L60).\n\n### `onExit`\n\nThe convenience function `onExit` is handy when you want to create objects lazily within `onEnterScope()` and\nnot create a property in the class itself. This callback notifies you when the `Scope` is destroyed similar to\n`onExitScope()`.\n\n=== \"Metro\"\n\n    ```kotlin\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesScoped(AppScope::class)\n    class MyClass(private val application: Application) : Scoped {\n    \n      override fun onEnterScope(scope: Scope) {\n        val receiver = object : BroadcastReceiver()\n    \n        application.registerReceiver(receiver, Intent())\n    \n        scope.onExit {\n          // This function is invoked when the scope gets destroyed.\n          application.unregisterReceiver(receiver)\n        }\n      }\n    }\n    ```\n\n=== \"kotlin-inject-anvil\"\n\n    ```kotlin\n    @Inject\n    @SingleIn(AppScope::class)\n    @ContributesBinding(AppScope::class)\n    class MyClass(private val application: Application) : Scoped {\n    \n      override fun onEnterScope(scope: Scope) {\n        val receiver = object : BroadcastReceiver()\n    \n        application.registerReceiver(receiver, Intent())\n    \n        scope.onExit {\n          // This function is invoked when the scope gets destroyed.\n          application.unregisterReceiver(receiver)\n        }\n      }\n    }\n    ```\n\n\n### Threading\n\nWhich thread is used for calling `onEnterScope()` and `onExitScope()` is an implementation detail of the scope\nowner when calling `scope.register(Scoped)`. Usually, the app scope is created as soon as possible when the\napplication launches and therefore the main thread is used. Child scopes may use the main thread or a background\nthread.\n\nTo safely launch long running work or blocking tasks it’s recommended to use the coroutine scope provided by the\n`Scope`:\n\n```kotlin\noverride fun onEnterScope(scope: Scope) {\n  scope.launch { ... }\n}\n```\n\nClean up routines in `onExitScope()` must be blocking, otherwise these tasks live longer than the `Scope` and\ntherefore may cause a leak (thread and memory) and potential race conditions. It’s strongly recommended not to\nlaunch any asynchronous work within `onExitScope()`. By the time `onExitScope()` is called, the coroutine\nscope provided by the `Scope` has been canceled already.\n\n## Hosting `Scopes`\n\nScopes need to be remembered and must be accessible in order to get access to their services. Where to host scopes\ndepends on what scopes are required and when they need to be created. Most apps have some form of an application\nscope, which is a singleton scope for the entire lifetime of the application. A natural place to host this scope\nfor Android apps is within the `Application` class, for iOS apps within `App` struct or the main function\nfor desktop applications.\n\nA user scope has a shorter lifecycle than the application scope, but usually lives longer than UI components.\nIt is commonly hosted by a service object managing the login state. This scope is destroyed after the user\nsession expires.\n\nApp Platform by default only provides the `AppScope`, which has to be manually created by each application as\nhighlighted above.\n\n??? example \"Sample\"\n\n    The sample application has a common class [DemoApplication](https://github.com/amzn/app-platform/blob/main/sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt)\n    that is responsible for creating the app scope. The Android app instantiates `DemoApplication` in the\n    [`Application` class](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt#L19).\n    The iOS sample creates the `DemoApplication` in the [`UIApplicationDelegate`](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/iosApp/iosApp/iOSApp.swift#L6).\n    On Desktop `DemoApplication` is created part of the [`main()` function](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt#L8).\n\n### `RootScopeProvider`\n\n[`RootScopeProvider`](https://github.com/amzn/app-platform/blob/main/scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/RootScopeProvider.kt),\nas the name suggests, gives access to the root `Scope` (\"AppScope\"). Usually, this interface is implemented by the application\nobject of the individual platform to get access to the root `Scope` from a platform context, e.g. on Android this is\nhandy in an `Activity`:\n\n```kotlin\nclass MainActivity : Activity() {\n\n  private val rootScopeProvider\n    get() = application as RootScopeProvider\n\n  ...\n}\n```\n\n??? example \"Sample\"\n\n    The sample application implements `RootScopeProvider` in the Android\n    [`Application` class](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt#L19)\n    and the iOS [`UIApplicationDelegate`](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/iosApp/iosApp/iOSApp.swift#L6).\n    On Desktop there is no concept of a singleton application object by default, but in the sample app we created an\n    equivalent with [`DesktopApp`](https://github.com/amzn/app-platform/blob/main/sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt).\n"
  },
  {
    "path": "docs/setup.md",
    "content": "# Setup\n\n## Gradle\n\nApp Platform, its various features and dependencies are all configured through a Gradle plugin. The various options\nare explained in more detail in many of the following sections.\n\n=== \"build.gradle\"\n\n    ```groovy\n    plugins {\n      id 'software.amazon.app.platform' version 'x.y.z'\n    }\n\n    appPlatform {\n      // false by default. Adds dependencies on the APIs for scopes, presenters and renderers in order to use the App Platform.\n      addPublicModuleDependencies true\n\n      // false by default. Helpful for final application modules that must consume concrete implementations and not only APIs.\n      addImplModuleDependencies true\n\n      // false by default. Recommended DI option. Configures Metro and adds App Platform specific extensions as dependency.\n      enableMetro true\n\n      // false by default. Alternative DI option. Configures KSP and adds the kotlin-inject-anvil library as dependency.\n      enableKotlinInject true\n\n      // false by default. Configures Molecule and provides access to the MoleculePresenter API.\n      enableMoleculePresenters true\n\n      // false by default. Adds the necessary dependencies to use Compose Multiplatform with Renderers.\n      enableComposeUi true\n\n      // false by default. Verifies that this module follows conventions for our module structure and\n      // adds default dependencies. For Android projects it sets the namespace to avoid conflicts.\n      enableModuleStructure true\n    }\n    ```\n\n=== \"build.gradle.kts\"\n\n    ```kotlin\n    plugins {\n      id(\"software.amazon.app.platform\") version \"x.y.z\"\n    }\n\n    appPlatform {\n      // false by default. Adds dependencies on the APIs for scopes, presenters and renderers in order to use the App Platform.\n      addPublicModuleDependencies(true)\n\n      // false by default. Helpful for final application modules that must consume concrete implementations and not only APIs.\n      addImplModuleDependencies(true)\n\n      // false by default. Recommended DI option. Configures Metro and adds App Platform specific extensions as dependency.\n      enableMetro(true)\n\n      // false by default. Alternative DI option. Configures KSP and adds the kotlin-inject-anvil library as dependency.\n      enableKotlinInject(true)\n\n      // false by default. Configures Molecule and provides access to the MoleculePresenter API.\n      enableMoleculePresenters(true)\n\n      // false by default. Adds the necessary dependencies to use Compose Multiplatform with Renderers.\n      enableComposeUi(true)\n\n      // false by default. Verifies that this module follows conventions for our module structure and\n      // adds default dependencies. For Android projects it sets the namespace to avoid conflicts.\n      enableModuleStructure(true)\n    }\n    ```\n\n!!! note\n\n    All settings of App Platform are optional and opt-in, e.g. you can use Molecule Presenters without enabling\n    the opinionated module structure. Compose UI can be enabled without using `Metro` or\n    `kotlin-inject-anvil`. When you do want DI, Metro is the recommended default.\n\n## Snapshot\n\nTo import snapshot builds use following repository:\n\n=== \"build.gradle\"\n\n    ```groovy\n    maven {\n      url = 'https://central.sonatype.com/repository/maven-snapshots/'\n    }\n    ```\n\n=== \"build.gradle.kts\"\n\n    ```kotlin\n    maven {\n      url = uri(\"https://central.sonatype.com/repository/maven-snapshots/\")\n    }\n    ```\n"
  },
  {
    "path": "docs/template.md",
    "content": "# Template\n\n[`Templates`](https://github.com/amzn/app-platform/blob/main/presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/template/Template.kt)\nare an abstraction between `Presenters` and `Renderers` and represent the root of the presenter and renderer tree.\nPractically, a template is one particular type of `BaseModel` that hosts other models (a container of models).\nHowever, instead of using a weak type like `List<BaseModel>`, a template carries semantics about what content should\nbe rendered, how many UI layers there are and where each individual model should be displayed.\n\n`Templates` are app specific and not shared, because each app may use a different layering mechanism for individual\nscreen configurations. An example template definition could look like this:\n\n```kotlin\nsealed interface SampleAppTemplate : Template {\n\n  data class FullScreenTemplate(\n    val model: BaseModel,\n  ) : SampleAppTemplate\n\n  data class ListDetailTemplate(\n    val list: BaseModel,\n    val detail: BaseModel,\n  ) : SampleAppTemplate\n}\n```\n\n??? example \"Sample\"\n\n    A [similar hierarchy](https://github.com/amzn/app-platform/blob/main/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplate.kt)\n    is implemented in the sample application.\n\nThe `Template` interface extends `BaseModel` and each app must come with its own `TemplatePresenter` and\n`TemplateRenderer`. Both are implemented the same way as other presenters and renderers would be implemented.\nThe responsibility of the `TemplatePresenter` is to wrap another presenter and wrap its models within a `Template`,\ne.g.\n\n```kotlin hl_lines=\"8\"\n@Inject\nclass SampleAppTemplatePresenter(\n  @Assisted private val rootPresenter: MoleculePresenter<Unit, *>,\n) : MoleculePresenter<Unit, SampleAppTemplate> {\n  @Composable\n  override fun present(input: Unit): SampleAppTemplate {\n    return returningCompositionLocalProvider {\n      rootPresenter.present(Unit).toTemplate {\n        SampleAppTemplate.FullScreenTemplate(it)\n      }\n    }\n  }\n}\n```\n\n??? example \"Sample\"\n\n    The sample app has a [similar implementation](https://github.com/amzn/app-platform/blob/main/sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt).\n\nThe wrapped presenter can override which `Template` to use by implementing [`ModelDelegate`](https://github.com/amzn/app-platform/blob/main/presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/template/ModelDelegate.kt),\ne.g.\n\n```kotlin\ndata class Model(\n  ...\n) : BaseModel, ModelDelegate {\n  override fun delegate(): BaseModel = ListDetailTemplate(...)\n}\n```\n\n??? example \"Sample\"\n\n    The sample app makes use of this mechanism in the [user page](https://github.com/amzn/app-platform/blob/main/sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenter.kt),\n    where it the layout is split between a list presenter / renderer and detail presenter / renderer.\n\n    ```kotlin\n    data class Model(\n      val listModel: BaseModel,\n      val detailModel: BaseModel\n    ) : BaseModel, ModelDelegate {\n      override fun delegate(): BaseModel {\n        return SampleAppTemplate.ListDetailTemplate(listModel, detailModel)\n      }\n    }\n    ```\n\nThe `TemplateRenderer` receives the specific `Template`, lays out necessary containers and renders individual\nmodels in these layers. The renderer often injects `RendererFactory` to create renderers for the models, e.g.\n\n```kotlin\n@Inject\n@ContributesRenderer\nclass ComposeSampleAppTemplateRenderer(\n  private val rendererFactory: RendererFactory\n) : ComposeRenderer<SampleAppTemplate>() {\n\n  @Composable\n  override fun Compose(model: SampleAppTemplate) {\n    when (model) {\n      is SampleAppTemplate.FullScreenTemplate -> FullScreen(model)\n      is SampleAppTemplate.ListDetailTemplate -> ListDetail(model)\n    }\n  }\n\n  @Composable\n  private fun FullScreen(template: SampleAppTemplate.FullScreenTemplate) {\n    val renderer = rendererFactory.getComposeRenderer(template.model)\n    renderer.renderCompose(template.model)\n  }\n\n  @Composable\n  private fun ListDetail(template: SampleAppTemplate.ListDetailTemplate) {\n    Row {\n      Column {\n        rendererFactory.getComposeRenderer(template.list).renderCompose(template.list)\n      }\n      Column {\n        rendererFactory.getComposeRenderer(template.detail).renderCompose(template.detail)\n      }\n    }\n  }\n}\n```\n\n### Consuming `Templates`\n\nOn the API level `Templates` are regular `Models`, with a regular `Presenter` and `Renderer`. Therefore, they\nrequire no special treatment and the regular `RendererFactory` can be used:\n\n```kotlin\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    val factory = remember { ComposeRendererFactory(rootScopeProvider = rootScopeProvider) }\n\n    val templatePresenter = remember {\n      val component = rootScopeProvider.rootScope.kotlinInjectComponent<ViewControllerComponent>()\n      component.factory.createSampleAppTemplatePresenter(component.navigationPresenter)\n    }\n\n    val template = templatePresenter.present(Unit)\n    factory.getComposeRenderer(template).renderCompose(template)\n  }\n\n@ContributesTo(AppScope::class)\ninterface ViewControllerComponent {\n  val factory: SampleAppTemplatePresenter.Factory\n  val navigationPresenter: NavigationPresenter\n}\n```\n\n## Unidirectional dataflow\n\nTemplates complete the circle in our unidirectional dataflow pattern:\n\n![Unidirectional dataflow](images/unidirectional-dataflow.png){ width=\"600\" }\n\nThis diagram summarizes how models from child presenters bubble up ultimately to the template presenter. The template\npresenter wraps the models in a template, which is then handed off the rendering pipeline. `RendererFactory` finds\nthe right renderers for the template and models and the content will be shown on screen by individual renderers. The\ncircle repeats either when a renderer invokes a callback from the model and sends the event back to the presenter or\nanother state change occurs within the the presenter tree.\n"
  },
  {
    "path": "docs/testing.md",
    "content": "# Testing\n\nA fundamental design pattern to make testing effective is dependency inversion, which means that high-level\nAPIs don’t depend on low-level details and low-level details only import other high-level APIs.\nIt significantly reduces coupling between components.\n\nApp Platform implements the pattern in its [module structure](module-structure.md#gradle-modules) and in\n[Kotlin code](module-structure.md#kotlin-code). By relying on dependency inversion, we decouple projects from\ntheir dependencies and enable testing in isolation. This approach is important for unit tests, instrumented tested\nand integration tests. These three types of tests rely on a chain of trust, where we assume that dependencies\nare functioning and tests don’t need to be repeated.\n\n![Testing pyramid](images/testing-pyramid.png){ width=\"400\" }\n\n!!! info \"Instrumented tests\"\n\n    The sample application implements instrumented tests for two screens and navigates between the tests. The\n    [tests for Desktop](https://github.com/amzn/app-platform/blob/main/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt)\n    highlight how templates are rendered and robots are used for verification. They also set up a Metro\n    [`TestDesktopAppGraph`](https://github.com/amzn/app-platform/blob/main/sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppGraph.kt),\n    which replaces the main desktop graph.\n\n    The same UI test is [implemented for Android](https://github.com/amzn/app-platform/blob/main/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/AndroidLoginUiTest.kt).\n    The Android tests reuse the same robots for verification and set up a\n    [`TestAndroidAppGraph`](https://github.com/amzn/app-platform/blob/main/sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppGraph.kt)\n    in a similar way. The sample now uses Metro throughout, while `kotlin-inject-anvil` remains\n    available as the alternative path.\n\n## Fakes\n\nUnit tests build the foundation of the testing pyramid. They verify the smallest components of our app, which\nusually are single classes or functions and we rarely test multiple classes in combination. Dependencies of\nthese classes are typically replaced by fakes. Due to this low coupling unit tests tend to be very stable.\n\n!!! info \"Fakes vs real implementations\"\n\n    Using real implementations of dependencies for the unit under test is a valid option as it brings the\n    tested code close to production, increases confidence and removes isolation.\n    A [best practice from Google](https://abseil.io/resources/swe-book/html/ch13.html) is summarized as:\n\n    > A real implementation is preferred if it is fast, deterministic, and has simple dependencies. For example,\n    > a real implementation should be used for a value object. Examples include an amount of money, a date, a\n    > geographical address, or a collection class such as a list or a map.\n    >\n    > However, for more complex code, using a real implementation often isn’t feasible. There might not be an\n    > exact answer on when to use a real implementation or a test double given that there are trade-offs to be made.\n\n    The trade-offs include execution time, determinism and dependency construction. Fakes improve all three points\n    by avoiding slow IO, returning stable results and breaking dependency chains at the cost of diverging from the\n    behavior in production and reduced confidence.\n\n```kotlin\ninterface LocationProvider {\n  val location: StateFlow<Location>\n}\n\nclass RoutingRepository(\n  private val locationProvider: LocationProvider\n)\n```\n\nImagine to test `RoutingRepository`. To create an new instance under test, we must provide a `LocationProvider`.\nSince we use dependency inversion and didn’t hardcode a concrete implementation, it is simple to implement a fake\nfor this interface:\n\n```kotlin\nclass FakeLocationProvider(\n  val currentLocation: Location = Location(..)\n) : LocationProvider {\n  private val _location = MutableStateFlow(currentLocation)\n  override val location = _location\n\n  fun updateLocation(newLocation: Location) {\n    _location.value = newLocation\n  }\n}\n```\n\nNow we can instantiate our `RoutingRepository`:\n\n```kotlin\n@Test\nfun `the route is updated when the driver doesn't follow directions`() {\n  val locationProvider = FakeLocationProvider()\n  val routingRepository = RoutingRepository(locationProvider)\n\n  locationProvider.updateLocation(...)\n}\n```\n\nGood fake implementations are valuable. It’s best practice and strongly encouraged as an API provider to implement\nfakes for APIs and share them with consumers. The [App Platform module structure](module-structure.md) provides\n[`:testing` modules](module-structure.md#testing) for this purpose. For example, the owner of `LocationProvider`\nis encouraged to use this structure:\n\n```\n:location-provider:public   src/commonMain/kotlin/.../LocationProvider.kt\n:location-provider:testing  src/commonMain/kotlin/.../FakeLocationProvider.kt\n```\n\nThe owner of `RoutingRepository` can import `:location-provider:testing` and reuse the provided fake in tests.\nThis avoids duplication.\n\n??? example \"Sample\"\n\n    The sample app uses `:testing` modules to implement and share fakes across modules, e.g.\n    [`:sample:user:testing`](https://github.com/amzn/app-platform/tree/main/sample/user/testing). In other modules\n    fakes are created next to the tests ad-hoc, e.g. [`FakeUserPagePresenter`](https://github.com/amzn/app-platform/blob/0f3e242ae08bb242fbd7080d33caa069c8fae2b4/sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt#L51-L58)\n    and [`FakeAnimationHelper`](https://github.com/amzn/app-platform/blob/main/sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/FakeAnimationHelper.kt).\n\n    ```kotlin\n    private class FakeUserPagePresenter : UserPagePresenter {\n      @Composable\n      override fun present(input: Unit): UserPagePresenter.Model =\n        UserPagePresenter.Model(\n          listModel = object : BaseModel {},\n          detailModel = object : BaseModel {},\n        )\n    }\n    ```\n\n    ```kotlin\n    object FakeAnimationHelper : AnimationHelper {\n      override fun isAnimationsEnabled(): Boolean = true\n    }\n    ```\n\n## Robots\n\nTest [`Robots`](https://jakewharton.com/testing-robots/) are an abstraction between test interactions and the\nunderlying implementation. Imagine several tests clicking the *Logout* button use the label to find the UI element\non the screen. If the copy changes from *Logout* to *Sign out*, then all these tests would need to be updated.\nThat is tedious and makes tests harder to maintain. A test robot would hide how the *Logout* button can be found\non screen and only provides an option for the necessary interaction:\n\n```kotlin\nclass LogoutRobot : Robot {\n  fun clickLogoutButton() { .. }\n}\n```\n\nTest [`Robots`](https://github.com/amzn/app-platform/blob/main/robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/Robot.kt)\nare not limited to UI interactions such as verifying UI elements are shown or hidden and invoking\nactions on them. They can also be used to change fake implementations or make assertions on them. Imagine a\nrobot toggling network connectivity. Tests do not interact with fake implementations directly similar to them\nnot interacting with UI elements directly.\n\n```kotlin\nclass NetworkRobot : Robot {\n  var networkEnabled: Boolean\n  var connectivity: Connectivity\n\n  var throwErrorOnSendingRequest: Boolean = false\n\n  enum class Connectivity {\n    LTE, 3G, WIFI, ...\n  }\n}\n```\n\nAnother use case is verifying metrics and analytics events. In instrumented tests we’d use a fake metrics\nimplementation rather than sending events to our backend system. The robot would interact with the fake\nimplementation and make assertions:\n\n```kotlin\nclass FakeMetricsService : MetricsService {\n  val metrics: List<Metric>\n}\n\nclass MetricsRobot : Robot {\n  private val service: FakeMetricsService ...\n\n  fun assertMetricTracked(metric: Metric) {\n    assertThat(service.metrics).contains(metric)\n  }\n}\n```\n\nFake implementations and test robots help verifying interactions with hardware or devices that are not available\nduring an instrumented test run. For example, interactions with other devices can be simulated using a fake\nconnection.\n\n```kotlin\ninterface WebSocketConnection {\n  suspend fun send(message: ByteArray)\n}\n\nclass FakeWebSocketConnection : WebSocketConnection {\n  var throwError: Boolean\n\n  override suspend fun send(message: ByteArray) {\n    if (throwError) {\n      throw Exception(\"...\"\n    } else {\n      trackMessage(message)\n    }\n  }\n}\n\nclass ConnectionRobot : Robot {\n  private val webSocketConnection: FakeWebSocketConnection\n\n  fun sendingMessageFails() {\n    webSocketConnection.throwError = true\n  }\n\n  fun sendingMessageSucceeds() {\n    webSocketConnection.throwError = false\n  }\n}\n```\n\n### Robot types\n\n[`Robot`](https://github.com/amzn/app-platform/blob/main/robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/Robot.kt).\n\n:   Use this common interface for robots that don't interact with any UI, whether that's Compose Multiplatform or\n    Android Views. To obtain an instance of such a robot use the `robot<Type>()` function:\n\n    ```kotlin\n    @Inject\n    @ContributesRobot(AppScope::class)\n    class MetricsRobot(\n      private val metricsService: FakeMetricsService\n    ) : Robot {\n      fun assertMetricTracked(metric: Metric) {\n        assertThat(metricsService.metrics).contains(metric)\n      }\n    }\n\n    @Test\n    fun verify_analytics_event_tracked() {\n      ...\n      robot<MetricsRobot>().assertMetricTracked(..)\n    }\n    ```\n\n[`ComposeRobot`](https://github.com/amzn/app-platform/blob/main/robot-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/robot/ComposeRobot.kt)\n\n:   `ComposeRobot` should be used as parent type when the robot interacts with Compose UI elements. These robots need\n    access to a `SemanticsNodeInteractionsProvider` instance, which is for example provided by calling\n    `runComposeUiTest { ... }` within a test. To forward the `SemanticsNodeInteractionsProvider` instance to the robot\n    call `composeRobot<Type>()` instead of `robot<Type>()`.\n\n    !!! warning\n\n        Calling `robot<Type>()` for a `ComposeRobot` will result in a crash. Always use `composeRobot<Type>()` instead.\n\n    ```kotlin\n    @ContributesRobot(AppScope::class)\n    class LoginRobot : ComposeRobot() {\n\n      private val loginButtonNode\n        get() = compose.onNodeWithTag(\"loginButton\")\n\n      /** Verify that login button is displayed. */\n      fun seeLoginButton() {\n        loginButtonNode.assertIsDisplayed()\n      }\n\n      /** Clicks the login button and starts the login process. */\n      fun clickLoginButton() {\n        loginButtonNode.performClick()\n      }\n    }\n\n    @Test\n    fun `sample test`() {\n      runComposeUiTest {\n        composeRobot<LoginRobot> {\n          seeLoginButton()\n          clickLoginButton()\n        }\n      }\n    }\n    ```\n\n[`AndroidViewRobot`](https://github.com/amzn/app-platform/blob/main/robot/public/src/androidMain/kotlin/software/amazon/app/platform/robot/AndroidViewRobot.kt)\n\n:   `AndroidViewRobot` should be used as parent type when the robot interacts with Android Views.\n    To obtain an instance of such a robot use the `robot<Type>()` function:\n\n    ```kotlin\n    @ContributesRobot(AppScope::class)\n    class AndroidCounterRobot : AndroidViewRobot() {\n      fun seeCounterView() {\n        onView(withText(containsString(\"Counter: \"))).check(matches(isDisplayed()))\n      }\n    }\n\n    @Test\n    fun counter_is_shown() {\n      robot<AndroidCounterRobot> {\n        seeCounterView()\n      }\n    }\n    ```\n\n`Robots` must be annotated with `@ContributesRobot` in order to find them during tests when using the `robot<Type>()`\nor `composeRobot<Type>()` function. The annotation makes sure that the robots are added to the Metro\nor `kotlin-inject-anvil` dependency graph.\n\n??? info \"Generated code\"\n\n    The `@ContributesRobot` annotation generates following code.\n\n    === \"Metro\"\n\n        ```kotlin\n        @ContributesTo(AppScope::class)\n        public interface LoginRobotGraph {\n          @Provides public fun provideLoginRobot(): LoginRobot = LoginRobot()\n    \n          @Provides\n          @IntoMap\n          @RobotKey(LoginRobot::class)\n          public fun provideLoginRobotIntoMap(\n            robot: Provider<LoginRobot>\n          ): Robot = robot()\n        }\n        ```\n\n    === \"kotlin-inject-anvil\"\n\n        ```kotlin\n        @ContributesTo(AppScope::class)\n        public interface LoginRobotComponent {\n          @Provides public fun provideLoginRobot(): LoginRobot = LoginRobot()\n\n          @Provides\n          @IntoMap\n          public fun provideLoginRobotIntoMap(\n            robot: () -> LoginRobot\n          ): Pair<KClass<out Robot>, () -> Robot> = LoginRobot::class to robot\n        }\n        ```\n\n\nIf a `Robot` needs to inject other types such a fake implementations, then it needs to be additionally annotated with\n`@Inject`, e.g.\n\n```kotlin\n@Inject\n@ContributesRobot(AppScope::class)\nclass MetricsRobot(\n  private val metricsService: FakeMetricsService\n) : Robot {\n  fun assertMetricTracked(metric: Metric) {\n    assertThat(metricsService.metrics).contains(metric)\n  }\n}\n```\n\n### `:*-robots` modules\n\nSimilar to sharing fakes for unit tests by leveraging `:testing` modules, the module structure of App Platform\nprovides [`:*-robots` modules](module-structure.md#robots) to share code for instrumented tests across projects.\nIt’s strongly encouraged for features to create `:*-robots` modules and share robot implementations.\n\n??? example \"Sample\"\n\n    The sample application comes with two robot implementations [`LoginRobot`](https://github.com/amzn/app-platform/blob/main/sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt)\n    and [`UserPageRobot`](https://github.com/amzn/app-platform/blob/main/sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt),\n    each living in its feature specific `:robots` module.\n\n## Mocks\n\n**Which mocking framework is recommended?**\n\nNone.\n\nMocking frameworks in general are discouraged and the downside outweigh the little conveniences they offer.\nBy following the principle of dependency inversion we can easily avoid using mocking frameworks and implement\nfakes instead. There are many good resources available describing the advantages of fakes over mocking framework.\nWe recommend reading the provided resources in-order:\n\n* [AndroidX](https://github.com/androidx/androidx/blob/acb603e0857476b17e605fd1384c1f45e7991665/docs/api_guidelines/testing.md) strongly discourages mocking frameworks and banned them from new code. This guide explains in more detail their reasoning and it resonates well.\n* [Google engineers](https://abseil.io/resources/swe-book/html/ch13.html) compare test doubles and give excellent advice for how to fake dependencies (this article is longer, but it’s likely the best one available).\n* [developer.android.com](https://developer.android.com/training/testing/fundamentals/test-doubles#types) prefers fakes over mocks for test doubles: “Fakes don't require a mocking framework and are lightweight. They are preferred.”\n* [CashApp](https://www.billjings.net/posts/title/fakes-are-great-but-mocks-i-hate/?up=technical) banned mocking frameworks in the Android codebase, because mocks are a maintenance burden.\n* [Ryan Harter](https://ryanharter.com/blog/2020/06/replacing-mocks/) calls out easy traps when using mocks.\n* [Pravin Sonawane](https://medium.com/@june.pravin/mocking-is-not-practical-use-fakes-e30cc6eaaf4e) makes similar arguments and highlights how mocks encourage testing the “how” rather than focusing on the “what” (inputs and outputs).\n* Google blog [Don’t overuse mocks](https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html) highlights some downsides of mocks and presents real or fake implementations as alternative.\n"
  },
  {
    "path": "gradle/detekt-config.yml",
    "content": "# Detekt configuration tweaks. These are documented at\n#   https://detekt.github.io/detekt/configurations.html\n#   https://detekt.github.io/detekt/comments.html\n# Also helpful are the Detekt default settings at\n#   https://github.com/detekt/detekt/blob/main/detekt-core/src/main/resources/default-detekt-config.yml\nstyle:\n  DataClassShouldBeImmutable:\n    active: true\n  MagicNumber:\n    # Magic numbers in enums should be ignored\n    ignoreEnums: true\n    # The next two parameters are recommended for Compose: https://detekt.dev/docs/introduction/compose/\n    ignorePropertyDeclaration: true\n    ignoreCompanionObjectPropertyDeclaration: true\n  UnusedPrivateMember:\n    # Recommended for Compose: https://detekt.dev/docs/introduction/compose/\n    ignoreAnnotated: ['Preview']\n  MaxLineLength:\n    active: false\ncomments:\n  DeprecatedBlockTag:\n    active: true\n  EndOfSentenceFormat:\n    active: true\n  UndocumentedPublicClass:\n    active: true\n    ignoreDefaultCompanionObject: true\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\n  UndocumentedPublicFunction:\n    active: true\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\n  UndocumentedPublicProperty:\n    active: true\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\ncomplexity:\n  LongParameterList:\n    constructorThreshold: 12\n    ignoreAnnotated: [\n      ‘Inject’,\n    ]\n    # The next two parameters are recommended for Compose: https://detekt.dev/docs/introduction/compose/\n    functionThreshold: 12\n    ignoreDefaultParameters: true\n  TooManyFunctions:\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\n    ignoreDeprecated: true\n    ignorePrivate: true\n    ignoreOverridden: true\n  LargeClass:\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\n  LongMethod:\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\nnaming:\n  InvalidPackageDeclaration:\n    active: true\n  FunctionNaming:\n    # The next two parameters are recommended for Compose: https://detekt.dev/docs/introduction/compose/\n    functionPattern: '[a-zA-Z][a-zA-Z0-9]*'\n    ignoreAnnotated: ['Composable']\n    excludes: [\n      '**/src/*Test/**/*.kt',\n      '**/src/test/**/*.kt',\n    ]\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"8.13.2\"\nandroid-compileSdk = \"36\"\n# https://developer.android.com/jetpack/androidx/releases/compose-ui\n# https://maven.google.com/web/index.html#androidx.compose.ui:ui\nandroid-compose-version = \"1.10.6\"\nandroid-minSdk = \"23\"\nandroid-targetSdk = \"36\"\n#noinspection GradleDependency\nandroidx-activity = \"1.13.0\"\nandroidx-annotations = \"1.9.1\"\nandroidx-collection = \"1.5.0\"\n#noinspection GradleDependency\nandroidx-constraintlayout = \"2.1.4\"\n#noinspection GradleDependency\nandroidx-core = \"1.10.1\"\nandroidx-lint-gradle = \"1.0.0-alpha06\"\nandroidx-test-espresso = \"3.7.0\"\nandroidx-test-junit = \"1.3.0\"\nandroidx-test-monitor = \"1.8.0\"\nandroidx-test-orchestrator = \"1.6.1\"\nandroidx-test-rules = \"1.7.0\"\nandroidx-test-runner = \"1.7.0\"\nassertk = \"0.28.1\"\nauto-service = \"1.1.1\"\nauto-service-ksp = \"1.2.0\"\nbuild-config = \"6.0.9\"\n# https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html#kotlin-compatibility\n# https://mvnrepository.com/artifact/org.jetbrains.compose/compose-gradle-plugin\n# https://github.com/JetBrains/compose-multiplatform/releases\ncompose-multiplatform = \"1.10.3\"\ncoroutines = \"1.10.2\"\ndetekt = \"1.23.8\"\ngraphviz-java = \"0.18.1\"\njvm-compatibility = \"11\"\n# We need a newer version for buildSrc. This project uses JDK 21, Metro requires JDK 21, and therefore we should\n# compile buildSrc with 21.\njvm-buildsrc = \"21\"\nkotlin = \"2.3.20\"\nkotlin-atomicfu = \"0.32.1\"\nkotlin-compile-testing = \"0.12.1\"\nkotlin-hierarchy = \"1.1\"\nkotlin-inject = \"0.9.0\"\nkotlin-inject-anvil = \"0.1.7\"\nkotlin-poet = \"2.3.0\"\nkotlinx-binaryCompatibilityValidator = \"0.18.1\"\nktfmt-gradle = \"0.26.0\"\nksp = \"2.3.6\"\nmaven-publish = \"0.36.0\"\nmetro = \"1.0.0-RC2\"\nmolecule = \"2.2.0\"\nnavigation-event = \"1.0.1\"\nnavigation3 = \"1.1.0\"\n#noinspection GradleDependency\nrecyclerView = \"1.2.1\"\nturbine = \"1.2.1\"\n#noinspection GradleDependency\nviewbinding = \"7.0.0\"\n\n[libraries]\nandroid-gradle-plugin = { module = \"com.android.tools.build:gradle\", version.ref = \"agp\" }\nandroid-gradle-plugin-api = { module = \"com.android.tools.build:gradle-api\", version.ref = \"agp\" }\nandroidx-activity = { module = \"androidx.activity:activity\", version.ref = \"androidx-activity\" }\nandroidx-activity-compose = { module = \"androidx.activity:activity-compose\", version.ref = \"androidx-activity\" }\nandroidx-annotations = { module = \"androidx.annotation:annotation\", version.ref = \"androidx-annotations\" }\nandroidx-constraintlayout = { module = \"androidx.constraintlayout:constraintlayout\", version.ref = \"androidx-constraintlayout\" }\nandroidx-core = { module = \"androidx.core:core-ktx\", version.ref = \"androidx-core\" }\nandroidx-collection = { module = \"androidx.collection:collection\", version.ref = \"androidx-collection\" }\nandroidx-lint-gradle = {  module = \"androidx.lint:lint-gradle\", version.ref = \"androidx-lint-gradle\" }\nandroidx-test-espresso = { module = \"androidx.test.espresso:espresso-core\", version.ref = \"androidx-test-espresso\" }\nandroidx-test-junit = { module = \"androidx.test.ext:junit\", version.ref = \"androidx-test-junit\" }\nandroidx-test-monitor = { module = \"androidx.test:monitor\", version.ref = \"androidx-test-monitor\" }\nandroidx-test-orchestrator = { module = \"androidx.test:orchestrator\", version.ref = \"androidx-test-orchestrator\" }\nandroidx-test-rules = { module = \"androidx.test:rules\", version.ref = \"androidx-test-rules\" }\nandroidx-test-runner = { module = \"androidx.test:runner\", version.ref = \"androidx-test-runner\" }\nassertk = { module = \"com.willowtreeapps.assertk:assertk\", version.ref = \"assertk\" }\nauto-service-annotations = { module = \"com.google.auto.service:auto-service-annotations\", version.ref = \"auto-service\" }\nauto-service-ksp = { module = \"dev.zacsweers.autoservice:auto-service-ksp\", version.ref = \"auto-service-ksp\" }\nbuild-config-gradle-plugin = { module = \"com.github.gmazzo.buildconfig:plugin\", version.ref = \"build-config\" }\ncompose-gradle-plugin = { module = \"org.jetbrains.compose:compose-gradle-plugin\", version.ref = \"compose-multiplatform\" }\ncompose-ui-back-handler = { module = \"org.jetbrains.compose.ui:ui-backhandler\", version.ref = \"compose-multiplatform\" }\ncompose-ui-test-junit4 = { module = \"androidx.compose.ui:ui-test-junit4\", version.ref = \"android-compose-version\" }\ncompose-ui-test-junit4-android = { module = \"androidx.compose.ui:ui-test-junit4-android\", version.ref = \"android-compose-version\" }\ncompose-ui-test-manifest = { module = \"androidx.compose.ui:ui-test-manifest\", version.ref = \"android-compose-version\" }\ncoroutines-core = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-core\", version.ref = \"coroutines\" }\ncoroutines-swing = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-swing\", version.ref = \"coroutines\" }\ncoroutines-test = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-test\", version.ref = \"coroutines\" }\ndetekt-gradle-plugin = { module = \"io.gitlab.arturbosch.detekt:detekt-gradle-plugin\", version.ref = \"detekt\" }\ngraphviz-java = { module = \"guru.nidi:graphviz-java\", version.ref = \"graphviz-java\" }\nkotlin-atomicfu = { module = \"org.jetbrains.kotlinx:atomicfu\", version.ref = \"kotlin-atomicfu\" }\nkotlin-bom = { module = \"org.jetbrains.kotlin:kotlin-bom\", version.ref = \"kotlin\" }\nkotlin-annotations-jvm = { module = \"org.jetbrains.kotlin:kotlin-annotations-jvm\", version.ref = \"kotlin\" }\nkotlin-compiler = { module = \"org.jetbrains.kotlin:kotlin-compiler\", version.ref = \"kotlin\" }\nkotlin-compile-testing-core = { module = \"dev.zacsweers.kctfork:core\", version.ref = \"kotlin-compile-testing\" }\nkotlin-compile-testing-ksp = { module = \"dev.zacsweers.kctfork:ksp\", version.ref = \"kotlin-compile-testing\" }\nkotlin-compiler-embeddable = { module = \"org.jetbrains.kotlin:kotlin-compiler-embeddable\", version.ref = \"kotlin\" }\nkotlin-compiler-internal-test-framework = { module = \"org.jetbrains.kotlin:kotlin-compiler-internal-test-framework\", version.ref = \"kotlin\" }\nkotlin-compose-gradle-plugin = { module = \"org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin\", version.ref = \"kotlin\" }\nkotlin-gradle-plugin = { module = \"org.jetbrains.kotlin:kotlin-gradle-plugin\", version.ref = \"kotlin\" }\nkotlin-gradle-plugin-api = { module = \"org.jetbrains.kotlin:kotlin-gradle-plugin-api\", version.ref = \"kotlin\" }\nkotlin-hierarchy-plugin = { module = \"io.github.terrakok:kmp-hierarchy\", version.ref = \"kotlin-hierarchy\" }\nkotlin-multiplatform-gradle-plugin = { module = \"org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin\", version.ref = \"kotlin\" }\nkotlin-inject-ksp = { module = \"me.tatarka.inject:kotlin-inject-compiler-ksp\", version.ref = \"kotlin-inject\" }\nkotlin-inject-anvil-compiler = { module = \"software.amazon.lastmile.kotlin.inject.anvil:compiler\", version.ref = \"kotlin-inject-anvil\" }\nkotlin-inject-anvil-runtime = { module = \"software.amazon.lastmile.kotlin.inject.anvil:runtime\", version.ref = \"kotlin-inject-anvil\" }\nkotlin-inject-anvil-runtime-optional = { module = \"software.amazon.lastmile.kotlin.inject.anvil:runtime-optional\", version.ref = \"kotlin-inject-anvil\" }\nkotlin-inject-runtime = { module = \"me.tatarka.inject:kotlin-inject-runtime\", version.ref = \"kotlin-inject\" }\nkotlin-inject-runtime-kmp = { module = \"me.tatarka.inject:kotlin-inject-runtime-kmp\", version.ref = \"kotlin-inject\" }\nkotlin-reflect = { module = \"org.jetbrains.kotlin:kotlin-reflect\", version.ref = \"kotlin\" }\nkotlin-script-runtime = { module = \"org.jetbrains.kotlin:kotlin-script-runtime\", version.ref = \"kotlin\" }\nkotlin-test = { module = \"org.jetbrains.kotlin:kotlin-test\", version.ref = \"kotlin\" }\nkotlin-test-junit5 = { module = \"org.jetbrains.kotlin:kotlin-test-junit5\", version.ref = \"kotlin\" }\nkotlinx-binaryCompatibilityValidator = { module = \"org.jetbrains.kotlinx:binary-compatibility-validator\", version.ref = \"kotlinx-binaryCompatibilityValidator\" }\nkotlin-poet = { module = \"com.squareup:kotlinpoet\", version.ref = \"kotlin-poet\" }\nkotlin-poet-ksp = { module = \"com.squareup:kotlinpoet-ksp\", version.ref = \"kotlin-poet\" }\nktfmt-gradle-plugin = { module = \"com.ncorti.ktfmt.gradle:plugin\", version.ref = \"ktfmt-gradle\" }\nksp = { module = \"com.google.devtools.ksp:symbol-processing\", version.ref = \"ksp\" }\nksp-api = { module = \"com.google.devtools.ksp:symbol-processing-api\", version.ref = \"ksp\" }\nksp-embeddable = { module = \"com.google.devtools.ksp:symbol-processing-aa-embeddable\", version.ref = \"ksp\" }\nksp-gradle-plugin = { module = \"com.google.devtools.ksp:symbol-processing-gradle-plugin\", version.ref = \"ksp\" }\nmaven-publish-gradle-plugin = { module = \"com.vanniktech:gradle-maven-publish-plugin\", version.ref = \"maven-publish\" }\nmetro-compiler = { module = \"dev.zacsweers.metro:compiler\", version.ref = \"metro\" }\nmetro-gradle-plugin = { module = \"dev.zacsweers.metro:gradle-plugin\", version.ref = \"metro\" }\nmetro-runtime = { module = \"dev.zacsweers.metro:runtime\", version.ref = \"metro\" }\nmolecule-runtime = { module = \"app.cash.molecule:molecule-runtime\", version.ref = \"molecule\" }\nnavigation-event-compose = { module = \"org.jetbrains.androidx.navigationevent:navigationevent-compose\", version.ref = \"navigation-event\" }\nnavigation3-runtime = { module = \"androidx.navigation3:navigation3-runtime\", version.ref = \"navigation3\" }\nnavigation3-ui = { module = \"org.jetbrains.androidx.navigation3:navigation3-ui\", version.ref = \"navigation3\" }\nrecyclerView = { module = \"androidx.recyclerview:recyclerview\", version.ref = \"recyclerView\" }\nturbine = { module = \"app.cash.turbine:turbine\", version.ref = \"turbine\" }\n#noinspection SimilarGradleDependency\nviewbinding-api = { module = \"androidx.databinding:viewbinding\", version.ref = \"viewbinding\" }\n#noinspection SimilarGradleDependency\nviewbinding-agp = { module = \"androidx.databinding:viewbinding\", version.ref = \"agp\" }\n\n[plugins]\nandroid-app = { id = \"com.android.application\", version.ref = \"agp\" }\nandroid-kmp-library = { id = \"com.android.kotlin.multiplatform.library\", version.ref = \"agp\" }\nandroid-library = { id = \"com.android.library\", version.ref = \"agp\" }\nandroid-lint = { id = \"com.android.lint\", version.ref = \"agp\" }\napp-platform = { id = \"software.amazon.app.platform\" }\nbuild-config = { id = \"com.github.gmazzo.buildconfig\", version.ref = \"build-config\" }\ndetekt = { id = \"io.gitlab.arturbosch.detekt\", version.ref = \"detekt\" }\nkotlin-hierarchy = { id = \"io.github.terrakok.kmp-hierarchy\", version.ref = \"kotlin-hierarchy\" }\nkotlin-jvm = { id = \"org.jetbrains.kotlin.jvm\", version.ref = \"kotlin\" }\nkotlinx-binaryCompatibilityValidator = { id = \"org.jetbrains.kotlinx.binary-compatibility-validator\", version.ref = \"kotlinx-binaryCompatibilityValidator\" }\nktfmt = { id = \"com.ncorti.ktfmt.gradle\", version.ref = \"ktfmt-gradle\" }\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nmaven-publish = { id = \"com.vanniktech.maven.publish\", version.ref = \"maven-publish\" }\nmetro = { id = \"dev.zacsweers.metro\", version.ref = \"metro\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle-plugin/api/gradle-plugin.api",
    "content": "public class software/amazon/app/platform/gradle/AppPlatformExtension {\n\tpublic fun <init> (Lorg/gradle/api/model/ObjectFactory;Lorg/gradle/api/Project;)V\n\tpublic final fun addImplModuleDependencies (Z)V\n\tpublic final fun addPublicModuleDependencies (Z)V\n\tpublic final fun enableComposeUi (Z)V\n\tpublic final fun enableKotlinInject (Z)V\n\tpublic final fun enableMetro (Z)V\n\tpublic final fun enableModuleStructure (Z)V\n\tpublic final fun enableMoleculePresenters (Z)V\n}\n\npublic class software/amazon/app/platform/gradle/AppPlatformPlugin : org/gradle/api/Plugin {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/gradle/AppPlatformPlugin$Companion;\n\tpublic fun <init> ()V\n\tpublic synthetic fun apply (Ljava/lang/Object;)V\n\tpublic fun apply (Lorg/gradle/api/Project;)V\n\tpublic static final fun exportedDependencies ()Ljava/util/Set;\n}\n\npublic final class software/amazon/app/platform/gradle/AppPlatformPlugin$Companion {\n\tpublic final fun exportedDependencies ()Ljava/util/Set;\n}\n\npublic abstract class software/amazon/app/platform/gradle/ModuleStructureDependencyCheckTask : org/gradle/api/DefaultTask {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/gradle/ModuleStructureDependencyCheckTask$Companion;\n\tpublic fun <init> ()V\n\tpublic final fun checkDependencies ()V\n\tpublic abstract fun getIgnoredOutputFile ()Ljava/io/File;\n\tpublic abstract fun getModuleCompileClasspath ()Ljava/util/Set;\n\tpublic abstract fun getModulePath ()Ljava/lang/String;\n\tpublic abstract fun setIgnoredOutputFile (Ljava/io/File;)V\n\tpublic abstract fun setModuleCompileClasspath (Ljava/util/Set;)V\n\tpublic abstract fun setModulePath (Ljava/lang/String;)V\n}\n\npublic final class software/amazon/app/platform/gradle/ModuleStructureDependencyCheckTask$Companion {\n\tpublic final fun registerModuleStructureDependencyCheckTask (Lorg/gradle/api/Project;)V\n}\n\npublic class software/amazon/app/platform/gradle/ModuleStructurePlugin : org/gradle/api/Plugin {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/gradle/ModuleStructurePlugin$Companion;\n\tpublic fun <init> ()V\n\tpublic synthetic fun apply (Ljava/lang/Object;)V\n\tpublic fun apply (Lorg/gradle/api/Project;)V\n}\n\npublic final class software/amazon/app/platform/gradle/ModuleStructurePlugin$Companion {\n\tpublic final fun artifactId (Lorg/gradle/api/Project;Ljava/lang/String;)Ljava/lang/String;\n\tpublic static synthetic fun artifactId$default (Lsoftware/amazon/app/platform/gradle/ModuleStructurePlugin$Companion;Lorg/gradle/api/Project;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;\n\tpublic final fun namespace (Lorg/gradle/api/Project;)Ljava/lang/String;\n}\n\npublic final class software/amazon/app/platform/gradle/ModuleType : java/lang/Enum {\n\tpublic static final field APP Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field IMPL Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field IMPL_ROBOTS Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field INTERNAL Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field INTERNAL_ROBOTS Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field PUBLIC Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field PUBLIC_ROBOTS Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field TESTING Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final field UNKNOWN Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static fun getEntries ()Lkotlin/enums/EnumEntries;\n\tpublic final fun getUseTestDependenciesInMain ()Z\n\tpublic final fun isRobotsModule ()Z\n\tpublic static fun valueOf (Ljava/lang/String;)Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static fun values ()[Lsoftware/amazon/app/platform/gradle/ModuleType;\n}\n\npublic final class software/amazon/app/platform/gradle/ModuleTypeKt {\n\tpublic static final fun getModuleType (Lorg/gradle/api/Project;)Lsoftware/amazon/app/platform/gradle/ModuleType;\n\tpublic static final fun isAnyImplModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isAnyInternalModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isAnyPublicModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isAppModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isImplModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isInternalModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isPublicModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isRobotsModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isTestingModule (Lorg/gradle/api/Project;)Z\n\tpublic static final fun isUsingModuleStructure (Lorg/gradle/api/Project;)Z\n}\n\n"
  },
  {
    "path": "gradle-plugin/build.gradle",
    "content": "//file:noinspection UnnecessaryQualifiedReference\nplugins {\n    id 'java-gradle-plugin'\n    alias libs.plugins.kotlin.jvm\n    alias libs.plugins.ktfmt\n    alias libs.plugins.build.config\n    alias libs.plugins.maven.publish\n    alias libs.plugins.detekt\n    alias libs.plugins.kotlinx.binaryCompatibilityValidator\n    alias libs.plugins.android.lint\n}\n\nktfmt {\n    googleStyle()\n    trailingCommaManagementStrategy.set(com.ncorti.ktfmt.gradle.TrailingCommaManagementStrategy.COMPLETE)\n    removeUnusedImports.set(true)\n}\n\nmavenPublishing {\n    pom {\n        name = \"App Platform Gradle Plugin\"\n    }\n}\n\ngradlePlugin {\n    plugins {\n        appPlatformPlugin {\n            id = \"software.amazon.app.platform\"\n            displayName = \"App Platform Gradle Plugin\"\n            implementationClass = \"software.amazon.app.platform.gradle.AppPlatformPlugin\"\n            description = \"The Gradle plugin to make the integration of the App Platform easy.\"\n        }\n    }\n}\n\nbuildConfig {\n    buildConfigField(String, 'KOTLIN_INJECT_VERSION', libs.versions.kotlin.inject.asProvider().get())\n    buildConfigField(String, 'KOTLIN_INJECT_ANVIL_VERSION', libs.versions.kotlin.inject.anvil.get())\n    buildConfigField(String, 'APP_PLATFORM_GROUP', property('GROUP'))\n    buildConfigField(String, 'APP_PLATFORM_VERSION', property('VERSION_NAME'))\n    buildConfigField(String, 'MOLECULE_VERSION', libs.versions.molecule.get())\n    buildConfigField(String, 'ANDROID_COMPOSE_VERSION', libs.versions.android.compose.version.get())\n    buildConfigField(String, 'COMPOSE_MULTIPLATFORM_VERSION', libs.versions.compose.multiplatform.get())\n}\n\ndependencies {\n    implementation libs.kotlin.gradle.plugin.api\n\n    // The Compose plugin is needed for Molecule and not Compose Multiplatform.\n    implementation libs.kotlin.compose.gradle.plugin\n    implementation libs.compose.gradle.plugin\n\n    // This is needed to reference KspExperimental for experimental features.\n    compileOnly libs.ksp.api\n    implementation libs.ksp.gradle.plugin\n\n    // compileOnly to not set a minimum version for any consumers of this Gradle plugin and\n    // because AGP is purely optional. Usage of AGP APIs is gated by checks when the plugin\n    // is applied.\n    compileOnly libs.android.gradle.plugin.api\n\n    // compileOnly, because not every consumer of this Gradle plugin will use KMP. All usages\n    // are guarded by checks when the plugin is applied.\n    compileOnly libs.kotlin.multiplatform.gradle.plugin\n\n    lintChecks libs.androidx.lint.gradle\n}\n\njava {\n    sourceCompatibility = libs.versions.jvm.compatibility.get()\n    targetCompatibility = libs.versions.jvm.compatibility.get()\n}\n\nkotlin {\n    compilerOptions {\n        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(libs.versions.jvm.compatibility.get()))\n        allWarningsAsErrors.set(true)\n    }\n\n    explicitApi()\n}\n\ntasks.withType(ValidatePlugins).configureEach {\n    it.enableStricterValidation = true\n}\n\ntasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach {\n    it.jvmTarget = libs.versions.jvm.compatibility.get()\n    it.setSource(layout.files(\"src\"))\n}\n\n//noinspection UnnecessaryQualifiedReference\ntasks.withType(io.gitlab.arturbosch.detekt.DetektCreateBaselineTask).configureEach {\n    it.jvmTarget = libs.versions.jvm.compatibility.get()\n    it.setSource(layout.files(\"src\"))\n}\n\ndetekt {\n    config.from(file('../gradle/detekt-config.yml'))\n    buildUponDefaultConfig = true\n}\n\ntasks.register('release') {\n    dependsOn('build', 'check', 'ktfmtCheck', 'detekt', 'apiCheck')\n}\n"
  },
  {
    "path": "gradle-plugin/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n    }\n}\n\ndependencyResolutionManagement {\n    repositories {\n        mavenCentral()\n        google()\n        gradlePluginPortal()\n\n        maven {\n            url = \"https://central.sonatype.com/repository/maven-snapshots/\"\n        }\n    }\n\n    versionCatalogs {\n        libs {\n            from files('../gradle/libs.versions.toml')\n        }\n    }\n}\n\nrootProject.name = 'gradle-plugin'\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/AppPlatformExtension.kt",
    "content": "package software.amazon.app.platform.gradle\n\nimport com.google.devtools.ksp.gradle.KspExtension\nimport gradle_plugin.BuildConfig.ANDROID_COMPOSE_VERSION\nimport gradle_plugin.BuildConfig.APP_PLATFORM_GROUP\nimport gradle_plugin.BuildConfig.APP_PLATFORM_VERSION\nimport gradle_plugin.BuildConfig.COMPOSE_MULTIPLATFORM_VERSION\nimport gradle_plugin.BuildConfig.KOTLIN_INJECT_ANVIL_VERSION\nimport gradle_plugin.BuildConfig.KOTLIN_INJECT_VERSION\nimport gradle_plugin.BuildConfig.MOLECULE_VERSION\nimport javax.inject.Inject\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.dsl.DependencyHandler\nimport org.gradle.api.model.ObjectFactory\nimport org.gradle.api.provider.Property\nimport org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType\nimport org.jetbrains.kotlin.gradle.plugin.KotlinTarget\nimport org.jetbrains.kotlin.gradle.plugin.NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME\nimport org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME\nimport software.amazon.app.platform.gradle.ModuleStructurePlugin.Companion.testingSourceSets\n\n/**\n * The extension to configure the App Platform. Following options are available:\n * ```\n * appPlatform {\n *   enableKotlinInject true // false is the default\n *   enableMetro true // false is the default\n *\n *   enableMoleculePresenters true // false is the default\n *   enableModuleStructure true // false is the default\n *   enableComposeUi true // false is the default\n *\n *   addPublicModuleDependencies true // false is the default\n *   addImplModuleDependencies true // false is the default\n * }\n * ```\n */\n@Suppress(\"TooManyFunctions\", \"unused\")\npublic open class AppPlatformExtension\n@Inject\nconstructor(objects: ObjectFactory, private val project: Project) {\n  private val enableKotlinInject: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Adds KSP and kotlin-inject as dependency. */\n  public fun enableKotlinInject(enabled: Boolean) {\n    if (enabled == enableKotlinInject.get()) return\n\n    enableKotlinInject.set(enabled)\n    enableKotlinInject.disallowChanges()\n\n    if (enabled) {\n      addPublicModuleDependencies(true)\n      project.enableKotlinInject()\n    }\n  }\n\n  internal fun isKotlinInjectEnabled(): Property<Boolean> = enableKotlinInject\n\n  private val enableMetro: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Adds Metro as dependency. */\n  public fun enableMetro(enabled: Boolean) {\n    if (enabled == enableMetro.get()) return\n\n    enableMetro.set(enabled)\n    enableMetro.disallowChanges()\n\n    if (enabled) {\n      addPublicModuleDependencies(true)\n      project.enableMetro()\n    }\n  }\n\n  internal fun isMetroEnabled(): Property<Boolean> = enableMetro\n\n  private val enableMoleculePresenters: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Adds the Molecule Gradle plugin as dependency and gives access to `MoleculePresenter`. */\n  public fun enableMoleculePresenters(enabled: Boolean) {\n    if (enabled == enableMoleculePresenters.get()) return\n\n    enableMoleculePresenters.set(enabled)\n    enableMoleculePresenters.disallowChanges()\n\n    if (enabled) {\n      addPublicModuleDependencies(true)\n      project.enableMoleculePresenters()\n    }\n  }\n\n  internal fun isMoleculeEnabled(): Property<Boolean> = enableMoleculePresenters\n\n  private val enableComposeUi: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Adds necessary dependencies to use Compose Multiplatform with Renderers. */\n  public fun enableComposeUi(enabled: Boolean) {\n    if (enabled == enableComposeUi.get()) return\n\n    enableComposeUi.set(enabled)\n    enableComposeUi.disallowChanges()\n\n    if (enabled) {\n      addPublicModuleDependencies(true)\n      project.enableComposeUi()\n    }\n  }\n\n  internal fun isComposeUiEnabled(): Property<Boolean> = enableComposeUi\n\n  private val addImplModuleDependencies: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /**\n   * Adds a dependency on all :impl modules. This is helpful for application modules that import all\n   * implementations.\n   */\n  public fun addImplModuleDependencies(add: Boolean) {\n    addImplModuleDependencies.set(add)\n    addImplModuleDependencies.finalizeValueOnRead()\n\n    if (add) {\n      addPublicModuleDependencies(true)\n    }\n  }\n\n  internal fun isAddImplModuleDependencies(): Property<Boolean> = addImplModuleDependencies\n\n  private val addPublicModuleDependencies: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Adds dependencies on `:public` modules for the Presenters, Renderers and Scopes. */\n  public fun addPublicModuleDependencies(add: Boolean) {\n    addPublicModuleDependencies.set(add)\n    addPublicModuleDependencies.finalizeValueOnRead()\n  }\n\n  internal fun isAddPublicModuleDependencies(): Property<Boolean> = addPublicModuleDependencies\n\n  private val enableModuleStructure: Property<Boolean> =\n    objects.property(Boolean::class.java).convention(false)\n\n  /** Sets up this module to use our recommended module structure and applies certain defaults. */\n  public fun enableModuleStructure(enable: Boolean) {\n    if (enable == enableModuleStructure.get()) return\n\n    enableModuleStructure.set(enable)\n    enableModuleStructure.disallowChanges()\n\n    if (enable) {\n      project.plugins.apply(ModuleStructurePlugin::class.java)\n    }\n  }\n\n  internal fun isModuleStructureEnabled(): Property<Boolean> = enableModuleStructure\n\n  internal companion object {\n    internal val Project.appPlatform: AppPlatformExtension\n      get() = extensions.getByType(AppPlatformExtension::class.java)\n  }\n}\n\n@Suppress(\"LongMethod\")\nprivate fun Project.enableKotlinInject() {\n  plugins.apply(PluginIds.KSP)\n\n  val kspExtension = extensions.getByType(KspExtension::class.java)\n\n  // Disable this processor, because we implement our own version in order to support the\n  // Scoped interface.\n  kspExtension.arg(\n    \"software.amazon.lastmile.kotlin.inject.anvil.processor.ContributesBindingProcessor\",\n    \"disabled\",\n  )\n\n  fun DependencyHandler.addKspProcessorDependencies(kspConfigurationName: String) {\n    add(kspConfigurationName, \"me.tatarka.inject:kotlin-inject-compiler-ksp:$KOTLIN_INJECT_VERSION\")\n    add(\n      kspConfigurationName,\n      \"$APP_PLATFORM_GROUP:kotlin-inject-contribute-public:$APP_PLATFORM_VERSION\",\n    )\n    add(\n      kspConfigurationName,\n      \"$APP_PLATFORM_GROUP:kotlin-inject-contribute-impl-code-generators:$APP_PLATFORM_VERSION\",\n    )\n    add(\n      kspConfigurationName,\n      \"software.amazon.lastmile.kotlin.inject.anvil:compiler:$KOTLIN_INJECT_ANVIL_VERSION\",\n    )\n  }\n\n  plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(\"me.tatarka.inject:kotlin-inject-runtime:$KOTLIN_INJECT_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:kotlin-inject-public:$APP_PLATFORM_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:kotlin-inject-contribute-public:$APP_PLATFORM_VERSION\")\n      implementation(\n        \"software.amazon.lastmile.kotlin.inject.anvil:runtime:$KOTLIN_INJECT_ANVIL_VERSION\"\n      )\n      implementation(\n        \"software.amazon.lastmile.kotlin.inject.anvil:runtime-optional:\" +\n          KOTLIN_INJECT_ANVIL_VERSION\n      )\n    }\n\n    kmpExtension.targets.configureEach { target ->\n      addKspDependenciesWhenConfigExists(target) { configName ->\n        dependencies.addKspProcessorDependencies(configName)\n      }\n    }\n  }\n\n  plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n    dependencies.add(\"implementation\", \"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n    dependencies.add(\n      \"implementation\",\n      \"$APP_PLATFORM_GROUP:kotlin-inject-public:$APP_PLATFORM_VERSION\",\n    )\n    dependencies.add(\n      \"implementation\",\n      \"$APP_PLATFORM_GROUP:kotlin-inject-contribute-public:$APP_PLATFORM_VERSION\",\n    )\n    dependencies.add(\n      \"implementation\",\n      \"software.amazon.lastmile.kotlin.inject.anvil:runtime:$KOTLIN_INJECT_ANVIL_VERSION\",\n    )\n    dependencies.add(\n      \"implementation\",\n      \"software.amazon.lastmile.kotlin.inject.anvil:runtime-optional:$KOTLIN_INJECT_ANVIL_VERSION\",\n    )\n    dependencies.add(\n      \"implementation\",\n      \"me.tatarka.inject:kotlin-inject-runtime:$KOTLIN_INJECT_VERSION\",\n    )\n    dependencies.addKspProcessorDependencies(\"ksp\")\n  }\n}\n\nprivate fun Project.enableMetro() {\n  plugins.apply(PluginIds.METRO)\n\n  val useMetroKsp =\n    providers.gradleProperty(\"app.platform.metro.ksp\").map(String::toBoolean).orElse(false).get()\n\n  if (useMetroKsp) {\n    enableMetroKsp()\n  } else {\n    enableMetroCompilerPlugin()\n  }\n}\n\nprivate fun Project.enableMetroKsp() {\n  plugins.apply(PluginIds.KSP)\n\n  fun DependencyHandler.addKspProcessorDependencies(kspConfigurationName: String) {\n    add(\n      kspConfigurationName,\n      \"$APP_PLATFORM_GROUP:metro-contribute-impl-code-generators:$APP_PLATFORM_VERSION\",\n    )\n  }\n\n  plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(\"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:metro-public:$APP_PLATFORM_VERSION\")\n    }\n\n    kmpExtension.targets.configureEach { target ->\n      addKspDependenciesWhenConfigExists(target) { configName ->\n        dependencies.addKspProcessorDependencies(configName)\n      }\n    }\n  }\n\n  plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n    dependencies.add(\"implementation\", \"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n    dependencies.add(\"implementation\", \"$APP_PLATFORM_GROUP:metro-public:$APP_PLATFORM_VERSION\")\n    dependencies.addKspProcessorDependencies(\"ksp\")\n  }\n}\n\nprivate fun Project.enableMetroCompilerPlugin() {\n  fun DependencyHandler.addCompilerPluginDependencies() {\n    add(\n      PLUGIN_CLASSPATH_CONFIGURATION_NAME,\n      \"$APP_PLATFORM_GROUP:metro-contribute-impl-compiler-plugin:$APP_PLATFORM_VERSION\",\n    )\n    add(\n      NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME,\n      \"$APP_PLATFORM_GROUP:metro-contribute-impl-compiler-plugin:$APP_PLATFORM_VERSION\",\n    )\n  }\n\n  plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(\"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:metro-public:$APP_PLATFORM_VERSION\")\n    }\n    dependencies.addCompilerPluginDependencies()\n  }\n\n  plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n    dependencies.add(\"implementation\", \"$APP_PLATFORM_GROUP:di-common-public:$APP_PLATFORM_VERSION\")\n\n    dependencies.add(\"implementation\", \"$APP_PLATFORM_GROUP:metro-public:$APP_PLATFORM_VERSION\")\n\n    dependencies.addCompilerPluginDependencies()\n  }\n}\n\nprivate fun Project.enableMoleculePresenters() {\n  plugins.apply(PluginIds.COMPOSE_COMPILER)\n\n  plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(\"app.cash.molecule:molecule-runtime:$MOLECULE_VERSION\")\n      implementation(\"$APP_PLATFORM_GROUP:presenter-molecule-public:$APP_PLATFORM_VERSION\")\n    }\n    testingSourceSets.forEach { sourceSetName ->\n      kmpExtension.sourceSets.getByName(sourceSetName).dependencies {\n        implementation(\"$APP_PLATFORM_GROUP:presenter-molecule-testing:$APP_PLATFORM_VERSION\")\n      }\n    }\n  }\n\n  plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n    dependencies.add(\"implementation\", \"app.cash.molecule:molecule-runtime:$MOLECULE_VERSION\")\n    dependencies.add(\n      \"implementation\",\n      \"$APP_PLATFORM_GROUP:presenter-molecule-public:$APP_PLATFORM_VERSION\",\n    )\n    testingSourceSets.forEach { sourceSetName ->\n      dependencies.add(\n        sourceSetName,\n        \"$APP_PLATFORM_GROUP:presenter-molecule-testing:$APP_PLATFORM_VERSION\",\n      )\n    }\n  }\n}\n\nprivate fun Project.enableComposeUi() {\n  plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n    plugins.apply(PluginIds.COMPOSE_COMPILER)\n    plugins.apply(PluginIds.COMPOSE_MULTIPLATFORM)\n\n    kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n      implementation(\"org.jetbrains.compose.foundation:foundation:$COMPOSE_MULTIPLATFORM_VERSION\")\n      implementation(\"org.jetbrains.compose.runtime:runtime:$COMPOSE_MULTIPLATFORM_VERSION\")\n\n      implementation(\n        \"$APP_PLATFORM_GROUP:renderer-compose-multiplatform-public:$APP_PLATFORM_VERSION\"\n      )\n\n      if (isRobotsModule()) {\n        implementation(\n          \"$APP_PLATFORM_GROUP:robot-compose-multiplatform-public:$APP_PLATFORM_VERSION\"\n        )\n      }\n    }\n  }\n\n  plugins.withIds(PluginIds.KOTLIN_ANDROID) {\n    plugins.apply(PluginIds.COMPOSE_COMPILER)\n\n    android.buildFeatures.compose = true\n\n    dependencies.add(\"implementation\", \"androidx.compose.runtime:runtime:$ANDROID_COMPOSE_VERSION\")\n    dependencies.add(\n      \"implementation\",\n      \"androidx.compose.foundation:foundation:$ANDROID_COMPOSE_VERSION\",\n    )\n    dependencies.add(\n      \"implementation\",\n      \"$APP_PLATFORM_GROUP:renderer-compose-multiplatform-public:$APP_PLATFORM_VERSION\",\n    )\n\n    if (isRobotsModule()) {\n      dependencies.add(\n        \"implementation\",\n        \"$APP_PLATFORM_GROUP:robot-compose-multiplatform-public:$APP_PLATFORM_VERSION\",\n      )\n    }\n  }\n\n  plugins.withIds(PluginIds.ANDROID_APP, PluginIds.ANDROID_LIBRARY) {\n    android.buildFeatures.compose = true\n\n    if (isAppModule()) {\n      dependencies.add(\n        \"androidTestImplementation\",\n        \"$APP_PLATFORM_GROUP:robot-compose-multiplatform-public:$APP_PLATFORM_VERSION\",\n      )\n    }\n  }\n}\n\nprivate fun Project.addKspDependenciesWhenConfigExists(\n  target: KotlinTarget,\n  block: (String) -> Unit,\n) {\n  if (target.name != \"metadata\") {\n    target.compilations.configureEach { compilation ->\n      fun configExists(name: String): Boolean = configurations.any { it.name == name }\n\n      // Derive the KSP configuration name from the target name and compilation name.\n      // For main compilations: ksp<TargetName> (e.g. kspDesktop, kspIosSimulatorArm64)\n      // For test compilations: ksp<TargetName>Test (e.g. kspDesktopTest)\n      val targetName = target.name.capitalize()\n      var configName =\n        if (compilation.name == \"main\") {\n          \"ksp$targetName\"\n        } else {\n          \"ksp$targetName${compilation.name.capitalize()}\"\n        }\n\n      if (!configExists(configName) && target.platformType == KotlinPlatformType.androidJvm) {\n        // Android has different naming for some reason.\n        //\n        // E.g. for instrumentation tests 'kspAndroidDebugAndroidTest' should actually be\n        // 'kspAndroidAndroidTestDebug', but we will use 'kspAndroidAndroidTest'.\n        //\n        // For unit tests 'kspAndroidDebugUnitTest' should actually be 'kspAndroidTestDebug',\n        // but we will use 'kspAndroidTest'.\n        when {\n          configName.endsWith(\"AndroidTest\") -> configName = \"kspAndroidAndroidTest\"\n          configName.endsWith(\"UnitTest\") -> configName = \"kspAndroidTest\"\n        }\n      }\n\n      // Check again if the config exists.\n      if (configExists(configName)) {\n        block(configName)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/AppPlatformPlugin.kt",
    "content": "package software.amazon.app.platform.gradle\n\nimport gradle_plugin.BuildConfig.APP_PLATFORM_GROUP\nimport gradle_plugin.BuildConfig.APP_PLATFORM_VERSION\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport software.amazon.app.platform.gradle.AppPlatformExtension.Companion.appPlatform\nimport software.amazon.app.platform.gradle.ModuleStructurePlugin.Companion.testingSourceSets\n\n/** The Gradle plugin to make the integration of the App Platform easy. */\n@Suppress(\"unused\")\npublic open class AppPlatformPlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.extensions.create(\"appPlatform\", AppPlatformExtension::class.java)\n\n    target.afterEvaluate {\n      target.addPublicDependencies()\n      target.addImplDependencies()\n    }\n  }\n\n  @Suppress(\"LongMethod\")\n  private fun Project.addPublicDependencies() {\n    if (!appPlatform.isAddPublicModuleDependencies().get()) {\n      // If disabled, then don't add these dependencies.\n      return\n    }\n\n    val implementationDependencies =\n      setOf(\n        \"$APP_PLATFORM_GROUP:presenter-public:$APP_PLATFORM_VERSION\",\n        \"$APP_PLATFORM_GROUP:renderer-public:$APP_PLATFORM_VERSION\",\n        \"$APP_PLATFORM_GROUP:scope-public:$APP_PLATFORM_VERSION\",\n      )\n    val testImplementationDependencies =\n      setOf(\"$APP_PLATFORM_GROUP:scope-testing:$APP_PLATFORM_VERSION\")\n    val robotDependencies = setOf(\"$APP_PLATFORM_GROUP:robot-public:$APP_PLATFORM_VERSION\")\n\n    plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n      kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n        implementationDependencies.forEach { dep -> implementation(dep) }\n        if (isRobotsModule()) {\n          robotDependencies.forEach { dep -> implementation(dep) }\n        }\n      }\n      testingSourceSets.forEach { sourceSetName ->\n        kmpExtension.sourceSets.getByName(sourceSetName).dependencies {\n          testImplementationDependencies.forEach { dep -> implementation(dep) }\n        }\n      }\n    }\n\n    plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n      implementationDependencies.forEach { dep -> dependencies.add(\"implementation\", dep) }\n      testingSourceSets.forEach { sourceSetName ->\n        testImplementationDependencies.forEach { dep -> dependencies.add(sourceSetName, dep) }\n      }\n      if (isRobotsModule()) {\n        robotDependencies.forEach { dep -> dependencies.add(\"implementation\", dep) }\n      }\n    }\n\n    plugins.withId(PluginIds.ANDROID_KMP_LIBRARY) {\n      dependencies.add(\n        \"androidMainImplementation\",\n        \"$APP_PLATFORM_GROUP:renderer-android-view-public:$APP_PLATFORM_VERSION\",\n      )\n    }\n\n    plugins.withIds(PluginIds.ANDROID_APP, PluginIds.ANDROID_LIBRARY) {\n      dependencies.add(\n        \"implementation\",\n        \"$APP_PLATFORM_GROUP:renderer-android-view-public:$APP_PLATFORM_VERSION\",\n      )\n\n      if (isAppModule()) {\n        robotDependencies.forEach { dep -> dependencies.add(\"androidTestImplementation\", dep) }\n      }\n    }\n  }\n\n  private fun Project.addImplDependencies() {\n    if (!appPlatform.isAddImplModuleDependencies().get()) {\n      // If disabled, then don't add these dependencies.\n      return\n    }\n\n    val implementationDependencies = buildSet {\n      if (appPlatform.isMoleculeEnabled().get()) {\n        add(\"$APP_PLATFORM_GROUP:presenter-molecule-impl:$APP_PLATFORM_VERSION\")\n      }\n      if (appPlatform.isKotlinInjectEnabled().get()) {\n        add(\"$APP_PLATFORM_GROUP:kotlin-inject-impl:$APP_PLATFORM_VERSION\")\n      }\n      if (appPlatform.isMetroEnabled().get()) {\n        add(\"$APP_PLATFORM_GROUP:metro-impl:$APP_PLATFORM_VERSION\")\n      }\n    }\n\n    plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n      kmpExtension.sourceSets.getByName(\"commonMain\").dependencies {\n        implementationDependencies.forEach { dep -> implementation(dep) }\n      }\n    }\n\n    plugins.withIds(PluginIds.KOTLIN_ANDROID, PluginIds.KOTLIN_JVM) {\n      implementationDependencies.forEach { dep -> dependencies.add(\"implementation\", dep) }\n    }\n  }\n\n  public companion object {\n    /**\n     * Returns the set of dependencies that need to be exported in a Framework for native targets in\n     * order to make App Platform work.\n     */\n    @JvmStatic\n    public fun exportedDependencies(): Set<String> =\n      setOf(\n          \"di-common-public\",\n          \"kotlin-inject-contribute-public\",\n          \"kotlin-inject-impl\",\n          \"kotlin-inject-public\",\n          \"metro-impl\",\n          \"metro-public\",\n          \"presenter-molecule-impl\",\n          \"presenter-molecule-public\",\n          \"presenter-public\",\n          \"renderer-compose-multiplatform-public\",\n          \"renderer-public\",\n          \"scope-public\",\n        )\n        .mapTo(mutableSetOf()) { \"$APP_PLATFORM_GROUP:$it:$APP_PLATFORM_VERSION\" }\n  }\n}\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/GradleExtensions.kt",
    "content": "package software.amazon.app.platform.gradle\n\nimport com.android.build.api.dsl.CommonExtension\nimport com.android.build.api.variant.AndroidComponentsExtension\nimport java.util.Locale\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.api.Task\nimport org.gradle.api.UnknownTaskException\nimport org.gradle.api.plugins.PluginContainer\nimport org.gradle.api.project.IsolatedProject\nimport org.gradle.api.tasks.TaskContainer\nimport org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension\n\ninternal fun PluginContainer.withIds(vararg pluginIds: String, action: (Plugin<*>) -> Unit) {\n  pluginIds.forEach { id -> withId(id) { action(it) } }\n}\n\n// This is OK because no properties within parent are accessed\n// https://github.com/gradle/gradle/issues/33198\n@Suppress(\"GradleProjectIsolation\")\ninternal fun Project.requireParent(): IsolatedProject =\n  requireNotNull(parent) {\n      \"The parent project for a module enabling the module structure should not be null.\"\n    }\n    .isolated\n\ninternal val Project.isKmpModule: Boolean\n  get() = plugins.hasPlugin(PluginIds.KOTLIN_MULTIPLATFORM)\n\ninternal val Project.android: CommonExtension<*, *, *, *, *, *>\n  get() = extensions.getByType(CommonExtension::class.java)\n\ninternal val Project.androidComponents: AndroidComponentsExtension<*, *, *>\n  get() = extensions.getByType(AndroidComponentsExtension::class.java)\n\ninternal val Project.kmpExtension: KotlinMultiplatformExtension\n  get() = extensions.getByType(KotlinMultiplatformExtension::class.java)\n\ninternal fun TaskContainer.namedOptional(name: String, configurationAction: (Task) -> Unit) {\n  try {\n    named(name, configurationAction)\n  } catch (_: UnknownTaskException) {}\n}\n\ninternal fun String.capitalize(): String = replaceFirstChar {\n  if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString()\n}\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/ModuleStructureDependencyCheckTask.kt",
    "content": "package software.amazon.app.platform.gradle\n\nimport java.io.File\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.GradleException\nimport org.gradle.api.Project\nimport org.gradle.api.artifacts.Configuration\nimport org.gradle.api.artifacts.ExternalDependency\nimport org.gradle.api.artifacts.ProjectDependency\nimport org.gradle.api.tasks.CacheableTask\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.Optional\nimport org.gradle.api.tasks.OutputFile\nimport org.gradle.api.tasks.TaskAction\n\n/** Checks that our module structure dependency rules are followed. */\n@CacheableTask\npublic abstract class ModuleStructureDependencyCheckTask : DefaultTask() {\n\n  /** The path of this module, e.g. `:presenter:public`. */\n  @get:Input public abstract var modulePath: String\n\n  /** All Gradle modules on the compile classpath. */\n  @get:Input public abstract var moduleCompileClasspath: Set<String>\n\n  /** An empty output makes the task work with up-to-date checks. */\n  @Suppress(\"unused\") @get:OutputFile @get:Optional public abstract var ignoredOutputFile: File\n\n  init {\n    description = \"Checks that our module structure dependency rules are followed.\"\n    group = \"Verification\"\n  }\n\n  @TaskAction\n  @PublishedApi\n  internal fun checkDependencies() {\n    val moduleType = modulePath.moduleType\n\n    if (moduleType == ModuleType.PUBLIC) {\n      checkOnlyPublicModule()\n    }\n    if (moduleType != ModuleType.APP && moduleType != ModuleType.IMPL_ROBOTS) {\n      checkNoImplImport()\n    }\n    if (moduleType != ModuleType.TESTING && !moduleType.isRobotsModule) {\n      checkNoTestingImport()\n    }\n    if (!moduleType.isRobotsModule) {\n      checkNoRobotsImport()\n    }\n    if (moduleType != ModuleType.APP) {\n      checkNoInternalImportFromOtherLibrary()\n    }\n  }\n\n  private fun checkOnlyPublicModule() {\n    val forbiddenDependencies = moduleCompileClasspath.filter { it.moduleType != ModuleType.PUBLIC }\n\n    if (forbiddenDependencies.isNotEmpty()) {\n      throw GradleException(\n        \":public modules are only allowed to depend on other :public modules. \" +\n          \"Remove the dependencies: ${forbiddenDependencies.joinToString()} \" +\n          \"from $modulePath.\"\n      )\n    }\n  }\n\n  private fun checkNoImplImport() {\n    val forbiddenDependencies = moduleCompileClasspath.filter { it.moduleType == ModuleType.IMPL }\n\n    if (forbiddenDependencies.isNotEmpty()) {\n      throw GradleException(\n        \"No module except for an app module is allowed to import an :impl module. \" +\n          \"Remove the dependencies: ${forbiddenDependencies.joinToString()} \" +\n          \"from $modulePath.\"\n      )\n    }\n  }\n\n  private fun checkNoTestingImport() {\n    val forbiddenDependencies = moduleCompileClasspath.filter {\n      it.moduleType == ModuleType.TESTING\n    }\n\n    if (forbiddenDependencies.isNotEmpty()) {\n      throw GradleException(\n        \"Testing modules should be added to the test compile classpath, otherwise \" +\n          \"they're included in the final app. Remove the dependencies: \" +\n          \"${forbiddenDependencies.joinToString()} from $modulePath.\"\n      )\n    }\n  }\n\n  private fun checkNoRobotsImport() {\n    val forbiddenDependencies = moduleCompileClasspath.filter { it.moduleType.isRobotsModule }\n\n    if (forbiddenDependencies.isNotEmpty()) {\n      throw GradleException(\n        \"Robot modules should be added to the instrumented test compile classpath, \" +\n          \"otherwise they're included in the final app. Remove the dependencies: \" +\n          \"${forbiddenDependencies.joinToString()} from $modulePath.\"\n      )\n    }\n  }\n\n  private fun checkNoInternalImportFromOtherLibrary() {\n    val forbiddenDependencies =\n      moduleCompileClasspath\n        .filter { it.moduleType == ModuleType.INTERNAL }\n        .filter { dependency ->\n          // Usually :internal modules are part of the same Gradle project, therefore the\n          // dependency string starts with a colon \":\", e.g. :library:internal. If that's\n          // the case, then compare the parent path with this project's parent path. If\n          // they match, then the :internal dependency is allowed. If they don't match,\n          // then the dependency is forbidden.\n          //\n          // For external dependencies this check is much harder and for now we simply\n          // assume that the internal dependency isn't allowed.\n          if (dependency.startsWith(\":\")) {\n            dependency.substringBeforeLast(':') != modulePath.substringBeforeLast(':')\n          } else {\n            // It's an external dependency\n            true\n          }\n        }\n\n    if (forbiddenDependencies.isNotEmpty()) {\n      throw GradleException(\n        \"Internal modules can only be imported within the same library or by app \" +\n          \"modules, but not from another library. Remove the dependencies: \" +\n          \"${forbiddenDependencies.joinToString()} from $modulePath.\"\n      )\n    }\n  }\n\n  private val String.moduleType: ModuleType\n    get() =\n      if (startsWith(':')) {\n        moduleTypeFromProjectPath()\n      } else {\n        substringAfter(':').substringBefore(':').moduleTypeFromArtifactId()\n      }\n\n  public companion object {\n    /** Registers the task in the given project. */\n    public fun Project.registerModuleStructureDependencyCheckTask() {\n      val baseTaskName = \"checkModuleStructureDependencies\"\n      val baseTask =\n        tasks.register(baseTaskName) {\n          it.description = \"Checks that our module structure dependency rules for all targets.\"\n          it.group = \"Verification\"\n        }\n\n      afterEvaluate { tasks.namedOptional(\"check\") { it.dependsOn(baseTask) } }\n\n      fun registerForConfiguration(taskSuffix: String, configuration: () -> Configuration) {\n        val checkTask =\n          tasks.register(\n            \"$baseTaskName${taskSuffix.capitalize()}\",\n            ModuleStructureDependencyCheckTask::class.java,\n          ) { task ->\n            task.modulePath = path\n            task.moduleCompileClasspath =\n              configuration()\n                .allDependencies\n                .mapNotNull { dependency ->\n                  when (dependency) {\n                    is ExternalDependency -> {\n                      \"${dependency.group}:${dependency.name}:${dependency.version}\"\n                        .takeIf { dependency.name.moduleTypeFromArtifactId() != ModuleType.UNKNOWN }\n                    }\n\n                    is ProjectDependency -> {\n                      dependency.path.takeIf {\n                        it.moduleTypeFromProjectPath() != ModuleType.UNKNOWN\n                      }\n                    }\n\n                    else -> null\n                  }\n                }\n                .toSet()\n          }\n\n        baseTask.configure { it.dependsOn(checkTask) }\n      }\n\n      plugins.withIds(PluginIds.ANDROID_LIBRARY, PluginIds.ANDROID_APP) {\n        androidComponents.onVariants { variant ->\n          registerForConfiguration(\n            taskSuffix = \"android${variant.name.capitalize()}\",\n            configuration = { variant.compileConfiguration },\n          )\n        }\n      }\n\n      plugins.withId(PluginIds.KOTLIN_MULTIPLATFORM) {\n        kmpExtension.targets.configureEach { target ->\n          // We register Android above.\n          if (target.name == \"android\") return@configureEach\n\n          target.compilations.configureEach configureEach2@{ compilation ->\n            // We only care about main.\n            if (compilation.name != \"main\") return@configureEach2\n\n            registerForConfiguration(\n              taskSuffix = target.name,\n              configuration = {\n                configurations.getByName(compilation.compileDependencyConfigurationName)\n              },\n            )\n          }\n        }\n      }\n\n      plugins.withId(PluginIds.KOTLIN_JVM) {\n        registerForConfiguration(\n          taskSuffix = \"jvm\",\n          configuration = { configurations.getByName(\"compileClasspath\") },\n        )\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/ModuleStructurePlugin.kt",
    "content": "package software.amazon.app.platform.gradle\n\nimport com.android.build.api.dsl.androidLibrary\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport software.amazon.app.platform.gradle.ModuleStructureDependencyCheckTask.Companion.registerModuleStructureDependencyCheckTask\n\n/** The Gradle plugin that sets up our module structure. */\npublic open class ModuleStructurePlugin : Plugin<Project> {\n  override fun apply(target: Project) {\n    target.ensureFollowsNamingConvention()\n    target.addModuleStructureDependencies()\n    target.configureAndroidNamespace()\n    target.registerModuleStructureDependencyCheckTask()\n  }\n\n  private fun Project.ensureFollowsNamingConvention() {\n    check(isUsingModuleStructure()) {\n      \"$path enables the module structure, but the project name doesn't follow the naming convention.\"\n    }\n  }\n\n  private fun Project.addModuleStructureDependencies() {\n    plugins.withIds(\n      PluginIds.KOTLIN_MULTIPLATFORM,\n      PluginIds.KOTLIN_JVM,\n      PluginIds.KOTLIN_ANDROID,\n    ) {\n      val parent = requireParent()\n\n      // Nothing to add.\n      if (isPublicModule()) return@withIds\n\n      fun addPublicModule() {\n        // this is ok because no properties within publicModule are accessed\n        @Suppress(\"GradleProjectIsolation\") val publicModule = findProject(\"${parent.path}:public\")\n        if (publicModule != null) {\n          if (isKmpModule) {\n            dependencies.add(\"commonMainApi\", publicModule)\n          } else {\n            dependencies.add(\"api\", publicModule)\n          }\n        }\n      }\n\n      when {\n        isTestingModule() -> {\n          // :testing modules provide helper functions or fake implementations of the\n          // APIs in the :public module.\n          addPublicModule()\n        }\n\n        isImplModule() || isInternalModule() -> {\n          // :impl and :internal modules implement interfaces and types from the :public\n          // module.\n          addPublicModule()\n        }\n\n        isRobotsModule() -> {\n          // :robot modules usually reference types from the :public and :impl modules.\n          addPublicModule()\n\n          // Add a dependency to the implementation module. Note that an \"implementation\"\n          // dependency is chosen rather than an \"api\" dependency. The goal of the a\n          // robots module to hide all details of the :impl module and only expose\n          // abstractions with the help of robots.\n          @Suppress(\"GradleProjectIsolation\") // no properties within project are accessed\n          findProject(path.substringBefore(\"-robots\"))\n            ?.takeIf { it.isImplModule() }\n            ?.let { implModule ->\n              if (isKmpModule) {\n                dependencies.add(\"commonMainImplementation\", implModule)\n              } else {\n                dependencies.add(\"implementation\", implModule)\n              }\n            }\n        }\n      }\n    }\n  }\n\n  private fun Project.configureAndroidNamespace() {\n    plugins.withIds(PluginIds.ANDROID_APP, PluginIds.ANDROID_LIBRARY) {\n      // Do not override any configured namespace.\n      if (android.namespace == null) {\n        android.namespace = namespace()\n      }\n    }\n    plugins.withId(PluginIds.ANDROID_KMP_LIBRARY) {\n      @Suppress(\"UnstableApiUsage\")\n      kmpExtension.androidLibrary {\n        if (namespace == null) {\n          namespace = namespace()\n        }\n      }\n    }\n  }\n\n  public companion object {\n\n    /**\n     * Returns a consistent namespace for a Gradle module that has the recommended App Platform\n     * module structure in mind. It helps to avoid clashing namespaces across projects.\n     *\n     * This value can be used as namespace for Android projects and gets automatically set when no\n     * other namespace is declared.\n     *\n     * It requires that the `GROUP` property is set for the Gradle project.\n     *\n     * E.g. it produces following results:\n     * ```\n     * GROUP=software.amazon.abc\n     *\n     * :def:public  -> \"software.amazon.abc.def\"\n     * :def:impl  -> \"software.amazon.abc.def.impl\"\n     * :def:impl-ghj-robots  -> \"software.amazon.abc.def.impl.ghj.robots\"\n     * ```\n     *\n     * @see com.android.build.api.dsl.CommonExtension.namespace\n     */\n    public fun Project.namespace(): String {\n      val group =\n        providers.gradleProperty(\"GROUP\").let {\n          check(it.isPresent) {\n            \"Couldn't find the GROUP property for this project. Make sure you define \" +\n              \"a group in the project's gradle.properties file, e.g. `GROUP=\" +\n              \"software.amazon.abc`.\"\n          }\n          return@let it.get()\n        }\n\n      val path =\n        when {\n          isPublicModule() -> requireParent().path\n          isAnyPublicModule() && isRobotsModule() -> \"${requireParent().path}:robots\"\n          else -> path\n        }\n\n      return \"$group${path.replace(':', '.').replace('-', '.')}\"\n    }\n\n    /**\n     * Returns a consistent artifact ID for a Gradle module that has the recommended App Platform\n     * module structure in mind. This artifact ID should be used for publishing library modules.\n     *\n     * It produces following results:\n     * ```\n     * :abc:public  -> \"abc-public\"\n     * :abc:impl-def-robots  -> \"abc-impl-def-robots\"\n     * ```\n     */\n    public fun Project.artifactId(libraryName: String = requireParent().name): String {\n      return \"$libraryName-$name\"\n    }\n\n    internal val Project.testingSourceSets: List<String>\n      get() = buildList {\n        when {\n          plugins.hasPlugin(PluginIds.KOTLIN_MULTIPLATFORM) -> {\n            add(\"commonTest\")\n            if (moduleType.useTestDependenciesInMain) {\n              add(\"commonMain\")\n            }\n          }\n\n          plugins.hasPlugin(PluginIds.KOTLIN_ANDROID) ||\n            plugins.hasPlugin(PluginIds.KOTLIN_JVM) -> {\n            add(\"testImplementation\")\n            if (moduleType.useTestDependenciesInMain) {\n              add(\"implementation\")\n            }\n          }\n        }\n      }\n  }\n}\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/ModuleType.kt",
    "content": "@file:Suppress(\"TooManyFunctions\", \"unused\")\n\npackage software.amazon.app.platform.gradle\n\nimport org.gradle.api.Project\nimport software.amazon.app.platform.gradle.ModuleType.APP\nimport software.amazon.app.platform.gradle.ModuleType.IMPL\nimport software.amazon.app.platform.gradle.ModuleType.IMPL_ROBOTS\nimport software.amazon.app.platform.gradle.ModuleType.INTERNAL\nimport software.amazon.app.platform.gradle.ModuleType.INTERNAL_ROBOTS\nimport software.amazon.app.platform.gradle.ModuleType.PUBLIC\nimport software.amazon.app.platform.gradle.ModuleType.PUBLIC_ROBOTS\nimport software.amazon.app.platform.gradle.ModuleType.TESTING\nimport software.amazon.app.platform.gradle.ModuleType.UNKNOWN\n\n/** The type of module based on our module structure. */\npublic enum class ModuleType(\n  /** Whether this type is a robots module. Robot modules are used for instrumented tests. */\n  public val isRobotsModule: Boolean = false,\n\n  /**\n   * Whether dependencies that typically used only in tests are part of the main source set, e.g.\n   * that's the case for `:testing` and robot modules.\n   */\n  public val useTestDependenciesInMain: Boolean = false,\n) {\n  /**\n   * `:app` modules refer to the final application, where all feature implementations are imported\n   * and assembled as a single binary. Therefore, `:app` modules are allowed to depend on `:impl`\n   * modules of all imported libraries and features.\n   *\n   * App modules are leaf modules prefixed with \"app\" or live in a folder named \"app\".\n   */\n  APP,\n\n  /**\n   * `:public` modules contain the code that should be shared and reused by other modules and\n   * libraries. APIs (interfaces) usually live in `:public` modules, but also code where dependency\n   * inversion isn’t applied such as static utilities, extension functions and UI components.\n   */\n  PUBLIC,\n\n  /** `:public-robots` host robots or test code for a `:public` module. */\n  PUBLIC_ROBOTS(isRobotsModule = true, useTestDependenciesInMain = true),\n\n  /**\n   * `:testing` modules provide a mechanism to share utilities or fake implementations for tests\n   * with other libraries. `:testing` modules are allowed to be imported as test dependency by any\n   * other module type and are never added to the runtime classpath. Even its own `:public` module\n   * can reuse the code from the `:testing` module for its tests.\n   */\n  TESTING(useTestDependenciesInMain = true),\n\n  /**\n   * `:impl` modules contain the concrete implementations of the API from `:public` modules. A\n   * library can have zero or more `:impl` modules. If a library contains multiple `:impl` modules,\n   * then they’re suffixed, e.g. `:login:impl-amazon` and `:login:impl-google`.\n   */\n  IMPL,\n\n  /**\n   * `:*-robots` modules help implementing the robot pattern for UI tests and make them shareable.\n   * Robots must know about concrete implementations, therefore they usually depend on an `:impl`\n   * module, but don't expose this `:impl` module on the compile classpath. `:robot` modules are\n   * only imported and reused for UI tests and are never added as dependency to the runtime\n   * classpath of a module similar to `:testing` modules.\n   */\n  IMPL_ROBOTS(isRobotsModule = true, useTestDependenciesInMain = true),\n\n  /**\n   * `:internal` modules are used when code should be shared between multiple `:impl` modules of the\n   * same library, but the code should not be exposed through the `:public` module. This code is\n   * \"internal\" to this library.\n   */\n  INTERNAL,\n\n  /** `:internal-robots` host robots or test code for an `:internal` module. */\n  INTERNAL_ROBOTS(isRobotsModule = true, useTestDependenciesInMain = true),\n\n  /**\n   * The module type could not be parsed, likely because the module is not following the module\n   * structure.\n   */\n  UNKNOWN,\n}\n\n/** The type of module based on our module structure. */\npublic val Project.moduleType: ModuleType\n  get() = path.moduleTypeFromProjectPath()\n\ninternal fun String.moduleTypeFromProjectPath(): ModuleType {\n  val name = substringAfterLast(':')\n\n  val isRobots = name.endsWith(\"-robots\")\n\n  return when {\n    name.startsWith(\"public\") -> if (isRobots) PUBLIC_ROBOTS else PUBLIC\n    name == \"testing\" -> TESTING\n    name.startsWith(\"impl\") -> if (isRobots) IMPL_ROBOTS else IMPL\n    name.startsWith(\"internal\") -> if (isRobots) INTERNAL_ROBOTS else INTERNAL\n    contains(\":app:\") || name.startsWith(\"app\") -> APP\n    else -> UNKNOWN\n  }\n}\n\ninternal fun String.moduleTypeFromArtifactId(): ModuleType {\n  // E.g. abc-public, def-impl-xyz-robots\n  return when {\n    endsWith(\"-public-robots\") -> PUBLIC_ROBOTS\n    endsWith(\"-public\") -> PUBLIC\n    endsWith(\"-testing\") -> TESTING\n    endsWith(\"-impl\") -> IMPL\n    contains(\"-impl-\") -> if (endsWith(\"-robots\")) IMPL_ROBOTS else IMPL\n    endsWith(\"-internal\") -> INTERNAL\n    contains(\"-internal-\") -> if (endsWith(\"-robots\")) INTERNAL_ROBOTS else INTERNAL\n    this == \"app\" -> APP\n    startsWith(\"app-\") -> APP\n    else -> UNKNOWN\n  }\n}\n\n/**\n * Returns true for app modules. Typically, these modules are leaf modules prefixed with \"app\" or\n * live in a folder named \"app\".\n */\npublic fun Project.isAppModule(): Boolean = moduleType == APP\n\n/** Returns true for any public module including robots module. */\npublic fun Project.isAnyPublicModule(): Boolean =\n  moduleType == PUBLIC || moduleType == PUBLIC_ROBOTS\n\n/** Returns true for the public module of a library, but not subtypes, e.g. a robots module. */\npublic fun Project.isPublicModule(): Boolean = moduleType == PUBLIC\n\n/** Returns true for the testing module of a library. */\npublic fun Project.isTestingModule(): Boolean = moduleType == TESTING\n\n/** Returns true for any impl module including robots module. */\npublic fun Project.isAnyImplModule(): Boolean = moduleType == IMPL || moduleType == IMPL_ROBOTS\n\n/** Returns true for an impl module, but not subtypes, e.g. a robots module. */\npublic fun Project.isImplModule(): Boolean = moduleType == IMPL\n\n/** Returns true for an internal module, but not subtypes, e.g. a robots module. */\npublic fun Project.isAnyInternalModule(): Boolean =\n  moduleType == INTERNAL || moduleType == INTERNAL_ROBOTS\n\n/** Returns true for an internal module, but not subtypes, e.g. a robots module. */\npublic fun Project.isInternalModule(): Boolean = moduleType == INTERNAL\n\n/** Returns true for any robots module. */\npublic fun Project.isRobotsModule(): Boolean = moduleType.isRobotsModule\n\n/** Checks whether the project follows the naming convention of the module structure. */\npublic fun Project.isUsingModuleStructure(): Boolean = moduleType != UNKNOWN\n"
  },
  {
    "path": "gradle-plugin/src/main/kotlin/software/amazon/app/platform/gradle/PluginIds.kt",
    "content": "package software.amazon.app.platform.gradle\n\ninternal object PluginIds {\n  const val ANDROID_APP = \"com.android.application\"\n  const val ANDROID_KMP_LIBRARY = \"com.android.kotlin.multiplatform.library\"\n  const val ANDROID_LIBRARY = \"com.android.library\"\n  const val COMPOSE_COMPILER = \"org.jetbrains.kotlin.plugin.compose\"\n  const val COMPOSE_MULTIPLATFORM = \"org.jetbrains.compose\"\n  const val KOTLIN_MULTIPLATFORM = \"org.jetbrains.kotlin.multiplatform\"\n  const val KOTLIN_JVM = \"org.jetbrains.kotlin.jvm\"\n  const val KOTLIN_ANDROID = \"org.jetbrains.kotlin.android\"\n  const val KSP = \"com.google.devtools.ksp\"\n  const val METRO = \"dev.zacsweers.metro\"\n}\n"
  },
  {
    "path": "gradle.properties",
    "content": "VERSION_NAME=0.0.11-SNAPSHOT\nGROUP=software.amazon.app.platform\n\norg.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8\norg.gradle.daemon=true\norg.gradle.parallel=true\norg.gradle.caching=true\n\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.parallel=true\n\nkotlin.mpp.stability.nowarn=true\nkotlin.mpp.androidSourceSetLayoutVersion=2\nkotlin.mpp.enableCInteropCommonization=true\nkotlin.native.distribution.downloadFromMaven=true\n# https://youtrack.jetbrains.com/issue/KT-82395\nkotlin.incremental.js=false\nkotlin.incremental.js.klib=false\n\norg.jetbrains.compose.experimental.uikit.enabled=true\n\n# This property does not work when setting up publishing through the DSL as we do.\n# SONATYPE_AUTOMATIC_RELEASE=true\nSONATYPE_HOST=CENTRAL_PORTAL\n# Keep this set to false by default, otherwise publishing to Maven local is extremely slow. There is a bug:\n# https://github.com/gradle/gradle/issues/26256\nRELEASE_SIGNING_ENABLED=false\n\nPOM_DESCRIPTION=The App Platform is a lightweight application framework for state and memory management suitable for Kotlin Multiplatform projects, in particular Android, iOS, JVM, native and Web.\nPOM_INCEPTION_YEAR=2025\n\nPOM_URL=https://github.com/amzn/app-platform/\nPOM_SCM_URL=https://github.com/amzn/app-platform/\nPOM_SCM_CONNECTION=scm:git:git://github.com/amzn/app-platform.git\nPOM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/amzn/app-platform.git\n\nPOM_LICENCE_NAME=Apache-2.0\nPOM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0\nPOM_LICENCE_DIST=repo\n\nPOM_DEVELOPER_ID=last-mile-dat\nPOM_DEVELOPER_NAME=Driver Assistance Technology\nPOM_DEVELOPER_URL=https://github.com/amzn\n\nandroid.useAndroidX=true\nandroid.enableJetifier=false\nandroid.nonTransitiveRClass=true\nandroid.defaults.buildfeatures.buildconfig=false\nandroid.defaults.buildfeatures.aidl=false\nandroid.defaults.buildfeatures.renderscript=false\nandroid.defaults.buildfeatures.resvalues=false\nandroid.defaults.buildfeatures.shaders=false\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "internal/testing/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n"
  },
  {
    "path": "internal/testing/src/androidMain/kotlin/software/amazon/app/platform/internal/IgnoreNative.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Native platforms. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreNative actual constructor()\n"
  },
  {
    "path": "internal/testing/src/androidMain/kotlin/software/amazon/app/platform/internal/IgnoreWasm.android.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Wasm. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreWasm actual constructor()\n"
  },
  {
    "path": "internal/testing/src/androidMain/kotlin/software/amazon/app/platform/internal/Platform.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** The current test environment target. */\nactual val platform: Platform = Platform.JVM\n"
  },
  {
    "path": "internal/testing/src/androidMain/kotlin/software/amazon/app/platform/internal/Thread.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Provides the name of the current thread this is called on. */\nactual val currentThreadName: String\n  get() = Thread.currentThread().name\n"
  },
  {
    "path": "internal/testing/src/commonMain/kotlin/software/amazon/app/platform/internal/IgnoreNative.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Native platforms. */\n@Target(CLASS, FUNCTION) expect annotation class IgnoreNative()\n"
  },
  {
    "path": "internal/testing/src/commonMain/kotlin/software/amazon/app/platform/internal/IgnoreWasm.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Wasm. */\n@Target(CLASS, FUNCTION) expect annotation class IgnoreWasm()\n"
  },
  {
    "path": "internal/testing/src/commonMain/kotlin/software/amazon/app/platform/internal/Platform.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** All test environment targets. */\nenum class Platform {\n  /** The JVM target includes Android and Desktop. */\n  JVM,\n  /** The Native target includes Apple and Linux. */\n  Native,\n  /** The Web target includes Wasm. */\n  Web,\n}\n\n/** The current test environment target. */\nexpect val platform: Platform\n"
  },
  {
    "path": "internal/testing/src/commonMain/kotlin/software/amazon/app/platform/internal/Thread.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Provides the name of the current thread this is called on. */\nexpect val currentThreadName: String\n"
  },
  {
    "path": "internal/testing/src/desktopMain/kotlin/software/amazon/app/platform/internal/IgnoreNative.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Native platforms. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreNative actual constructor()\n"
  },
  {
    "path": "internal/testing/src/desktopMain/kotlin/software/amazon/app/platform/internal/IgnoreWasm.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Wasm. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreWasm actual constructor()\n"
  },
  {
    "path": "internal/testing/src/desktopMain/kotlin/software/amazon/app/platform/internal/Platform.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** The current test environment target. */\nactual val platform: Platform = Platform.JVM\n"
  },
  {
    "path": "internal/testing/src/desktopMain/kotlin/software/amazon/app/platform/internal/Thread.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Provides the name of the current thread this is called on. */\nactual val currentThreadName: String\n  get() = Thread.currentThread().name\n"
  },
  {
    "path": "internal/testing/src/nativeMain/kotlin/software/amazon/app/platform/internal/IgnoreNative.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Skips annotated tests on Native platforms. */\nactual typealias IgnoreNative = kotlin.test.Ignore\n"
  },
  {
    "path": "internal/testing/src/nativeMain/kotlin/software/amazon/app/platform/internal/IgnoreWasm.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Wasm. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreWasm actual constructor()\n"
  },
  {
    "path": "internal/testing/src/nativeMain/kotlin/software/amazon/app/platform/internal/Platform.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** The current test environment target. */\nactual val platform: Platform = Platform.Native\n"
  },
  {
    "path": "internal/testing/src/nativeMain/kotlin/software/amazon/app/platform/internal/Thread.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Provides the name of the current thread this is called on. */\nactual val currentThreadName: String = throw NotImplementedError()\n"
  },
  {
    "path": "internal/testing/src/wasmJsMain/kotlin/software/amazon/app/platform/internal/IgnoreNative.kt",
    "content": "package software.amazon.app.platform.internal\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\n\n/** Skips annotated tests on Native platforms. */\n@Target(CLASS, FUNCTION) actual annotation class IgnoreNative actual constructor()\n"
  },
  {
    "path": "internal/testing/src/wasmJsMain/kotlin/software/amazon/app/platform/internal/IgnoreWasm.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Skips annotated tests on Wasm. */\nactual typealias IgnoreWasm = kotlin.test.Ignore\n"
  },
  {
    "path": "internal/testing/src/wasmJsMain/kotlin/software/amazon/app/platform/internal/Platform.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** The current test environment target. */\nactual val platform: Platform = Platform.Web\n"
  },
  {
    "path": "internal/testing/src/wasmJsMain/kotlin/software/amazon/app/platform/internal/Thread.kt",
    "content": "package software.amazon.app.platform.internal\n\n/** Provides the name of the current thread this is called on. */\nactual val currentThreadName: String = throw NotImplementedError()\n"
  },
  {
    "path": "ios-run.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd -- \"$(dirname -- \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nAPP_KEY=\"\"\nAPP_LABEL=\"\"\nPROJECT_PATH=\"\"\nSCHEME=\"\"\nDERIVED_DATA_PATH=\"\"\n\nSIMULATOR_NAMES=()\nSIMULATOR_UDIDS=()\nSIMULATOR_STATES=()\nSIMULATOR_RUNTIMES=()\nPROMPT_SELECTION_INDEX=\"\"\nSELECTED_SIMULATOR_UDID=\"\"\n\nrequire_command() {\n  local command_name=\"$1\"\n  if ! command -v \"$command_name\" >/dev/null 2>&1; then\n    echo \"Missing required command: $command_name\" >&2\n    exit 1\n  fi\n}\n\nchoose_application() {\n  while true; do\n    cat <<'EOF'\nWhich application do you want to run?\n1) sample\n2) recipes\n3) starter blueprint\nEOF\n    printf \"Enter selection [1-3]: \"\n    read -r selection\n\n    case \"$selection\" in\n      1)\n        APP_KEY=\"sample\"\n        APP_LABEL=\"sample\"\n        PROJECT_PATH=\"$ROOT_DIR/sample/iosApp/iosApp.xcodeproj\"\n        SCHEME=\"iosApp\"\n        DERIVED_DATA_PATH=\"/tmp/app-platform-ios-run-sample\"\n        return\n        ;;\n      2)\n        APP_KEY=\"recipes\"\n        APP_LABEL=\"recipes\"\n        PROJECT_PATH=\"$ROOT_DIR/recipes/recipesIosApp/recipesIosApp.xcodeproj\"\n        SCHEME=\"recipesIosApp\"\n        DERIVED_DATA_PATH=\"/tmp/app-platform-ios-run-recipes\"\n        return\n        ;;\n      3)\n        APP_KEY=\"starter\"\n        APP_LABEL=\"starter blueprint\"\n        PROJECT_PATH=\"$ROOT_DIR/blueprints/starter/iosApp/iosApp.xcodeproj\"\n        SCHEME=\"iosApp\"\n        DERIVED_DATA_PATH=\"/tmp/app-platform-ios-run-starter\"\n        return\n        ;;\n      *)\n        echo \"Invalid selection.\"\n        ;;\n    esac\n  done\n}\n\nload_simulators() {\n  local mode=\"$1\"\n  local current_runtime=\"\"\n  local line=\"\"\n  local parsed_line=\"\"\n  local device_name=\"\"\n  local device_udid=\"\"\n  local device_state=\"\"\n\n  SIMULATOR_NAMES=()\n  SIMULATOR_UDIDS=()\n  SIMULATOR_STATES=()\n  SIMULATOR_RUNTIMES=()\n\n  if [[ \"$mode\" == \"available\" ]]; then\n    while IFS= read -r line; do\n      parsed_line=\"$(printf '%s\\n' \"$line\" | sed -nE 's/^-- (.+) --$/\\1/p')\"\n      if [[ -n \"$parsed_line\" ]]; then\n        current_runtime=\"$parsed_line\"\n        continue\n      fi\n\n      if [[ \"$current_runtime\" != iOS* ]]; then\n        continue\n      fi\n\n      parsed_line=\"$(printf '%s\\n' \"$line\" | sed -nE 's/^[[:space:]]+(.+) \\(([A-F0-9-]+)\\) \\(([^)]+)\\)[[:space:]]*$/\\1\\t\\2\\t\\3/p')\"\n      if [[ -n \"$parsed_line\" ]]; then\n        IFS=$'\\t' read -r device_name device_udid device_state <<<\"$parsed_line\"\n        SIMULATOR_NAMES+=(\"$device_name\")\n        SIMULATOR_UDIDS+=(\"$device_udid\")\n        SIMULATOR_STATES+=(\"$device_state\")\n        SIMULATOR_RUNTIMES+=(\"$current_runtime\")\n      fi\n    done < <(xcrun simctl list devices available)\n  else\n    while IFS= read -r line; do\n      parsed_line=\"$(printf '%s\\n' \"$line\" | sed -nE 's/^-- (.+) --$/\\1/p')\"\n      if [[ -n \"$parsed_line\" ]]; then\n        current_runtime=\"$parsed_line\"\n        continue\n      fi\n\n      if [[ \"$current_runtime\" != iOS* ]]; then\n        continue\n      fi\n\n      parsed_line=\"$(printf '%s\\n' \"$line\" | sed -nE 's/^[[:space:]]+(.+) \\(([A-F0-9-]+)\\) \\(([^)]+)\\)[[:space:]]*$/\\1\\t\\2\\t\\3/p')\"\n      if [[ -n \"$parsed_line\" ]]; then\n        IFS=$'\\t' read -r device_name device_udid device_state <<<\"$parsed_line\"\n        if [[ \"$device_state\" != \"Booted\" ]]; then\n          continue\n        fi\n        SIMULATOR_NAMES+=(\"$device_name\")\n        SIMULATOR_UDIDS+=(\"$device_udid\")\n        SIMULATOR_STATES+=(\"$device_state\")\n        SIMULATOR_RUNTIMES+=(\"$current_runtime\")\n      fi\n    done < <(xcrun simctl list devices)\n  fi\n}\n\nprompt_for_simulator_index() {\n  local prompt=\"$1\"\n  local max_index=\"${#SIMULATOR_NAMES[@]}\"\n  local i=\"\"\n  local selection=\"\"\n\n  if (( max_index == 0 )); then\n    echo \"No matching simulators found.\" >&2\n    exit 1\n  fi\n\n  echo \"$prompt\" >&2\n  for (( i = 0; i < max_index; i++ )); do\n    printf \"%d) %s [%s] (%s)\\n\" \\\n      \"$((i + 1))\" \\\n      \"${SIMULATOR_NAMES[$i]}\" \\\n      \"${SIMULATOR_RUNTIMES[$i]}\" \\\n      \"${SIMULATOR_STATES[$i]}\" >&2\n  done\n\n  while true; do\n    printf \"Enter selection [1-%d]: \" \"$max_index\" >&2\n    read -r selection\n    if [[ \"$selection\" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= max_index )); then\n      PROMPT_SELECTION_INDEX=\"$((selection - 1))\"\n      return\n    fi\n    echo \"Invalid selection.\" >&2\n  done\n}\n\nchoose_simulator() {\n  local selected_udid=\"\"\n\n  load_simulators \"booted\"\n\n  case \"${#SIMULATOR_UDIDS[@]}\" in\n    0)\n      load_simulators \"available\"\n      prompt_for_simulator_index \"No simulator is booted. Which simulator should be booted?\"\n      selected_udid=\"${SIMULATOR_UDIDS[$PROMPT_SELECTION_INDEX]}\"\n      echo \"Booting ${SIMULATOR_NAMES[$PROMPT_SELECTION_INDEX]}...\" >&2\n      xcrun simctl boot \"$selected_udid\"\n      open -a Simulator --args -CurrentDeviceUDID \"$selected_udid\" >/dev/null 2>&1 || open -a Simulator >/dev/null 2>&1 || true\n      xcrun simctl bootstatus \"$selected_udid\" -b\n      SELECTED_SIMULATOR_UDID=\"$selected_udid\"\n      ;;\n    1)\n      echo \"Using booted simulator: ${SIMULATOR_NAMES[0]} [${SIMULATOR_RUNTIMES[0]}]\" >&2\n      SELECTED_SIMULATOR_UDID=\"${SIMULATOR_UDIDS[0]}\"\n      ;;\n    *)\n      prompt_for_simulator_index \"More than one simulator is booted. Which one should be used?\"\n      SELECTED_SIMULATOR_UDID=\"${SIMULATOR_UDIDS[$PROMPT_SELECTION_INDEX]}\"\n      ;;\n  esac\n}\n\nfind_built_app() {\n  local products_dir=\"$1/Build/Products/Debug-iphonesimulator\"\n  find \"$products_dir\" -maxdepth 1 -type d -name '*.app' | head -n 1\n}\n\nmain() {\n  local app_path=\"\"\n  local bundle_id=\"\"\n\n  if [[ \"$(uname -s)\" != \"Darwin\" ]]; then\n    echo \"This script requires macOS.\" >&2\n    exit 1\n  fi\n\n  require_command xcodebuild\n  require_command xcrun\n  require_command open\n  require_command /usr/libexec/PlistBuddy\n\n  choose_application\n  choose_simulator\n\n  echo \"Building $APP_LABEL for simulator $SELECTED_SIMULATOR_UDID...\"\n  xcodebuild \\\n    -project \"$PROJECT_PATH\" \\\n    -scheme \"$SCHEME\" \\\n    -configuration Debug \\\n    -destination \"platform=iOS Simulator,id=$SELECTED_SIMULATOR_UDID\" \\\n    -derivedDataPath \"$DERIVED_DATA_PATH\" \\\n    build\n\n  app_path=\"$(find_built_app \"$DERIVED_DATA_PATH\")\"\n  if [[ -z \"$app_path\" ]]; then\n    echo \"Could not find the built .app bundle in $DERIVED_DATA_PATH.\" >&2\n    exit 1\n  fi\n\n  bundle_id=\"$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \"$app_path/Info.plist\")\"\n\n  echo \"Installing $app_path...\"\n  xcrun simctl install \"$SELECTED_SIMULATOR_UDID\" \"$app_path\"\n\n  echo \"Launching $bundle_id...\"\n  xcrun simctl launch \"$SELECTED_SIMULATOR_UDID\" \"$bundle_id\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "kotlin-inject/impl/api/android/impl.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/PresenterCoroutineScopeComponent {\n\tpublic fun providePresenterCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/PresenterCoroutineScopeComponent$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/PresenterCoroutineScopeComponent;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent {\n\tpublic fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun provideAppScopeCoroutineScopeScoped (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent {\n\tpublic fun provideDefaultCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideIoCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideMainCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\n"
  },
  {
    "path": "kotlin-inject/impl/api/desktop/impl.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/PresenterCoroutineScopeComponent {\n\tpublic fun providePresenterCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/PresenterCoroutineScopeComponent$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/PresenterCoroutineScopeComponent;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent {\n\tpublic fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun provideAppScopeCoroutineScopeScoped (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent {\n\tpublic fun provideDefaultCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideIoCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideMainCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\n"
  },
  {
    "path": "kotlin-inject/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n}\n"
  },
  {
    "path": "kotlin-inject/impl/src/commonMain/kotlin/software/amazon/app/platform/presenter/PresenterCoroutineScopeComponent.kt",
    "content": "package software.amazon.app.platform.presenter\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.plus\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.scope.coroutine.MainCoroutineDispatcher\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\n/** Provides the coroutine scope to run presenters. */\n@ContributesTo(AppScope::class)\npublic interface PresenterCoroutineScopeComponent {\n  /**\n   * Bind the app coroutine scope as default scope for presenters to allow them to run as long as\n   * the app is alive. The coroutine scope will use the main dispatcher by default, because\n   * presenters produce state for the UI and computing their models should have the highest\n   * priority.\n   */\n  @Provides\n  @PresenterCoroutineScope\n  public fun providePresenterCoroutineScope(\n    @ForScope(AppScope::class) scope: CoroutineScope,\n    @MainCoroutineDispatcher mainDispatcher: CoroutineDispatcher,\n  ): CoroutineScope = scope + mainDispatcher\n}\n"
  },
  {
    "path": "kotlin-inject/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/AppScopeCoroutineScopeComponent.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.SupervisorJob\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/** Component providing coroutine scopes in the App scope. */\n@ContributesTo(AppScope::class)\npublic interface AppScopeCoroutineScopeComponent {\n  /**\n   * Provides the [CoroutineScopeScoped] for the app scope. This is a single instance for the app\n   * scope.\n   */\n  @Provides\n  @SingleIn(AppScope::class)\n  @ForScope(AppScope::class)\n  public fun provideAppScopeCoroutineScopeScoped(\n    @IoCoroutineDispatcher dispatcher: CoroutineDispatcher\n  ): CoroutineScopeScoped {\n    return CoroutineScopeScoped(dispatcher + SupervisorJob() + CoroutineName(\"AppScope\"))\n  }\n\n  /**\n   * Provides the [CoroutineScope] for the app scope. A new child scope is created every time an\n   * instance is injected so that the parent cannot be canceled accidentally.\n   */\n  @Provides\n  @ForScope(AppScope::class)\n  public fun provideAppCoroutineScope(\n    @ForScope(AppScope::class) appScopeCoroutineScopeScoped: CoroutineScopeScoped\n  ): CoroutineScope {\n    return appScopeCoroutineScopeScoped.createChild()\n  }\n}\n"
  },
  {
    "path": "kotlin-inject/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/CoroutineDispatcherComponent.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/** Provides default dispatchers for Coroutine scopes. */\n@ContributesTo(AppScope::class)\npublic interface CoroutineDispatcherComponent {\n  /** Provides the IO dispatcher in the dependency graph. */\n  @Provides\n  @IoCoroutineDispatcher\n  public fun provideIoCoroutineDispatcher(): CoroutineDispatcher = ioDispatcher\n\n  /** Provides the default dispatcher in the dependency graph. */\n  @Provides\n  @DefaultCoroutineDispatcher\n  public fun provideDefaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Default\n\n  /** Provides the main dispatcher in the dependency graph. */\n  @Provides\n  @MainCoroutineDispatcher\n  public fun provideMainCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate\n}\n"
  },
  {
    "path": "kotlin-inject/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlinx.coroutines.CoroutineDispatcher\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\ninternal expect val ioDispatcher: CoroutineDispatcher\n"
  },
  {
    "path": "kotlin-inject/impl/src/noWasmJsMain/kotlin/software/amazon/app/platform/scope/coroutine/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.IO\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\ninternal actual val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n"
  },
  {
    "path": "kotlin-inject/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/scope/coroutine/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\n// Fallback to the Default dispatcher.\ninternal actual val ioDispatcher: CoroutineDispatcher = Dispatchers.Default\n"
  },
  {
    "path": "kotlin-inject/public/api/android/public.api",
    "content": "public final class software/amazon/app/platform/scope/di/ComponentServiceKt {\n\tpublic static final field DI_COMPONENT_KEY Ljava/lang/String;\n\tpublic static final fun addDiComponent (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n\tpublic static final fun addKotlinInjectComponent (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n}\n\n"
  },
  {
    "path": "kotlin-inject/public/api/desktop/public.api",
    "content": "public final class software/amazon/app/platform/scope/di/ComponentServiceKt {\n\tpublic static final field DI_COMPONENT_KEY Ljava/lang/String;\n\tpublic static final fun addDiComponent (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n\tpublic static final fun addKotlinInjectComponent (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n}\n\n"
  },
  {
    "path": "kotlin-inject/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n\n    commonTestImplementation project(':internal:testing')\n}\n"
  },
  {
    "path": "kotlin-inject/public/src/commonMain/kotlin/software/amazon/app/platform/scope/di/ComponentService.kt",
    "content": "package software.amazon.app.platform.scope.di\n\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.parents\n\n@PublishedApi internal const val DI_COMPONENT_KEY: String = \"diComponent\"\n\n/** This function is deprecated. [kotlinInjectComponent] is a one to one replacement. */\n@Deprecated(\n  message = \"Use kotlinInjectComponent instead.\",\n  replaceWith = ReplaceWith(\"kotlinInjectComponent<T>()\"),\n  level = DeprecationLevel.WARNING,\n)\npublic inline fun <reified T : Any> Scope.diComponent(): T = kotlinInjectComponent()\n\n/**\n * Provides the DI component that has been added to this [Scope]. A common pattern is to use this\n * function to look up component interfaces in static contexts like test methods, static functions\n * or where constructor injection cannot be used, e.g.\n *\n * ```\n * interface HudComponent {\n *     fun hudManager(): HudManager\n * }\n *\n * rootScope.diComponent<HudComponent>().hudManager()\n * ```\n *\n * The given component type [T] of the DI component can be provided by this scope or a parent scope.\n */\npublic inline fun <reified T : Any> Scope.kotlinInjectComponent(): T {\n  parents(includeSelf = true)\n    .firstNotNullOfOrNull { scope -> scope.getService<Any>(DI_COMPONENT_KEY) as? T }\n    ?.let {\n      return it\n    }\n\n  val diComponents =\n    parents(includeSelf = true)\n      .map { it.getService<Any>(DI_COMPONENT_KEY) }\n      .filterNotNull()\n      .map { it::class }\n\n  // The replace() will align inner class references across platforms. Native uses a '.',\n  // whereas the JVM platform use '$'.\n  throw NoSuchElementException(\n    \"Couldn't find component implementing ${T::class}. Inspected: \" +\n      \"[${diComponents.joinToString { it.simpleName.toString() }}] (fully qualified \" +\n      \"names: [${diComponents.joinToString { it.toString().replace('\\$', '.') }}])\"\n  )\n}\n\n/** This function is deprecated. [addKotlinInjectComponent] is a one to one replacement. */\n@Deprecated(\n  message = \"Use addKotlinInjectComponent instead.\",\n  replaceWith = ReplaceWith(\"addKotlinInjectComponent(component)\"),\n  level = DeprecationLevel.WARNING,\n)\npublic fun Scope.Builder.addDiComponent(component: Any) {\n  addKotlinInjectComponent(component)\n}\n\n/**\n * Adds the given [component] to this builder. The instance can be later retrieved with\n * [kotlinInjectComponent].\n */\npublic fun Scope.Builder.addKotlinInjectComponent(component: Any) {\n  addService(DI_COMPONENT_KEY, component)\n}\n"
  },
  {
    "path": "kotlin-inject/public/src/commonTest/kotlin/software/amazon/app/platform/scope/di/ComponentServiceTest.kt",
    "content": "package software.amazon.app.platform.scope.di\n\nimport assertk.assertThat\nimport assertk.assertions.hasMessage\nimport assertk.assertions.isSameInstanceAs\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.internal.Platform\nimport software.amazon.app.platform.internal.platform\nimport software.amazon.app.platform.scope.Scope\n\nclass ComponentServiceTest {\n\n  @Test\n  fun `a DI component can be registered in a scope`() {\n    val component = ParentComponentImpl()\n\n    val scope = Scope.buildRootScope { addKotlinInjectComponent(component) }\n\n    assertThat(scope.kotlinInjectComponent<ParentComponent>()).isSameInstanceAs(component)\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `if a DI component cannot be found then an exception is thrown with a helpful error message`() {\n    val parentComponent = ParentComponentImpl()\n    val childComponent = ChildComponentImpl()\n\n    val parentScope = Scope.buildRootScope { addKotlinInjectComponent(parentComponent) }\n    val childScope = parentScope.buildChild(\"child\") { addKotlinInjectComponent(childComponent) }\n\n    val exception =\n      assertFailsWith<NoSuchElementException> { childScope.kotlinInjectComponent<Unit>() }\n\n    val kotlinReflectWarning =\n      when (platform) {\n        Platform.JVM -> \" (Kotlin reflection is not available)\"\n        Platform.Native,\n        Platform.Web -> \"\"\n      }\n\n    assertThat(exception)\n      .hasMessage(\n        \"Couldn't find component implementing class kotlin.Unit$kotlinReflectWarning. \" +\n          \"Inspected: [ChildComponentImpl, ParentComponentImpl] (fully qualified names: \" +\n          \"[class software.amazon.app.platform.scope.di.ComponentServiceTest.\" +\n          \"ChildComponentImpl$kotlinReflectWarning, class software.amazon.app.\" +\n          \"platform.scope.di.ComponentServiceTest.ParentComponentImpl\" +\n          \"$kotlinReflectWarning])\"\n      )\n  }\n\n  @Test\n  fun `a DI component can be retrieved from a scope`() {\n    val parentComponent = ParentComponentImpl()\n    val childComponent = ChildComponentImpl()\n\n    val parentScope = Scope.buildRootScope { addKotlinInjectComponent(parentComponent) }\n    val childScope = parentScope.buildChild(\"child\") { addKotlinInjectComponent(childComponent) }\n\n    assertThat(childScope.kotlinInjectComponent<ChildComponent>()).isSameInstanceAs(childComponent)\n    assertThat(childScope.kotlinInjectComponent<ParentComponent>())\n      .isSameInstanceAs(parentComponent)\n\n    assertThat(parentScope.kotlinInjectComponent<ParentComponent>())\n      .isSameInstanceAs(parentComponent)\n    assertFailsWith<NoSuchElementException> { parentScope.kotlinInjectComponent<ChildComponent>() }\n  }\n\n  private interface ParentComponent\n\n  private class ParentComponentImpl : ParentComponent\n\n  private interface ChildComponent\n\n  private class ChildComponentImpl : ChildComponent\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib.jvm'\n    id 'com.google.devtools.ksp'\n\n    alias(libs.plugins.build.config)\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ntest {\n    useJUnitPlatform()\n\n    // Since Kotlin 2.0 we need more memory to run our tests.\n    maxHeapSize = \"2g\"\n}\n\ndependencies {\n    implementation project(':ksp-common:public')\n    implementation libs.ksp.api\n\n    implementation libs.kotlin.poet\n    implementation libs.kotlin.poet.ksp\n\n    implementation libs.auto.service.annotations\n    ksp libs.auto.service.ksp\n\n    // Gives us access to annotations.\n    implementation project(':di-common:public')\n    implementation project(':scope:public')\n    implementation libs.kotlin.inject.runtime\n    implementation libs.kotlin.inject.anvil.runtime\n    implementation libs.kotlin.inject.anvil.runtime.optional\n\n    testImplementation project(':kotlin-inject:public')\n    testImplementation project(':ksp-common:testing')\n    testImplementation project(':presenter:public')\n    testImplementation project(':renderer:public')\n    testImplementation project(':robot:public')\n    testImplementation libs.kotlin.compile.testing.core\n    testImplementation libs.kotlin.compile.testing.ksp\n\n    // Added so that the SymbolProcessor is picked up in tests.\n    testImplementation libs.kotlin.inject.ksp\n    testImplementation libs.kotlin.inject.anvil.compiler\n\n    // Bump transitive dependency.\n    testImplementation libs.kotlin.compiler.embeddable\n    testImplementation libs.ksp\n    testImplementation libs.ksp.embeddable\n}\n\nbuildConfig {\n    useKotlinOutput {\n        internalVisibility = false\n    }\n}\n\n// We don't need the apiCheck in this module.\ntasks.named('apiCheck').configure {\n    it.enabled = false\n}\ntasks.named('apiDump').configure {\n    it.enabled = false\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/KotlinInjectContextAware.kt",
    "content": "package software.amazon.app.platform.inject\n\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtools.ksp.symbol.KSType\nimport me.tatarka.inject.annotations.Inject\nimport me.tatarka.inject.annotations.Scope\nimport software.amazon.app.platform.ksp.ContextAware\n\n@Suppress(\"TooManyFunctions\")\ninternal interface KotlinInjectContextAware : ContextAware {\n  val injectFqName\n    get() = Inject::class.requireQualifiedName()\n\n  private val scopeFqName\n    get() = Scope::class.requireQualifiedName()\n\n  fun KSAnnotation.isKotlinInjectScopeAnnotation(): Boolean {\n    return annotationType.resolve().isKotlinInjectScopeAnnotation()\n  }\n\n  private fun KSType.isKotlinInjectScopeAnnotation(): Boolean {\n    return declaration.annotations.any {\n      it.annotationType.resolve().declaration.requireQualifiedName() == scopeFqName\n    }\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/KotlinInjectExtensionSymbolProcessorProvider.kt",
    "content": "package software.amazon.app.platform.inject\n\nimport com.google.auto.service.AutoService\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\nimport software.amazon.app.platform.inject.processor.ContributesBindingProcessor\nimport software.amazon.app.platform.inject.processor.ContributesBindingScopedProcessor\nimport software.amazon.app.platform.inject.processor.ContributesMockImplProcessor\nimport software.amazon.app.platform.inject.processor.ContributesRealImplProcessor\nimport software.amazon.app.platform.inject.processor.ContributesRendererProcessor\nimport software.amazon.app.platform.inject.processor.ContributesRobotProcessor\nimport software.amazon.app.platform.ksp.CompositeSymbolProcessor\n\n/** Entry point for KSP to pick up our [SymbolProcessor]. */\n@AutoService(SymbolProcessorProvider::class)\n@Suppress(\"unused\")\npublic class KotlinInjectExtensionSymbolProcessorProvider : SymbolProcessorProvider {\n  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {\n    return CompositeSymbolProcessor(\n      ContributesBindingProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesBindingScopedProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesRendererProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesRealImplProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesMockImplProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesRobotProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n    )\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/Util.kt",
    "content": "package software.amazon.app.platform.inject\n\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.Annotatable\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport software.amazon.lastmile.kotlin.inject.anvil.internal.Origin\n\n/**\n * The package in which code is generated that should be picked up during the merging phase. This\n * package is used by the open source project.\n */\ninternal const val OPEN_SOURCE_LOOKUP_PACKAGE = \"amazon.lastmile.inject\"\n\n/** The package in which the App Platform extensions generate code. */\ninternal const val APP_PLATFORM_LOOKUP_PACKAGE = \"app.platform.inject\"\n\ninternal fun <T : Annotatable.Builder<T>> Annotatable.Builder<T>.addOriginAnnotation(\n  clazz: KSClassDeclaration\n): T =\n  addAnnotation(\n    AnnotationSpec.builder(Origin::class)\n      .addMember(\"value = %T::class\", clazz.toClassName())\n      .build()\n  )\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesBindingProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSType\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.OPEN_SOURCE_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.app.platform.ksp.argumentOfTypeAt\nimport software.amazon.app.platform.ksp.decapitalize\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n\n/**\n * Generates the code for [ContributesBinding].\n *\n * In the lookup package [OPEN_SOURCE_LOOKUP_PACKAGE] a new interface is generated with a provider\n * method for the annotated type. To avoid name clashes the package name of the original interface\n * is encoded in the interface name. E.g.\n *\n * ```\n * package software.amazon.test\n *\n * @Inject\n * @SingleIn(AppScope::class)\n * @ContributesBinding(AppScope::class)\n * class RealAuthenticator : Authenticator\n * ```\n *\n * Will generate:\n * ```\n * package $LOOKUP_PACKAGE\n *\n * @Origin(RealAuthenticator::class)\n * interface SoftwareAmazonTestRealAuthenticator {\n *     @Provides fun provideRealAuthenticatorAuthenticator(\n *         realAuthenticator: RealAuthenticator\n *     ): Authenticator = realAuthenticator\n * }\n * ```\n */\ninternal class ContributesBindingProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesBinding::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkHasScope(it)\n      }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  @Suppress(\"LongMethod\")\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val componentClassName = ClassName(OPEN_SOURCE_LOOKUP_PACKAGE, clazz.safeClassName)\n\n    val annotations = clazz.findAnnotationsAtLeastOne(ContributesBinding::class)\n    checkNoDuplicateBoundTypes(clazz, annotations)\n\n    val boundTypes =\n      annotations\n        .mapNotNull { annotation ->\n          val boundType =\n            boundType(clazz, annotation).takeUnless { it.isScoped() } ?: return@mapNotNull null\n\n          GeneratedFunction(\n            boundType = boundType,\n            multibinding = annotation.argumentOfTypeAt<Boolean>(this, \"multibinding\") ?: false,\n          )\n        }\n        .distinctBy { it.bindingMethodReturnType.canonicalName + it.multibinding }\n\n    // The only boundType was Scoped, which is handled by a separate processor.\n    if (boundTypes.isEmpty()) return\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addFunctions(\n              boundTypes.map { function ->\n                val multibindingSuffix =\n                  if (function.multibinding) {\n                    \"Multibinding\"\n                  } else {\n                    \"\"\n                  }\n                FunSpec.builder(\n                    \"provide${clazz.innerClassNames()}\" +\n                      function.bindingMethodReturnType.simpleName +\n                      multibindingSuffix\n                  )\n                  .addAnnotation(Provides::class)\n                  .apply {\n                    if (function.multibinding) {\n                      addAnnotation(IntoSet::class)\n                    }\n                  }\n                  .apply {\n                    val parameterName = clazz.innerClassNames().decapitalize()\n                    addParameter(\n                      ParameterSpec.builder(name = parameterName, type = clazz.toClassName())\n                        .build()\n                    )\n\n                    addStatement(\"return $parameterName\")\n                  }\n                  .returns(function.bindingMethodReturnType)\n                  .build()\n              }\n            )\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private inner class GeneratedFunction(boundType: KSType, val multibinding: Boolean) {\n    val bindingMethodReturnType by lazy { boundType.toClassName() }\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesBindingScopedProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.OPEN_SOURCE_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.app.platform.ksp.decapitalize\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\n/**\n * Generates the code for [ContributesBinding] and the `Scoped` type.\n *\n * In the lookup package [OPEN_SOURCE_LOOKUP_PACKAGE] a new interface is generated with a provider\n * method for the annotated type. To avoid name clashes the package name of the original interface\n * is encoded in the interface name. E.g.\n *\n * ```\n * package software.amazon.test\n *\n * @Inject\n * @SingleIn(AppScope::class)\n * @ContributesBinding(AppScope::class)\n * class RealAuthenticator : Authenticator, Scoped\n * ```\n *\n * Will generate:\n * ```\n * package $LOOKUP_PACKAGE\n *\n * @Origin(RealAuthenticator::class)\n * interface SoftwareAmazonTestRealAuthenticatorScoped {\n *     @Provides\n *     @IntoSet\n *     @ForScope(AppScope::class)\n *     fun provideRealAuthenticatorAuthenticatorScoped(\n *         realAuthenticator: RealAuthenticator\n *     ): Scoped = realAuthenticator\n * }\n * ```\n */\ninternal class ContributesBindingScopedProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesBinding::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .filter { clazz ->\n        val hasSuperType = clazz.superTypes.any { it.resolve().isScoped() }\n        if (hasSuperType) return@filter true\n\n        val annotations = clazz.findAnnotationsAtLeastOne(ContributesBinding::class)\n        annotations.any { annotation -> boundType(clazz, annotation).isScoped() }\n      }\n      .onEach {\n        checkIsPublic(it)\n        checkHasScope(it)\n      }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  @Suppress(\"LongMethod\")\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val componentPackage = \"${APP_PLATFORM_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val componentClassName =\n      ClassName(componentPackage, \"${clazz.innerClassNames()}ScopedComponent\")\n\n    val scope = clazz.scope()\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"scope = %T::class\", scope.type.toClassName())\n                .build()\n            )\n            .addFunction(\n              FunSpec.builder(\"provide${clazz.innerClassNames()}Scoped\")\n                .addAnnotation(Provides::class)\n                .addAnnotation(IntoSet::class)\n                .addAnnotation(\n                  AnnotationSpec.builder(ForScope::class)\n                    .addMember(\"scope = %T::class\", scope.type.toClassName())\n                    .build()\n                )\n                .apply {\n                  val parameterName = clazz.innerClassNames().decapitalize()\n                  addParameter(\n                    ParameterSpec.builder(name = parameterName, type = clazz.toClassName()).build()\n                  )\n\n                  addStatement(\"return $parameterName\")\n                }\n                .returns(scopedClassName)\n                .build()\n            )\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesMockImplProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.LambdaTypeName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.app.platform.inject.mock.ContributesMockImpl\nimport software.amazon.app.platform.inject.mock.MockMode\nimport software.amazon.app.platform.inject.mock.RealImpl\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\n/**\n * Generates the necessary code in order to support [ContributesMockImpl].\n *\n * If the class implements `Scoped`, then based on the mock mode flag the mock implementation gets\n * called or not.\n *\n * ```\n * package app.platform.inject.software.amazon.test\n *\n * @ContributesTo(scope = AppScope::class)\n * public interface MockVtsMockImplComponent {\n *      @Provides\n *      public fun provideVts(\n *          @MockMode mockMode: Boolean,\n *          mockImpl: () -> MockVts,\n *          @RealImpl realImpl: () -> Vts,\n *      ): Vts = if (mockMode) mockImpl() else realImpl()\n *\n *      @Provides\n *      @IntoSet\n *      @ForScope(AppScope::class)\n *      fun provideMockVtsScoped(\n *          @MockMode mockMode: Boolean,\n *          mockImpl: () -> MockVts,\n *      ): Scoped = if (mockMode) mockImpl() else Scoped.NO_OP\n * }\n * ```\n */\ninternal class ContributesMockImplProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesMockImpl::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach { checkIsPublic(it) }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  @OptIn(KspExperimental::class)\n  @Suppress(\"LongMethod\")\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val packageName = \"${APP_PLATFORM_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val componentClassName = ClassName(packageName, \"${clazz.innerClassNames()}MockImplComponent\")\n\n    val annotations = clazz.findAnnotationsAtLeastOne(ContributesMockImpl::class)\n    checkNoDuplicateBoundTypes(clazz, annotations)\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", clazz.scope().type.toClassName())\n                .build()\n            )\n            .addFunctions(\n              annotations.map { annotation ->\n                val boundType = boundType(clazz, annotation)\n\n                check(!boundType.isScoped(), clazz) { \"Scoped cannot be used as bound type.\" }\n\n                FunSpec.builder(\"provide${boundType.declaration.simpleName.asString()}\")\n                  .addAnnotation(Provides::class)\n                  .addParameter(\n                    ParameterSpec.builder(\"mockMode\", Boolean::class)\n                      .addAnnotation(MockMode::class)\n                      .build()\n                  )\n                  .addParameter(\"mockImpl\", LambdaTypeName.get(returnType = clazz.toClassName()))\n                  .addParameter(\n                    ParameterSpec.builder(\n                        \"realImpl\",\n                        LambdaTypeName.get(returnType = boundType.toClassName()),\n                      )\n                      .addAnnotation(RealImpl::class)\n                      .build()\n                  )\n                  .returns(boundType.toClassName())\n                  .addStatement(\"return if (mockMode) mockImpl() else realImpl()\")\n                  .build()\n              }\n            )\n            .apply {\n              if (\n                clazz.superTypes.any { it.resolve().isScoped() } &&\n                  !clazz.isAnnotationPresent(ContributesBinding::class)\n              ) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.innerClassNames()}Scoped\")\n                    .addAnnotation(Provides::class)\n                    .addAnnotation(IntoSet::class)\n                    .addAnnotation(\n                      AnnotationSpec.builder(ForScope::class)\n                        .addMember(\"scope = %T::class\", clazz.scope().type.toClassName())\n                        .build()\n                    )\n                    .addParameter(\n                      ParameterSpec.builder(\"mockMode\", Boolean::class)\n                        .addAnnotation(MockMode::class)\n                        .build()\n                    )\n                    .addParameter(\"mockImpl\", LambdaTypeName.get(returnType = clazz.toClassName()))\n                    .returns(scopedClassName)\n                    .addStatement(\"return if (mockMode) mockImpl() else %T.NO_OP\", scopedClassName)\n                    .build()\n                )\n              }\n            }\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesRealImplProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.LambdaTypeName\nimport com.squareup.kotlinpoet.ParameterSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.app.platform.inject.mock.ContributesRealImpl\nimport software.amazon.app.platform.inject.mock.MockMode\nimport software.amazon.app.platform.inject.mock.RealImpl\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\n/**\n * Generates the necessary code in order to support [ContributesRealImpl].\n *\n * If the class implements `Scoped`, then based on the mock mode flag the real implementation gets\n * called or not.\n *\n * ```\n * package app.platform.inject.software.amazon.test\n *\n * @ContributesTo(scope = AppScope::class)\n * public interface RealVtsRealImplComponent {\n *      @Provides\n *      @RealImpl\n *      public fun provideVtsRealImpl(realImpl: RealVts): Vts = realVts\n *\n *      @Provides\n *      @IntoSet\n *      @ForScope(AppScope::class)\n *      fun provideVtsRealImplScoped(\n *          @MockMode mockMode: Boolean,\n *          realImpl: () -> RealVts,\n *      ): Scoped = if (mockMode) Scoped.NO_OP else realImpl()\n * }\n * ```\n */\ninternal class ContributesRealImplProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesRealImpl::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach { checkIsPublic(it) }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  @OptIn(KspExperimental::class)\n  @Suppress(\"LongMethod\")\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val packageName = \"${APP_PLATFORM_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val componentClassName = ClassName(packageName, \"${clazz.innerClassNames()}RealImplComponent\")\n\n    val annotations = clazz.findAnnotationsAtLeastOne(ContributesRealImpl::class)\n    checkNoDuplicateBoundTypes(clazz, annotations)\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", clazz.scope().type.toClassName())\n                .build()\n            )\n            .addFunctions(\n              annotations.map { annotation ->\n                val boundType = boundType(clazz, annotation)\n\n                check(!boundType.isScoped(), clazz) { \"Scoped cannot be used as bound type.\" }\n\n                FunSpec.builder(\n                    \"provide${boundType.declaration.simpleName.asString()}\" + \"RealImpl\"\n                  )\n                  .addAnnotation(Provides::class)\n                  .addAnnotation(RealImpl::class)\n                  .addParameter(\"realImpl\", clazz.toClassName())\n                  .returns(boundType.toClassName())\n                  .addStatement(\"return realImpl\")\n                  .build()\n              }\n            )\n            .apply {\n              if (\n                clazz.superTypes.any { it.resolve().isScoped() } &&\n                  !clazz.isAnnotationPresent(ContributesBinding::class)\n              ) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.innerClassNames()}Scoped\")\n                    .addAnnotation(Provides::class)\n                    .addAnnotation(IntoSet::class)\n                    .addAnnotation(\n                      AnnotationSpec.builder(ForScope::class)\n                        .addMember(\"scope = %T::class\", clazz.scope().type.toClassName())\n                        .build()\n                    )\n                    .addParameter(\n                      ParameterSpec.builder(\"mockMode\", Boolean::class)\n                        .addAnnotation(MockMode::class)\n                        .build()\n                    )\n                    .addParameter(\"realImpl\", LambdaTypeName.get(returnType = clazz.toClassName()))\n                    .returns(scopedClassName)\n                    .addStatement(\"return if (mockMode) %T.NO_OP else realImpl()\", scopedClassName)\n                    .build()\n                )\n              }\n            }\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesRendererProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAllSuperTypes\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSType\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.LambdaTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.STAR\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.WildcardTypeName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport kotlin.reflect.KClass\nimport me.tatarka.inject.annotations.Inject\nimport me.tatarka.inject.annotations.IntoMap\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * Generates the code for [ContributesRenderer].\n *\n * In the lookup package [APP_PLATFORM_LOOKUP_PACKAGE] a new interface is generated with a provider\n * method for the renderer, e.g.\n *\n * ```\n * package software.amazon.test\n *\n * @ContributesRenderer\n * class TestRenderer : Renderer<Model>\n * ```\n *\n * Will generate:\n * ```\n * package $APP_PLATFORM_LOOKUP_PACKAGE.software.amazon.test\n *\n * @ContributesTo(RendererScope::class)\n * @Origin(TestRenderer::class)\n * interface TestRendererComponent {\n *     @Provides\n *     @IntoMap\n *     fun provideTestRendererIntoMap(\n *         renderer: () -> TestRenderer,\n *     ): Pair<KClass<out BaseModel>, () -> Renderer<*>> = Model::class to renderer\n *\n *     @Provides\n *     fun provideTestRenderer(): TestRenderer = TestRenderer()\n *\n *     @Provides\n *     @IntoMap\n *     @ForScope(RendererScope::class)\n *     fun provideRendererModelKey(): Pair<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n *         Model::class to TestRenderer::class\n * }\n * ```\n */\ninternal class ContributesRendererProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  private val baseModel = ClassName(\"software.amazon.app.platform.presenter\", \"BaseModel\")\n  private val baseModelFqName = baseModel.canonicalName\n\n  private val rendererWildcard =\n    ClassName(\"software.amazon.app.platform.renderer\", \"Renderer\").parameterizedBy(STAR)\n\n  private val rendererScope = ClassName(\"software.amazon.app.platform.renderer\", \"RendererScope\")\n\n  private val singleIn = SingleIn::class.asClassName()\n\n  private val unitFqName = Unit::class.requireQualifiedName()\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesRenderer::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkNoSingleton(it)\n      }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  @OptIn(KspExperimental::class)\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val packageName = \"${APP_PLATFORM_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val componentClassName = ClassName(packageName, \"${clazz.innerClassNames()}Component\")\n    val hasInjectAnnotation = clazz.isAnnotationPresent(Inject::class)\n\n    if (hasInjectAnnotation) {\n      checkNoZeroArgConstructor(clazz)\n    } else {\n      checkZeroArgConstructor(clazz)\n    }\n\n    val includeSealedSubtypes =\n      try {\n        clazz.getAnnotationsByType(ContributesRenderer::class).single().includeSealedSubtypes\n      } catch (_: NoSuchElementException) {\n        /*\n        Caused by: java.util.NoSuchElementException: Collection contains no element matching the predicate.\n          at com.google.devtools.ksp.UtilsKt.createInvocationHandler$lambda$8(utils.kt:591)\n          at jdk.proxy105/jdk.proxy105.$Proxy1029.includeSealedSubtypes(Unknown Source)\n          at software.amazon.app.platform.inject.processor.ContributesRendererProcessor.generateComponentInterface(ContributesRendererProcessor.kt:120)\n\n        We're seeing this exception when trying to read 'includeSealedSubtypes' for an annotation\n        where the value is not declared, e.g. '@ContributesRenderer' (without any arguments).\n        This happens only on iOS for some reason. Fallback to the default value 'true'.\n         */\n        true\n      }\n\n    val allModels =\n      if (includeSealedSubtypes) {\n        generateSequence(listOf(modelType(clazz))) { classes ->\n            classes.flatMap { it.getSealedSubclasses() }.takeIf { it.isNotEmpty() }\n          }\n          .flatten()\n      } else {\n        sequenceOf(modelType(clazz))\n      }\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", rendererScope)\n                .build()\n            )\n            .apply {\n              if (!hasInjectAnnotation) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.safeClassName}\")\n                    .addAnnotation(Provides::class)\n                    .returns(clazz.toClassName())\n                    .addStatement(\"return %T()\", clazz.toClassName())\n                    .build()\n                )\n              }\n            }\n            .addFunctions(allModels.map { createModelBindingFunction(clazz, it) }.toList())\n            .addFunctions(allModels.map { createModelKeyFunction(clazz, it) }.toList())\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private fun modelType(clazz: KSClassDeclaration): KSClassDeclaration {\n    val annotation = clazz.findAnnotation(ContributesRenderer::class)\n    val explicitModelType =\n      (annotation.arguments.firstOrNull { it.name?.asString() == \"modelType\" }\n          ?: annotation.arguments.firstOrNull())\n        ?.let { (it.value as? KSType)?.declaration as? KSClassDeclaration }\n        ?.takeIf { it.requireQualifiedName() != unitFqName }\n\n    if (explicitModelType != null) {\n      return explicitModelType\n    }\n\n    val implicitModelTypes =\n      clazz\n        .getAllSuperTypes()\n        .flatMap { superType ->\n          superType.arguments.filter { it.type?.resolve()?.extendsBaseModel() ?: false }\n        }\n        .mapNotNull { it.type?.resolve()?.declaration as? KSClassDeclaration }\n        .distinctBy { it.requireQualifiedName() }\n        .toList()\n\n    check(implicitModelTypes.size == 1, clazz) {\n      buildString {\n        append(\n          \"Couldn't find BaseModel type for ${clazz.simpleName.asString()}. \" +\n            \"Consider adding an explicit parameter.\"\n        )\n        if (implicitModelTypes.size > 1) {\n          append(\"Found: \")\n          append(implicitModelTypes.joinToString { it.requireQualifiedName() })\n        }\n      }\n    }\n\n    return implicitModelTypes[0]\n  }\n\n  private fun createModelBindingFunction(\n    clazz: KSClassDeclaration,\n    modelType: KSClassDeclaration,\n  ): FunSpec {\n    return FunSpec.builder(\"provide${clazz.safeClassName}\" + modelType.innerClassNames())\n      .addAnnotation(Provides::class)\n      .addAnnotation(IntoMap::class)\n      .addParameter(name = \"renderer\", type = LambdaTypeName.get(returnType = clazz.toClassName()))\n      .returns(\n        Pair::class.asClassName()\n          .parameterizedBy(\n            listOf(\n              KClass::class.asClassName().parameterizedBy(WildcardTypeName.producerOf(baseModel)),\n              LambdaTypeName.get(returnType = rendererWildcard),\n            )\n          )\n      )\n      .addStatement(\"return %T::class·to·renderer\", modelType.toClassName())\n      .build()\n  }\n\n  private fun createModelKeyFunction(\n    clazz: KSClassDeclaration,\n    modelType: KSClassDeclaration,\n  ): FunSpec {\n    return FunSpec.builder(\"provide${clazz.safeClassName}\" + modelType.innerClassNames() + \"Key\")\n      .addAnnotation(Provides::class)\n      .addAnnotation(IntoMap::class)\n      .addAnnotation(\n        AnnotationSpec.builder(ForScope::class)\n          .addMember(\"scope = %T::class\", rendererScope)\n          .build()\n      )\n      .returns(\n        Pair::class.asClassName()\n          .parameterizedBy(\n            listOf(\n              KClass::class.asClassName().parameterizedBy(WildcardTypeName.producerOf(baseModel)),\n              KClass::class.asClassName()\n                .parameterizedBy(WildcardTypeName.producerOf(rendererWildcard)),\n            )\n          )\n      )\n      .addStatement(\"return %T::class·to·%T::class\", modelType.toClassName(), clazz.toClassName())\n      .build()\n  }\n\n  private fun checkNoSingleton(clazz: KSClassDeclaration) {\n    val hasSingleInAnnotation =\n      clazz.annotations.any { annotation ->\n        annotation.isAnnotation(singleIn.canonicalName) &&\n          clazz.scope().type.declaration.requireQualifiedName() == rendererScope.canonicalName\n      }\n\n    if (hasSingleInAnnotation) {\n      logger.error(\n        \"Renderers should not be singletons in the RendererScope. The \" +\n          \"RendererFactory will cache the Renderer when necessary. Remove the \" +\n          \"@SingleIn(RendererScope::class) annotation.\",\n        clazz,\n      )\n    }\n  }\n\n  private fun checkNoZeroArgConstructor(clazz: KSClassDeclaration) {\n    val parameterCount = clazz.primaryConstructor?.parameters?.size ?: 0\n    check(parameterCount > 0, clazz) {\n      \"It's redundant to use @Inject when using \" +\n        \"@ContributesRenderer for a Renderer with a zero-arg constructor.\"\n    }\n  }\n\n  private fun checkZeroArgConstructor(clazz: KSClassDeclaration) {\n    val parameterCount = clazz.primaryConstructor?.parameters?.size ?: 0\n    check(parameterCount == 0, clazz) {\n      \"When using @ContributesRenderer and you need to inject types in the constructor, \" +\n        \"then it's necessary to add the @Inject annotation.\"\n    }\n  }\n\n  private fun KSType.extendsBaseModel(): Boolean {\n    val superTypes =\n      (this.declaration as? KSClassDeclaration)?.getAllSuperTypes() ?: emptySequence()\n\n    return superTypes.any { it.declaration.qualifiedName?.asString() == baseModelFqName }\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/inject/processor/ContributesRobotProcessor.kt",
    "content": "package software.amazon.app.platform.inject.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAllSuperTypes\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.LambdaTypeName\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.WildcardTypeName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport kotlin.reflect.KClass\nimport me.tatarka.inject.annotations.Inject\nimport me.tatarka.inject.annotations.IntoMap\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.KotlinInjectContextAware\nimport software.amazon.app.platform.inject.addOriginAnnotation\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.ksp.decapitalize\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/**\n * Generates the necessary code in order to support [ContributesRobot].\n *\n * If you use `@ContributesRobot(AbcScope::class)`, then this code generator will generate a\n * component interface, which gets contributed to this scope.\n *\n * ```\n * package app.platform.inject.software.amazon.test\n *\n * @ContributesTo(scope = AbcScope::class)\n * public interface AbcRobotComponent {\n *     @Provide\n *     fun provideAbcRobot(): AbcRobot = AbcRobot()\n *\n *     @Provides\n *     @IntoMap\n *     fun provideAbcRobotIntoMap(\n *         robot: () -> AbcRobot,\n *     ): Pair<KClass<out Robot>, () -> Robot> = AbcRobot::class to robot\n * }\n * ```\n */\n@OptIn(KspExperimental::class)\ninternal class ContributesRobotProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, KotlinInjectContextAware {\n\n  private val robotClassName = ClassName(\"software.amazon.app.platform.robot\", \"Robot\")\n  private val robotFqName = robotClassName.canonicalName\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesRobot::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkHasInjectAnnotation(it)\n        checkNotSingleton(it)\n        checkSuperType(it)\n        checkAppScope(it)\n      }\n      .forEach { generateComponentInterface(it) }\n\n    return emptyList()\n  }\n\n  private fun generateComponentInterface(clazz: KSClassDeclaration) {\n    val packageName = \"${APP_PLATFORM_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val componentClassName = ClassName(packageName, \"${clazz.innerClassNames()}Component\")\n\n    val fileSpec =\n      FileSpec.builder(componentClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(componentClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", clazz.scope().type.toClassName())\n                .build()\n            )\n            .apply {\n              if (!clazz.isAnnotationPresent(Inject::class)) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.innerClassNames()}\")\n                    .addAnnotation(Provides::class)\n                    .returns(clazz.toClassName())\n                    .addStatement(\"return %T()\", clazz.toClassName())\n                    .build()\n                )\n              }\n            }\n            .addFunction(\n              FunSpec.builder(\"provide${clazz.innerClassNames()}IntoMap\")\n                .addAnnotation(Provides::class)\n                .addAnnotation(IntoMap::class)\n                .addParameter(\n                  name = \"robot\",\n                  type = LambdaTypeName.get(returnType = clazz.toClassName()),\n                )\n                .returns(\n                  Pair::class.asClassName()\n                    .parameterizedBy(\n                      listOf(\n                        KClass::class.asClassName()\n                          .parameterizedBy(WildcardTypeName.producerOf(robotClassName)),\n                        LambdaTypeName.get(returnType = robotClassName),\n                      )\n                    )\n                )\n                .addStatement(\"return %T::class·to·robot\", clazz.toClassName())\n                .build()\n            )\n            .addProperty(name = clazz.innerClassNames().decapitalize(), type = clazz.toClassName())\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private fun checkHasInjectAnnotation(clazz: KSClassDeclaration) {\n    if (clazz.primaryConstructor?.parameters?.isNotEmpty() == true) {\n      check(clazz.annotations.any { it.isAnnotation(injectFqName) }, clazz) {\n        \"${clazz.simpleName.asString()} must be annotated with @Inject when \" +\n          \"injecting arguments into a robot.\"\n      }\n    }\n  }\n\n  private fun checkNotSingleton(clazz: KSClassDeclaration) {\n    check(clazz.annotations.none { it.isKotlinInjectScopeAnnotation() }, clazz) {\n      \"It's not allowed allowed for a robot to be a singleton, because the lifetime \" +\n        \"of the robot is scoped to the robot() factory function. Remove the @\" +\n        clazz.annotations.first { it.isKotlinInjectScopeAnnotation() }.shortName.asString() +\n        \" annotation.\"\n    }\n  }\n\n  private fun checkSuperType(clazz: KSClassDeclaration) {\n    val extendsRobot =\n      clazz.getAllSuperTypes().any { it.declaration.requireQualifiedName() == robotFqName }\n\n    check(extendsRobot, clazz) {\n      \"In order to use @ContributesRobot, ${clazz.simpleName.asString()} must \" +\n        \"implement $robotFqName.\"\n    }\n  }\n\n  private fun checkAppScope(clazz: KSClassDeclaration) {\n    val scope = clazz.scope().type.declaration.requireQualifiedName()\n    check(scope == AppScope::class.requireQualifiedName(), clazz) {\n      \"Robots can only be contributed to the AppScope for now. Scope $scope is unsupported.\"\n    }\n  }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/CommonSourceCode.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject\n\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.jetbrains.kotlin.descriptors.runtime.structure.primitiveByWrapper\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.lastmile.kotlin.inject.anvil.internal.Origin\n\ninternal val JvmCompilationResult.componentInterface: Class<*>\n  get() = classLoader.loadClass(\"software.amazon.test.ComponentInterface\")\n\ninternal val Class<*>.origin: Class<*>\n  get() = getAnnotation(Origin::class.java).value.java\n\ninternal val Class<*>.generatedComponent: Class<*>\n  get() =\n    classLoader.loadClass(\n      \"$OPEN_SOURCE_LOOKUP_PACKAGE.\" +\n        canonicalName.split(\".\").joinToString(separator = \"\") { it.capitalize() }\n    )\n\ninternal fun <T : Any> Class<*>.newComponent(vararg arguments: Any): T {\n  @Suppress(\"UNCHECKED_CAST\")\n  return classLoader\n    .loadClass(\"$packageName.Inject$simpleName\")\n    .getDeclaredConstructor(\n      *arguments.map { arg -> arg::class.java.primitiveByWrapper ?: arg::class.java }.toTypedArray()\n    )\n    .newInstance(*arguments) as T\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/Compilation.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject\n\nimport assertk.assertThat\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation\nimport com.tschuchort.compiletesting.SourceFile\nimport com.tschuchort.compiletesting.addPreviousResultToClasspath\nimport com.tschuchort.compiletesting.configureKsp\nimport java.io.File\nimport java.io.OutputStream\nimport java.nio.file.Files\nimport java.util.ServiceLoader\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.jetbrains.kotlin.config.JvmTarget\nimport software.amazon.app.platform.ksp.isError\nimport software.amazon.app.platform.ksp.isOk\n\n/** A simple API over a [KotlinCompilation] with extra configuration support for KSP processors. */\n// Inspired by Anvil:\n// https://github.com/square/anvil/blob/97e2cc0430311c6b0ed5341da95bb243b582fab8/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt\nclass Compilation internal constructor(val kotlinCompilation: KotlinCompilation) {\n\n  private var isCompiled = false\n  private var processorsConfigured = false\n\n  /** Configures the behavior of this compilation. */\n  fun configureAppPlatformProcessor(): Compilation = apply {\n    checkNotCompiled()\n    check(!processorsConfigured) { \"Processor should not be configured twice.\" }\n\n    processorsConfigured = true\n\n    kotlinCompilation.configureKsp() {\n      symbolProcessorProviders +=\n        ServiceLoader.load(\n          SymbolProcessorProvider::class.java,\n          SymbolProcessorProvider::class.java.classLoader,\n        )\n\n      processorOptions +=\n        \"software.amazon.lastmile.kotlin.inject.anvil.processor.\" + \"ContributesBindingProcessor\" to\n          \"disabled\"\n\n      // Run KSP embedded directly within this kotlinc invocation\n      withCompilation = true\n      incremental = true\n    }\n  }\n\n  /** Adds the given sources to this compilation with their packages and names inferred. */\n  fun addSources(@Language(\"kotlin\") vararg sources: String): Compilation = apply {\n    checkNotCompiled()\n    kotlinCompilation.sources += sources.mapIndexed { index, content ->\n      val packageDir =\n        content\n          .lines()\n          .firstOrNull { it.trim().startsWith(\"package \") }\n          ?.substringAfter(\"package \")\n          ?.replace('.', '/')\n          ?.let { \"$it/\" } ?: \"\"\n\n      val name =\n        \"${kotlinCompilation.workingDir.absolutePath}/sources/src/main/java/\" +\n          \"$packageDir/Source$index.kt\"\n\n      Files.createDirectories(File(name).parentFile.toPath())\n\n      SourceFile.kotlin(name, contents = content, trimIndent = true)\n    }\n  }\n\n  fun addPreviousCompilationResult(result: JvmCompilationResult): Compilation = apply {\n    checkNotCompiled()\n    kotlinCompilation.addPreviousResultToClasspath(result)\n  }\n\n  private fun checkNotCompiled() {\n    check(!isCompiled) {\n      \"Already compiled! Create a new compilation if you want to compile again.\"\n    }\n  }\n\n  /**\n   * Compiles the underlying [KotlinCompilation]. Note that if [configureAppPlatformProcessor] has\n   * not been called prior to this, it will be configured with default behavior.\n   */\n  fun compile(\n    @Language(\"kotlin\") vararg sources: String,\n    block: JvmCompilationResult.() -> Unit = {},\n  ): JvmCompilationResult {\n    checkNotCompiled()\n    if (!processorsConfigured) {\n      // Configure with default behaviors\n      configureAppPlatformProcessor()\n    }\n    addSources(*sources)\n    isCompiled = true\n\n    return kotlinCompilation.compile().apply(block)\n  }\n\n  companion object {\n    operator fun invoke(): Compilation {\n      return Compilation(\n        KotlinCompilation().apply {\n          // Sensible default behaviors\n          inheritClassPath = true\n          jvmTarget = JvmTarget.JVM_1_8.description\n          verbose = false\n        }\n      )\n    }\n  }\n}\n\n/**\n * Helpful for testing code generators in unit tests end to end.\n *\n * This covers common cases, but is built upon reusable logic in [Compilation] and\n * [Compilation.configureAppPlatformProcessor]. Consider using those APIs if more advanced\n * configuration is needed.\n */\nfun compile(\n  @Language(\"kotlin\") vararg sources: String,\n  allWarningsAsErrors: Boolean = true,\n  messageOutputStream: OutputStream = System.out,\n  workingDir: File? = null,\n  previousCompilationResult: JvmCompilationResult? = null,\n  moduleName: String? = null,\n  exitCode: KotlinCompilation.ExitCode = KotlinCompilation.ExitCode.OK,\n  block: JvmCompilationResult.() -> Unit = {},\n): JvmCompilationResult {\n  return Compilation()\n    .apply {\n      kotlinCompilation.apply {\n        this.allWarningsAsErrors = allWarningsAsErrors\n        this.messageOutputStream = messageOutputStream\n        if (workingDir != null) {\n          this.workingDir = workingDir\n        }\n        if (moduleName != null) {\n          this.moduleName = moduleName\n        }\n      }\n\n      if (previousCompilationResult != null) {\n        addPreviousCompilationResult(previousCompilationResult)\n      }\n    }\n    .configureAppPlatformProcessor()\n    .compile(*sources)\n    .also {\n      if (exitCode == KotlinCompilation.ExitCode.OK) {\n        assertThat(it.exitCode).isOk()\n      } else {\n        assertThat(it.exitCode).isError()\n      }\n    }\n    .also(block)\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/CompilerTestUtil.kt",
    "content": "package software.amazon.app.platform.inject\n\nimport java.lang.reflect.Method\n\n// Following changes to Kotlin starting in 2.2.0,\n// https://kotlinlang.org/docs/whatsnew22.html#changes-to-default-method-generation-for-interface-functions\n// default methods are generated where they previously weren't. For testing we only validate the non\n// synthetic methods.\ninternal val Class<*>.declaredNonSyntheticMethods: List<Method>\n  get() = declaredMethods.filterNot { it.isSynthetic }\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesBindingProcessorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.hasSize\nimport assertk.assertions.isEqualTo\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.OPEN_SOURCE_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.generatedComponent\nimport software.amazon.app.platform.inject.origin\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.ksp.isNotAnnotatedWith\n\nclass ContributesBindingProcessorTest {\n\n  @Test\n  fun `a component interface is generated in the lookup package for a contributed binding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n\n            @Inject\n            @ContributesBinding(Unit::class)\n            class Impl : Base \n            \"\"\"\n    ) {\n      val generatedComponent = impl.generatedComponent\n\n      assertThat(generatedComponent.packageName).isEqualTo(OPEN_SOURCE_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(impl)\n\n      val method = generatedComponent.declaredNonSyntheticMethods.single()\n      assertThat(method.name).isEqualTo(\"provideImplBase\")\n      assertThat(method.parameters.single().type).isEqualTo(impl)\n      assertThat(method.returnType).isEqualTo(base)\n      assertThat(method).isAnnotatedWith(Provides::class)\n    }\n  }\n\n  @Test\n  fun `a component interface is generated in the lookup package for an inner contributed binding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n\n            interface Impl {\n                @Inject\n                @ContributesBinding(Unit::class)\n                class Inner : Base\n            } \n            \"\"\"\n    ) {\n      val generatedComponent = impl.inner.generatedComponent\n\n      assertThat(generatedComponent.packageName).isEqualTo(OPEN_SOURCE_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(impl.inner)\n\n      val method = generatedComponent.declaredNonSyntheticMethods.single()\n      assertThat(method.name).isEqualTo(\"provideImplInnerBase\")\n      assertThat(method.parameters.single().type).isEqualTo(impl.inner)\n      assertThat(method.returnType).isEqualTo(base)\n      assertThat(method).isAnnotatedWith(Provides::class)\n    }\n  }\n\n  @Test\n  fun `the explicit bound type has a higher priority`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n            interface Base2 : Base\n\n            @Inject\n            @ContributesBinding(Unit::class, boundType = Base::class)\n            class Impl : Base2 \n\n            @Inject\n            @ContributesBinding(Unit::class)\n            class Impl2 : Base2 \n            \"\"\"\n    ) {\n      assertThat(impl.generatedComponent.declaredNonSyntheticMethods.single().returnType)\n        .isEqualTo(base)\n      assertThat(impl2.generatedComponent.declaredNonSyntheticMethods.single().returnType)\n        .isEqualTo(base2)\n    }\n  }\n\n  @Test\n  fun `it's an error when there's no super type`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            @Inject\n            @ContributesBinding(Unit::class)\n            class Impl \n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\"The bound type could not be determined for Impl. There are no super types.\")\n    }\n  }\n\n  @Test\n  fun `it's an error when there are multiple super types`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n            interface Base2\n\n            @Inject\n            @ContributesBinding(Unit::class)\n            class Impl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"The bound type could not be determined for Impl. \" +\n            \"There are multiple super types: Base, Base2.\"\n        )\n    }\n  }\n\n  @Test\n  fun `bindings are repeatable`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n            interface Base2\n\n            @Inject\n            @ContributesBinding(Unit::class, boundType = Base::class)\n            @ContributesBinding(Unit::class, boundType = Base2::class)\n            class Impl : Base, Base2 \n            \"\"\"\n    ) {\n      val generatedComponent = impl.generatedComponent\n\n      assertThat(generatedComponent.packageName).isEqualTo(OPEN_SOURCE_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(impl)\n\n      with(generatedComponent.declaredNonSyntheticMethods.single { it.name == \"provideImplBase\" }) {\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(base)\n        assertThat(this).isAnnotatedWith(Provides::class)\n      }\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single { it.name == \"provideImplBase2\" }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(base2)\n        assertThat(this).isAnnotatedWith(Provides::class)\n      }\n    }\n  }\n\n  @Test\n  fun `it's an error to use different scopes for multiple bindings`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n            interface Base2\n\n            @Inject\n            @ContributesBinding(scope = String::class, boundType = Base::class)\n            @ContributesBinding(scope = Unit::class, boundType = Base2::class)\n            class Impl : Base, Base2 \n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages).contains(\"All scopes on annotations must be the same.\")\n    }\n  }\n\n  @Test\n  fun `it's an error to duplicate the same binding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n\n            @Inject\n            @ContributesBinding(Unit::class, boundType = Base::class)\n            @ContributesBinding(Unit::class, boundType = Base::class)\n            class Impl : Base, Base2 \n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\"The same type should not be contributed twice: software.amazon.test.Base.\")\n    }\n  }\n\n  @Test\n  fun `a component interface is generated in the lookup package for a contributed multibinding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n\n            @Inject\n            @ContributesBinding(Unit::class, multibinding = true)\n            class Impl : Base \n            \"\"\"\n    ) {\n      val generatedComponent = impl.generatedComponent\n\n      assertThat(generatedComponent.packageName).isEqualTo(OPEN_SOURCE_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(impl)\n\n      val method = generatedComponent.declaredNonSyntheticMethods.single()\n      assertThat(method.name).isEqualTo(\"provideImplBaseMultibinding\")\n      assertThat(method.parameters.single().type).isEqualTo(impl)\n      assertThat(method.returnType).isEqualTo(base)\n      assertThat(method).isAnnotatedWith(Provides::class)\n      assertThat(method).isAnnotatedWith(IntoSet::class)\n    }\n  }\n\n  @Test\n  fun `both binding and multibinding component interfaces can be generated in the lookup package for a contributed multibinding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import me.tatarka.inject.annotations.Inject\n\n            interface Base\n\n            @Inject\n            @ContributesBinding(Unit::class, multibinding = false)\n            @ContributesBinding(Unit::class, multibinding = true)\n            class Impl : Base \n            \"\"\"\n    ) {\n      val generatedComponent = impl.generatedComponent\n\n      assertThat(generatedComponent.packageName).isEqualTo(OPEN_SOURCE_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(impl)\n\n      assertThat(generatedComponent.declaredNonSyntheticMethods).hasSize(2)\n\n      val bindingMethod =\n        generatedComponent.declaredNonSyntheticMethods.first { it.name == \"provideImplBase\" }\n      assertThat(bindingMethod.parameters.single().type).isEqualTo(impl)\n      assertThat(bindingMethod.returnType).isEqualTo(base)\n      assertThat(bindingMethod).isAnnotatedWith(Provides::class)\n      assertThat(bindingMethod).isNotAnnotatedWith(IntoSet::class)\n\n      val multibindingBindingMethod =\n        generatedComponent.declaredNonSyntheticMethods.first {\n          it.name == \"provideImplBaseMultibinding\"\n        }\n      assertThat(multibindingBindingMethod.parameters.single().type).isEqualTo(impl)\n      assertThat(multibindingBindingMethod.returnType).isEqualTo(base)\n      assertThat(multibindingBindingMethod).isAnnotatedWith(Provides::class)\n      assertThat(multibindingBindingMethod).isAnnotatedWith(IntoSet::class)\n    }\n  }\n\n  private val JvmCompilationResult.base: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base\")\n\n  private val JvmCompilationResult.base2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base2\")\n\n  private val JvmCompilationResult.impl: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Impl\")\n\n  private val JvmCompilationResult.impl2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Impl2\")\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesBindingScopedProcessorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.hasSize\nimport assertk.assertions.isEqualTo\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport kotlin.test.assertFailsWith\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.componentInterface\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.generatedComponent\nimport software.amazon.app.platform.inject.newComponent\nimport software.amazon.app.platform.inject.origin\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\nclass ContributesBindingScopedProcessorTest {\n\n  @Test\n  fun `a binding method for Scoped is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class)\n            class Impl : Base, Scoped\n            \"\"\"\n    ) {\n      val generatedComponent = impl.scopedComponent\n\n      assertThat(generatedComponent.origin).isEqualTo(impl)\n      assertThat(generatedComponent.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single { it.name == \"provideImplScoped\" }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(scoped)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoSet::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n    }\n  }\n\n  @Test\n  fun `a binding method for Scoped is generated for inner classes`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            interface Impl {\n                @Inject\n                @ContributesBinding(Unit::class)\n                class Inner : Base, Scoped\n            } \n            \"\"\"\n    ) {\n      val generatedComponent = impl.inner.scopedComponent\n\n      assertThat(generatedComponent.origin).isEqualTo(impl.inner)\n      assertThat(generatedComponent.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(Unit::class)\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideImplInnerScoped\"\n        }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(impl.inner)\n        assertThat(returnType).isEqualTo(scoped)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoSet::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(Unit::class)\n      }\n    }\n  }\n\n  @Test\n  fun `a binding method for Scoped is generated for repeated annotations`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n            interface Base2\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class, boundType = Base::class)\n            @ContributesBinding(AppScope::class, boundType = Base2::class)\n            class Impl : Base, Base2, Scoped\n            \"\"\"\n    ) {\n      val generatedComponent = impl.scopedComponent\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single { it.name == \"provideImplScoped\" }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(scoped)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoSet::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n    }\n  }\n\n  @Test\n  fun `a binding method for Scoped is generated without any other binding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class)\n            class Impl : Scoped\n            \"\"\"\n    ) {\n      val generatedComponent = impl.scopedComponent\n      with(generatedComponent.declaredNonSyntheticMethods.single()) {\n        assertThat(name).isEqualTo(\"provideImplScoped\")\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(scoped)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoSet::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n\n      // Because Scoped is the only super type.\n      assertFailsWith<ClassNotFoundException> { impl.generatedComponent }\n    }\n  }\n\n  @Test\n  fun `a binding method for Scoped is generated only explicitly when Scoped is part of the supertype hierarchy`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base : Scoped\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class)\n            class Impl : Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class, boundType = Base::class)\n            @ContributesBinding(AppScope::class, boundType = Scoped::class)\n            class Impl2 : Base\n            \"\"\"\n    ) {\n      with(impl.generatedComponent.declaredNonSyntheticMethods.single()) {\n        assertThat(name).isEqualTo(\"provideImplBase\")\n        assertThat(parameters.single().type).isEqualTo(impl)\n        assertThat(returnType).isEqualTo(base)\n        assertThat(this).isAnnotatedWith(Provides::class)\n      }\n      // Because Scoped is not a direct super type.\n      assertFailsWith<ClassNotFoundException> { impl.scopedComponent }\n\n      with(impl2.generatedComponent.declaredNonSyntheticMethods.single()) {\n        assertThat(parameters.single().type).isEqualTo(impl2)\n        assertThat(returnType).isEqualTo(base)\n        assertThat(this).isAnnotatedWith(Provides::class)\n      }\n      with(\n        impl2.scopedComponent.declaredNonSyntheticMethods.single { it.name == \"provideImpl2Scoped\" }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(impl2)\n        assertThat(returnType).isEqualTo(scoped)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoSet::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n    }\n  }\n\n  @Test\n  fun `scoped instances are added to the component`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.renderer.RendererComponent\n            import software.amazon.app.platform.robot.RobotComponent\n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Inject\n            import me.tatarka.inject.annotations.Component\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n            import software.amazon.lastmile.kotlin.inject.anvil.ForScope\n            import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesBinding(AppScope::class)\n            class Impl : Base, Scoped\n\n            @Inject\n            @SingleIn(Unit::class)\n            @ContributesBinding(Unit::class)\n            class Impl2 : Base, Scoped\n\n            @Component\n            @MergeComponent(AppScope::class, exclude = [RendererComponent::class, RobotComponent::class])\n            @SingleIn(AppScope::class)\n            interface ComponentInterface : ComponentInterfaceMerged {\n                @ForScope(AppScope::class)\n                val scoped: Set<Scoped>\n            }\n\n            @Component\n            @MergeComponent(Unit::class)\n            @SingleIn(Unit::class)\n            interface ComponentInterface2 : ComponentInterface2Merged {\n                @ForScope(Unit::class)\n                val scoped: Set<Scoped>\n            }\n            \"\"\"\n    ) {\n      val component = componentInterface.newComponent<Any>()\n\n      @Suppress(\"UNCHECKED_CAST\")\n      val scoped =\n        component::class\n          .java\n          .declaredNonSyntheticMethods\n          .single { it.name == \"getScoped\" }\n          .invoke(component) as Set<Scoped>\n\n      assertThat(scoped).hasSize(1)\n      assertThat(scoped.single()::class.java).isEqualTo(impl)\n\n      val component2 = componentInterface2.newComponent<Any>()\n\n      @Suppress(\"UNCHECKED_CAST\")\n      val scoped2 =\n        component2::class\n          .java\n          .declaredNonSyntheticMethods\n          .single { it.name == \"getScoped\" }\n          .invoke(component2) as Set<Scoped>\n\n      assertThat(scoped2).hasSize(1)\n      assertThat(scoped2.single()::class.java).isEqualTo(impl2)\n    }\n  }\n\n  private val Class<*>.scopedComponent: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$APP_PLATFORM_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(\"$packageName.\").split(\".\").joinToString(separator = \"\") {\n            it.capitalize()\n          } +\n          \"ScopedComponent\"\n      )\n\n  private val JvmCompilationResult.componentInterface2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.ComponentInterface2\")\n\n  private val JvmCompilationResult.base: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base\")\n\n  private val JvmCompilationResult.impl: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Impl\")\n\n  private val JvmCompilationResult.impl2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Impl2\")\n\n  private val scoped: Class<*>\n    get() = Scoped::class.java\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesMockImplGeneratorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.hasSize\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport java.lang.reflect.WildcardType\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.jetbrains.kotlin.descriptors.runtime.structure.parameterizedTypeArguments\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.componentInterface\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.mock.MockMode\nimport software.amazon.app.platform.inject.mock.RealImpl\nimport software.amazon.app.platform.inject.newComponent\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\nclass ContributesMockImplGeneratorTest {\n\n  @Test\n  fun `correct provides method is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base    \n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(Boolean::class.java)\n      assertThat(providesMethod.parameters[1].parameterizedType.parameterizedTypeArguments.single())\n        .isEqualTo(mockImpl)\n      assertThat(\n          providesMethod.parameters[2]\n            .parameterizedType\n            .parameterizedTypeArguments\n            .filterIsInstance<WildcardType>()\n            .single()\n            .upperBounds\n            .single()\n        )\n        .isEqualTo(base)\n      assertThat(providesMethod.parameters[2].annotations.single().annotationClass)\n        .isEqualTo(RealImpl::class)\n      assertThat(providesMethod.returnType).isEqualTo(base)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `correct provides method is generated with boundType`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            class MockImpl : Base    \n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(Boolean::class.java)\n      assertThat(providesMethod.parameters[1].parameterizedType.parameterizedTypeArguments.single())\n        .isEqualTo(mockImpl)\n      assertThat(\n          providesMethod.parameters[2]\n            .parameterizedType\n            .parameterizedTypeArguments\n            .filterIsInstance<WildcardType>()\n            .single()\n            .upperBounds\n            .single()\n        )\n        .isEqualTo(base)\n      assertThat(providesMethod.parameters[2].annotations.single().annotationClass)\n        .isEqualTo(RealImpl::class)\n      assertThat(providesMethod.returnType).isEqualTo(base)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `correct provides method for inner class is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            class MockImpl {\n                @ContributesMockImpl(AppScope::class)\n                class Inner : Base\n            }\n            \"\"\"\n    ) {\n      val component = mockImpl.inner.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(Boolean::class.java)\n      assertThat(providesMethod.parameters[1].parameterizedType.parameterizedTypeArguments.single())\n        .isEqualTo(mockImpl.inner)\n      assertThat(\n          providesMethod.parameters[2]\n            .parameterizedType\n            .parameterizedTypeArguments\n            .filterIsInstance<WildcardType>()\n            .single()\n            .upperBounds\n            .single()\n        )\n        .isEqualTo(base)\n      assertThat(providesMethod.parameters[2].annotations.single().annotationClass)\n        .isEqualTo(RealImpl::class)\n      assertThat(providesMethod.returnType).isEqualTo(base)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `an abstract class as bound type is supported`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            open class Base\n            \n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base()\n            \"\"\"\n    ) {\n      assertThat(mockImpl.component).isNotNull()\n    }\n  }\n\n  @Test\n  fun `repeated annotations produce correct component`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            @ContributesMockImpl(AppScope::class, boundType = Base2::class)\n            class MockImpl : Base, Base2\n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      assertThat(component.declaredNonSyntheticMethods.map { it.name }).contains(\"provideBase\")\n      assertThat(component.declaredNonSyntheticMethods.map { it.name }).contains(\"provideBase2\")\n    }\n  }\n\n  @Test\n  fun `repeated annotations of the same class type throws error`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            class MockImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\"The same type should not be contributed twice: software.amazon.test.Base.\")\n    }\n  }\n\n  @Test\n  fun `repeated annotations of different scopes throws error`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            @ContributesMockImpl(Unit::class, boundType = Base2::class)\n            class MockImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages).contains(\"All scopes on annotations must be the same.\")\n    }\n  }\n\n  @Test\n  fun `when no superType is defined, then an error is thrown`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            @ContributesMockImpl(AppScope::class)\n            class MockImpl\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"The bound type could not be determined for MockImpl. \" + \"There are no super types.\"\n        )\n    }\n  }\n\n  @Test\n  fun `the bound type can be different than the super type`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base : Base2\n            interface Base2\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base2::class)\n            class MockImpl : Base\n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(Boolean::class.java)\n      assertThat(providesMethod.parameters[1].parameterizedType.parameterizedTypeArguments.single())\n        .isEqualTo(mockImpl)\n      assertThat(\n          providesMethod.parameters[2]\n            .parameterizedType\n            .parameterizedTypeArguments\n            .filterIsInstance<WildcardType>()\n            .single()\n            .upperBounds\n            .single()\n        )\n        .isEqualTo(base2)\n      assertThat(providesMethod.parameters[2].annotations.single().annotationClass)\n        .isEqualTo(RealImpl::class)\n      assertThat(providesMethod.returnType).isEqualTo(base2)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `the bound type must be declared for multiple super types`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"The bound type could not be determined for MockImpl. \" +\n            \"There are multiple super types: Base, Base2.\"\n        )\n    }\n  }\n\n  @Test\n  fun `a provides method for the Scoped type is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base, Scoped\n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      with(component.declaredNonSyntheticMethods.single { it.name == \"provideBase\" }) {\n        assertThat(parameters[0].type).isEqualTo(Boolean::class.java)\n        assertThat(parameters[1].parameterizedType.parameterizedTypeArguments.single())\n          .isEqualTo(mockImpl)\n        assertThat(\n            parameters[2]\n              .parameterizedType\n              .parameterizedTypeArguments\n              .filterIsInstance<WildcardType>()\n              .single()\n              .upperBounds\n              .single()\n          )\n          .isEqualTo(base)\n        assertThat(parameters[2].annotations.single().annotationClass).isEqualTo(RealImpl::class)\n        assertThat(returnType).isEqualTo(base)\n\n        assertThat(getAnnotation(Provides::class.java)).isNotNull()\n      }\n\n      with(component.declaredNonSyntheticMethods.single { it.name == \"provideMockImplScoped\" }) {\n        assertThat(parameters[0].annotations.single().annotationClass).isEqualTo(MockMode::class)\n        assertThat(parameters[1].parameterizedType.parameterizedTypeArguments.single())\n          .isEqualTo(mockImpl)\n\n        assertThat(getAnnotation(Provides::class.java)).isNotNull()\n        assertThat(getAnnotation(IntoSet::class.java)).isNotNull()\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n    }\n  }\n\n  @Test\n  fun `a provides method for the Scoped type is skipped when the class is annotated with @ContributesBinding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n\n            interface Base\n            interface Base2\n            \n            @ContributesMockImpl(AppScope::class, boundType = Base::class)\n            @ContributesBinding(AppScope::class, boundType = Base2::class)\n            class MockImpl : Base, Base2, Scoped\n            \"\"\"\n    ) {\n      val component = mockImpl.component\n\n      assertThat(component.declaredNonSyntheticMethods.firstOrNull { it.name == \"provideBase\" })\n        .isNotNull()\n\n      assertThat(\n          component.declaredNonSyntheticMethods.firstOrNull { it.name == \"provideMockImplScoped\" }\n        )\n        .isNull()\n    }\n  }\n\n  @Test\n  fun `another super type besides Scoped is required`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            \n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Scoped\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages).contains(\"Scoped cannot be used as bound type.\")\n    }\n  }\n\n  @Test\n  fun `the mock or real impl are provided based on the mock mode flag`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.renderer.RendererComponent\n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.inject.mock.MockMode\n            import software.amazon.app.platform.robot.RobotComponent\n            import me.tatarka.inject.annotations.Component\n            import me.tatarka.inject.annotations.Inject\n            import me.tatarka.inject.annotations.Provides\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesRealImpl(AppScope::class)\n            class RealBaseImpl : Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base\n\n            @Component\n            @MergeComponent(AppScope::class, exclude = [RendererComponent::class, RobotComponent::class])\n            @SingleIn(AppScope::class)\n            abstract class ComponentInterface(\n                @get:Provides @get:MockMode val mockMode: Boolean,\n            ) : ComponentInterfaceMerged {\n                abstract val base: Base\n            }\n            \"\"\"\n    ) {\n      val componentMockModeTrue = componentInterface.newComponent<Any>(true)\n      val componentMockModeFalse = componentInterface.newComponent<Any>(false)\n\n      assertThat(\n          componentMockModeTrue::class\n              .java\n              .declaredNonSyntheticMethods\n              .single { it.name == \"getBase\" }\n              .invoke(componentMockModeTrue)::class\n            .java\n        )\n        .isEqualTo(mockImpl)\n\n      assertThat(\n          componentMockModeFalse::class\n              .java\n              .declaredNonSyntheticMethods\n              .single { it.name == \"getBase\" }\n              .invoke(componentMockModeFalse)::class\n            .java\n        )\n        .isEqualTo(realBaseImpl)\n    }\n  }\n\n  @Test\n  fun `the mock or real impl are provided in the Scoped set based on the mock mode flag`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.renderer.RendererComponent\n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.inject.mock.MockMode\n            import software.amazon.app.platform.robot.RobotComponent\n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Component\n            import me.tatarka.inject.annotations.Inject\n            import me.tatarka.inject.annotations.Provides\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ForScope\n            import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesRealImpl(AppScope::class)\n            class RealBaseImpl : Base, Scoped\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base, Scoped\n\n            @Component\n            @MergeComponent(AppScope::class, exclude = [RendererComponent::class, RobotComponent::class])\n            @SingleIn(AppScope::class)\n            abstract class ComponentInterface(\n                @get:Provides @get:MockMode val mockMode: Boolean,\n            ) : ComponentInterfaceMerged {\n                abstract val base: Base\n                \n                @ForScope(AppScope::class)\n                abstract val scoped: Set<Scoped>\n            }\n            \"\"\"\n    ) {\n      val componentMockModeTrue = componentInterface.newComponent<Any>(true)\n      val componentMockModeFalse = componentInterface.newComponent<Any>(false)\n\n      @Suppress(\"UNCHECKED_CAST\")\n      with(\n        componentMockModeTrue::class\n          .java\n          .declaredNonSyntheticMethods\n          .single { it.name == \"getScoped\" }\n          .invoke(componentMockModeTrue) as Set<Scoped>\n      ) {\n        assertThat(this).hasSize(2)\n        assertThat(singleOrNull { mockImpl.isAssignableFrom(it.javaClass) }).isNotNull()\n        assertThat(this).contains(Scoped.NO_OP)\n      }\n\n      @Suppress(\"UNCHECKED_CAST\")\n      with(\n        componentMockModeFalse::class\n          .java\n          .declaredNonSyntheticMethods\n          .single { it.name == \"getScoped\" }\n          .invoke(componentMockModeFalse) as Set<Scoped>\n      ) {\n        assertThat(this).hasSize(2)\n        assertThat(singleOrNull { realBaseImpl.isAssignableFrom(it.javaClass) }).isNotNull()\n        assertThat(this).contains(Scoped.NO_OP)\n      }\n    }\n  }\n\n  @Test\n  fun `a contributed real impl and mock impl can be excluded`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.renderer.RendererComponent\n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.inject.mock.MockMode\n            import software.amazon.app.platform.robot.RobotComponent\n            import me.tatarka.inject.annotations.Component\n            import me.tatarka.inject.annotations.Inject\n            import me.tatarka.inject.annotations.Provides\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesRealImpl(AppScope::class)\n            class RealBaseImpl : Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base\n\n            @Component\n            @MergeComponent(AppScope::class, exclude = [RendererComponent::class, RobotComponent::class, RealBaseImpl::class, MockImpl::class])\n            @SingleIn(AppScope::class)\n            abstract class ComponentInterface(\n                @get:Provides @get:MockMode val mockMode: Boolean,\n            ) : ComponentInterfaceMerged {\n                abstract val base: Base\n            }\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\"Cannot find an @Inject constructor or provider for: software.amazon.test.Base\")\n    }\n\n    // Test again and verify through the Scoped interface\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.renderer.RendererComponent\n            import software.amazon.app.platform.inject.mock.ContributesMockImpl\n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.inject.mock.MockMode\n            import software.amazon.app.platform.robot.RobotComponent\n            import software.amazon.app.platform.scope.Scoped\n            import me.tatarka.inject.annotations.Component\n            import me.tatarka.inject.annotations.Inject\n            import me.tatarka.inject.annotations.IntoSet\n            import me.tatarka.inject.annotations.Provides\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ForScope\n            import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            interface Base\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesRealImpl(AppScope::class)\n            class RealBaseImpl : Base, Scoped\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesMockImpl(AppScope::class)\n            class MockImpl : Base, Scoped\n\n            @Component\n            @MergeComponent(AppScope::class, exclude = [RendererComponent::class, RobotComponent::class, RealBaseImpl::class, MockImpl::class])\n            @SingleIn(AppScope::class)\n            abstract class ComponentInterface : ComponentInterfaceMerged {\n                \n                @ForScope(AppScope::class)\n                abstract val scoped: Set<Scoped>\n                \n                @Provides\n                @IntoSet\n                @ForScope(AppScope::class)\n                fun provideTestScoped(): Scoped = TestScoped\n            }\n\n            object TestScoped : Scoped\n            \"\"\"\n    ) {\n      val component = componentInterface.newComponent<Any>()\n\n      @Suppress(\"UNCHECKED_CAST\")\n      val scoped =\n        component::class\n          .java\n          .declaredNonSyntheticMethods\n          .single { it.name == \"getScoped\" }\n          .invoke(component) as Set<Scoped>\n\n      assertThat(scoped.single().javaClass.canonicalName)\n        .isEqualTo(\"software.amazon.test.TestScoped\")\n    }\n  }\n\n  private val JvmCompilationResult.base: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base\")\n\n  private val JvmCompilationResult.base2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base2\")\n\n  private val JvmCompilationResult.mockImpl: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.MockImpl\")\n\n  private val JvmCompilationResult.realBaseImpl: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.RealBaseImpl\")\n\n  private val Class<*>.component: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$APP_PLATFORM_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).split(\".\").joinToString(\n            separator = \"\"\n          ) {\n            it.capitalize()\n          } +\n          \"MockImplComponent\"\n      )\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesRealImplGeneratorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.jetbrains.kotlin.descriptors.runtime.structure.parameterizedTypeArguments\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.mock.MockMode\nimport software.amazon.app.platform.inject.mock.RealImpl\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\n\nclass ContributesRealImplGeneratorTest {\n\n  @Test\n  fun `correct provides method is generated when boundType is inferred`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesRealImpl(AppScope::class)\n            class RealImpl : Base\n            \"\"\"\n    ) {\n      val component = realImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(realImpl)\n      assertThat(providesMethod.returnType).isEqualTo(base)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n      assertThat(providesMethod.getAnnotation(RealImpl::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `correct provides method for inner class is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            class RealImpl {\n                @ContributesRealImpl(AppScope::class)\n                class Inner : Base\n            }\n            \"\"\"\n    ) {\n      val component = realImpl.inner.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(realImpl.inner)\n      assertThat(providesMethod.returnType).isEqualTo(base)\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n      assertThat(providesMethod.getAnnotation(RealImpl::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `repeated annotations produce correct component`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesRealImpl(AppScope::class, boundType = Base::class)\n            @ContributesRealImpl(AppScope::class, boundType = Base2::class)\n            class RealImpl : Base, Base2\n            \"\"\"\n    ) {\n      val component = realImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod1 =\n        component.declaredNonSyntheticMethods.single { it.name == \"provideBaseRealImpl\" }\n      val providesMethod2 =\n        component.declaredNonSyntheticMethods.single { it.name == \"provideBase2RealImpl\" }\n\n      assertThat(providesMethod1.parameters[0].type).isEqualTo(realImpl)\n      assertThat(providesMethod1.returnType).isEqualTo(base)\n      assertThat(providesMethod1.getAnnotation(Provides::class.java)).isNotNull()\n      assertThat(providesMethod1.getAnnotation(RealImpl::class.java)).isNotNull()\n\n      assertThat(providesMethod2.parameters[0].type).isEqualTo(realImpl)\n      assertThat(providesMethod2.returnType).isEqualTo(base2)\n      assertThat(providesMethod2.getAnnotation(Provides::class.java)).isNotNull()\n      assertThat(providesMethod2.getAnnotation(RealImpl::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `repeated annotations of the same class type throws error`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesRealImpl(AppScope::class, boundType = Base::class)\n            @ContributesRealImpl(AppScope::class, boundType = Base::class)\n            class RealImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\"The same type should not be contributed twice: software.amazon.test.Base.\")\n    }\n  }\n\n  @Test\n  fun `repeated annotations of different scopes throws error`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesRealImpl(AppScope::class, boundType = Base::class)\n            @ContributesRealImpl(Unit::class, boundType = Base2::class)\n            class RealImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages).contains(\"All scopes on annotations must be the same.\")\n    }\n  }\n\n  @Test\n  fun `when no superType is defined, then an error is thrown`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            @ContributesRealImpl(AppScope::class)\n            class RealImpl\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"The bound type could not be determined for RealImpl. \" + \"There are no super types.\"\n        )\n    }\n  }\n\n  @Test\n  fun `an abstract class as bound type is supported`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            open class Base\n            \n            @ContributesRealImpl(AppScope::class)\n            class RealImpl : Base()\n            \"\"\"\n    ) {\n      assertThat(realImpl.component).isNotNull()\n    }\n  }\n\n  @Test\n  fun `the bound type can be different than the super type`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base : Base2\n            interface Base2\n            \n            @ContributesRealImpl(AppScope::class, boundType = Base2::class)\n            class RealImpl : Base\n            \"\"\"\n    ) {\n      val component = realImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      val providesMethod = component.declaredNonSyntheticMethods.single()\n      assertThat(providesMethod.parameters[0].type).isEqualTo(realImpl)\n      assertThat(providesMethod.returnType).isEqualTo(base2)\n      assertThat(providesMethod.name).isEqualTo(\"provideBase2RealImpl\")\n\n      assertThat(providesMethod.getAnnotation(Provides::class.java)).isNotNull()\n      assertThat(providesMethod.getAnnotation(RealImpl::class.java)).isNotNull()\n    }\n  }\n\n  @Test\n  fun `the bound type must be declared for multiple super types`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            interface Base2\n            \n            @ContributesRealImpl(AppScope::class)\n            class RealImpl : Base, Base2\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"The bound type could not be determined for RealImpl. \" +\n            \"There are multiple super types: Base, Base2.\"\n        )\n    }\n  }\n\n  @Test\n  fun `a provides method for the Scoped type is generated`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface Base\n            \n            @ContributesRealImpl(AppScope::class)\n            class RealImpl : Base, Scoped\n            \"\"\"\n    ) {\n      val component = realImpl.component\n\n      assertThat(component.getAnnotation(ContributesTo::class.java)?.scope)\n        .isEqualTo(AppScope::class)\n\n      with(component.declaredNonSyntheticMethods.single { it.name == \"provideBaseRealImpl\" }) {\n        assertThat(parameters[0].type).isEqualTo(realImpl)\n        assertThat(returnType).isEqualTo(base)\n\n        assertThat(getAnnotation(Provides::class.java)).isNotNull()\n        assertThat(getAnnotation(RealImpl::class.java)).isNotNull()\n      }\n\n      with(component.declaredNonSyntheticMethods.single { it.name == \"provideRealImplScoped\" }) {\n        assertThat(parameters[0].annotations.single().annotationClass).isEqualTo(MockMode::class)\n        assertThat(parameters[1].parameterizedType.parameterizedTypeArguments.single())\n          .isEqualTo(realImpl)\n\n        assertThat(getAnnotation(Provides::class.java)).isNotNull()\n        assertThat(getAnnotation(IntoSet::class.java)).isNotNull()\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(AppScope::class)\n      }\n    }\n  }\n\n  @Test\n  fun `a provides method for the Scoped type is skipped when the class is annotated with @ContributesBinding`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\n\n            interface Base\n            interface Base2\n            \n            @ContributesRealImpl(AppScope::class, boundType = Base::class)\n            @ContributesBinding(AppScope::class, boundType = Base2::class)\n            class RealImpl : Base, Base2, Scoped\n            \"\"\"\n    ) {\n      val component = realImpl.component\n\n      with(component.declaredNonSyntheticMethods.single { it.name == \"provideBaseRealImpl\" }) {\n        assertThat(parameters[0].type).isEqualTo(realImpl)\n        assertThat(returnType).isEqualTo(base)\n\n        assertThat(getAnnotation(Provides::class.java)).isNotNull()\n        assertThat(getAnnotation(RealImpl::class.java)).isNotNull()\n      }\n\n      assertThat(\n          component.declaredNonSyntheticMethods.firstOrNull { it.name == \"provideRealImplScoped\" }\n        )\n        .isNull()\n    }\n  }\n\n  @Test\n  fun `another super type besides Scoped is required`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n            \n            import software.amazon.app.platform.inject.mock.ContributesRealImpl\n            import software.amazon.app.platform.scope.Scoped\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            \n            @ContributesRealImpl(AppScope::class)\n            class RealImpl : Scoped\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages).contains(\"Scoped cannot be used as bound type.\")\n    }\n  }\n\n  private val JvmCompilationResult.base: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base\")\n\n  private val JvmCompilationResult.base2: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Base2\")\n\n  private val JvmCompilationResult.realImpl: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.RealImpl\")\n\n  private val Class<*>.component: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$APP_PLATFORM_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).split(\".\").joinToString(\n            separator = \"\"\n          ) {\n            it.capitalize()\n          } +\n          \"RealImplComponent\"\n      )\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesRendererProcessorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsExactlyInAnyOrder\nimport assertk.assertions.containsOnly\nimport assertk.assertions.isEmpty\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNull\nimport assertk.assertions.startsWith\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport java.lang.reflect.Proxy\nimport kotlin.reflect.KClass\nimport me.tatarka.inject.annotations.IntoMap\nimport me.tatarka.inject.annotations.Provides\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.componentInterface\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.newComponent\nimport software.amazon.app.platform.inject.origin\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.renderer.RendererComponent\nimport software.amazon.app.platform.renderer.RendererScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\nclass ContributesRendererProcessorTest {\n\n  @Test\n  fun `a component interface is generated in the lookup package for a contributed renderer`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.inject.ContributesRenderer\n\n            class Model : BaseModel\n\n            @ContributesRenderer\n            class TestRenderer : Renderer<Model> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.rendererComponent\n\n      assertThat(generatedComponent.packageName).startsWith(APP_PLATFORM_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(testRenderer)\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererModel\"\n        }\n      ) {\n        assertThat(parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererModelKey\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(this.getAnnotation(ForScope::class.java).scope).isEqualTo(RendererScope::class)\n      }\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(model)\n\n      assertThat(componentInterface.newComponent<RendererComponent>().modelToRendererMapping.keys)\n        .containsOnly(model)\n\n      assertThat(componentInterface.newComponent<RendererComponent>().modelToRendererMapping.values)\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `a component interface is generated in the lookup package for a contributed renderer as inner class`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.inject.ContributesRenderer\n\n            class Model : BaseModel\n\n            class TestRenderer {\n                @ContributesRenderer\n                class Inner : Renderer<Model> {\n                    override fun render(model: Model) = Unit\n                }\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.inner.rendererComponent\n\n      assertThat(generatedComponent.packageName).startsWith(APP_PLATFORM_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(testRenderer.inner)\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererInner\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer.inner)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererInnerModel\"\n        }\n      ) {\n        assertThat(parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `a component interface is generated in the lookup package for a contributed renderer with a model as inner class`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.inject.ContributesRenderer\n\n            class Presenter {\n                class Model : BaseModel\n            }\n\n            @ContributesRenderer\n            class TestRenderer : Renderer<Presenter.Model> {\n                override fun render(model: Presenter.Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.rendererComponent\n\n      assertThat(generatedComponent.packageName).startsWith(APP_PLATFORM_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(testRenderer)\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererPresenterModel\"\n        }\n      ) {\n        assertThat(parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(presenter.model.kotlin)\n    }\n  }\n\n  @Test\n  fun `the explicit model type has a higher priority`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n\n            class Model : BaseModel\n            class Model2 : BaseModel\n\n            @ContributesRenderer(Model::class)\n            class TestRenderer : Renderer<Model2> {\n                override fun render(model: Model2) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `the model type can be inferred from the class hierarchy`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n\n            class Model : BaseModel\n            \n            interface OtherRenderer : Renderer<Model>\n\n            @ContributesRenderer\n            class TestRenderer : OtherRenderer {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\"\n    ) {\n      assertThat(testRenderer.modelType).isEqualTo(model)\n    }\n  }\n\n  @Test\n  fun `the model type can be inferred from the class hierarchy with multiple levels`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n\n            class Model : BaseModel\n            \n            interface OtherRenderer<S : Any, T : BaseModel, U : CharSequence> : Renderer<T>\n            interface OtherRenderer2<S : BaseModel, T : Any> : OtherRenderer<T, S, String>\n            interface OtherRenderer3 : OtherRenderer2<Model, Any>\n            interface OtherRenderer4 : OtherRenderer3\n    \n\n            @ContributesRenderer\n            class TestRenderer : OtherRenderer4 {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\"\n    ) {\n      assertThat(testRenderer.modelType).isEqualTo(model)\n    }\n  }\n\n  @Test\n  fun `the model type must be explicit when it cannot be inferred`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n\n            class Model1 : BaseModel\n            class Model2 : BaseModel\n            \n            interface OtherRenderer<S : BaseModel, T : BaseModel> : Renderer<S>\n\n            @ContributesRenderer\n            class TestRenderer : OtherRenderer<Model1, Model2> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Couldn't find BaseModel type for TestRenderer. Consider adding \" +\n            \"an explicit parameter.Found: software.amazon.test.Model1, software.amazon.test.Model2\"\n        )\n    }\n  }\n\n  @Test\n  fun `the component interface contains multiple binding methods for model hierarchies`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.inject.ContributesRenderer\n\n            interface Presenter {\n                sealed interface Model : BaseModel {\n                    sealed interface Inner : Model {\n                        data object Model1 : Inner\n                        data object Model2 : Inner\n                    }\n                    data object Model2 : Model\n\n                    // Note that this class doesn't extend Model.\n                    class OtherSubclass\n                }\n            }\n\n            @ContributesRenderer\n            class TestRenderer : Renderer<Presenter.Model> {\n                override fun render(model: Presenter.Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.rendererComponent\n\n      with(\n        generatedComponent.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      val bindingMethods =\n        generatedComponent.declaredNonSyntheticMethods.filter {\n          it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n            !it.name.endsWith(\"Key\")\n        }\n      assertThat(bindingMethods.map { it.name })\n        .containsExactlyInAnyOrder(\n          \"provideSoftwareAmazonTestTestRendererPresenterModel\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInner\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel1\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel2\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelModel2\",\n        )\n\n      bindingMethods.forEach {\n        assertThat(it.parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(it.returnType).isEqualTo(Pair::class.java)\n        assertThat(it).isAnnotatedWith(Provides::class)\n        assertThat(it).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsExactlyInAnyOrder(\n          presenter.model.kotlin,\n          presenter.model.inner.kotlin,\n          presenter.model.inner.model1.kotlin,\n          presenter.model.inner.model2.kotlin,\n          presenter.model.model2.kotlin,\n        )\n\n      val keyBindingMethods =\n        generatedComponent.declaredNonSyntheticMethods.filter {\n          it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n            it.name.endsWith(\"Key\")\n        }\n      assertThat(keyBindingMethods.map { it.name })\n        .containsExactlyInAnyOrder(\n          \"provideSoftwareAmazonTestTestRendererPresenterModelKey\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerKey\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel1Key\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel2Key\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelModel2Key\",\n        )\n\n      keyBindingMethods.forEach {\n        assertThat(it.parameters).isEmpty()\n        assertThat(it.returnType).isEqualTo(Pair::class.java)\n        assertThat(it).isAnnotatedWith(Provides::class)\n        assertThat(it).isAnnotatedWith(IntoMap::class)\n        assertThat(it).isAnnotatedWith(ForScope::class)\n      }\n\n      assertThat(componentInterface.newComponent<RendererComponent>().modelToRendererMapping.keys)\n        .containsExactlyInAnyOrder(\n          presenter.model.kotlin,\n          presenter.model.inner.kotlin,\n          presenter.model.inner.model1.kotlin,\n          presenter.model.inner.model2.kotlin,\n          presenter.model.model2.kotlin,\n        )\n\n      assertThat(\n          componentInterface\n            .newComponent<RendererComponent>()\n            .modelToRendererMapping\n            .values\n            .distinct()\n        )\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `the binding methods for subtypes are not generated when disabled`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.inject.ContributesRenderer\n\n            interface Presenter {\n                sealed interface Model : BaseModel {\n                    data object Model1 : Model\n                    data object Model2 : Model\n                }\n            }\n\n            @ContributesRenderer(includeSealedSubtypes = false)\n            class TestRenderer : Renderer<Presenter.Model> {\n                override fun render(model: Presenter.Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.rendererComponent\n\n      assertThat(\n          generatedComponent.declaredNonSyntheticMethods\n            .filter {\n              it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n                !it.name.endsWith(\"Key\")\n            }\n            .map { it.name }\n        )\n        .containsOnly(\"provideSoftwareAmazonTestTestRendererPresenterModel\")\n\n      assertThat(\n          generatedComponent.declaredNonSyntheticMethods\n            .filter { it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModelKey\") }\n            .map { it.name }\n        )\n        .containsOnly(\"provideSoftwareAmazonTestTestRendererPresenterModelKey\")\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(presenter.model.kotlin)\n\n      assertThat(componentInterface.newComponent<RendererComponent>().modelToRendererMapping.keys)\n        .containsOnly(presenter.model.kotlin)\n\n      assertThat(componentInterface.newComponent<RendererComponent>().modelToRendererMapping.values)\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `the component does not contain a binding for the renderer if it is annotated with @Inject`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import me.tatarka.inject.annotations.Inject\n\n            class Model : BaseModel\n\n            @ContributesRenderer\n            @Inject\n            class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val generatedComponent = testRenderer.rendererComponent\n\n      assertThat(generatedComponent.packageName).startsWith(APP_PLATFORM_LOOKUP_PACKAGE)\n      assertThat(generatedComponent.origin).isEqualTo(testRenderer)\n\n      assertThat(generatedComponent.declaredNonSyntheticMethods.map { it.name })\n        .containsOnly(\n          \"provideSoftwareAmazonTestTestRendererModel\",\n          \"provideSoftwareAmazonTestTestRendererModelKey\",\n        )\n\n      assertThat(componentInterface.newComponent<RendererComponent>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `when using @SingleIn(RendererScope_class) then a warning is printed`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import software.amazon.app.platform.renderer.RendererScope\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            class Model : BaseModel\n\n            @Inject\n            @SingleIn(RendererScope::class)\n            @ContributesRenderer\n            class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Source0.kt:15: Renderers should not be singletons in the \" +\n            \"RendererScope. The RendererFactory will cache the Renderer when \" +\n            \"necessary. Remove the @SingleIn(RendererScope::class) annotation.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it is redundant to add @Inject for a zero arg constructor`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n            import me.tatarka.inject.annotations.Inject\n\n            class Model : BaseModel\n\n            @ContributesRenderer\n            @Inject\n            class TestRenderer : Renderer<Model> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"It's redundant to use @Inject when using @ContributesRenderer \" +\n            \"for a Renderer with a zero-arg constructor.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it is required to use @Inject for a non-zero arg constructor`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n    \n            import software.amazon.app.platform.inject.ContributesRenderer\n            import software.amazon.app.platform.presenter.BaseModel\n            import software.amazon.app.platform.renderer.Renderer\n\n            class Model : BaseModel\n\n            @ContributesRenderer\n            class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n                override fun render(model: Model) = Unit\n            }\n            \"\"\",\n      componentInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"When using @ContributesRenderer and you need to inject types \" +\n            \"in the constructor, then it's necessary to add the @Inject annotation.\"\n        )\n    }\n  }\n\n  @Language(\"kotlin\")\n  private val componentInterfaceSource =\n    \"\"\"\n        package software.amazon.test\n\n        import software.amazon.app.platform.renderer.RendererComponent\n        import software.amazon.app.platform.renderer.RendererScope\n        import me.tatarka.inject.annotations.Component\n        import me.tatarka.inject.annotations.Provides\n        import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n        import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n        @Component\n        @MergeComponent(RendererScope::class)\n        @SingleIn(RendererScope::class)\n        abstract class ComponentInterface : ComponentInterfaceMerged, RendererComponent {\n            @Provides fun provideString(): String = \"abc\"\n        }\n    \"\"\"\n\n  private val JvmCompilationResult.testRenderer: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.TestRenderer\")\n\n  private val JvmCompilationResult.model: KClass<out Any>\n    get() = classLoader.loadClass(\"software.amazon.test.Model\").kotlin\n\n  private val JvmCompilationResult.presenter: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Presenter\")\n\n  private val Class<*>.model: Class<*>\n    get() = classes.single { it.simpleName == \"Model\" }\n\n  private val Class<*>.model1: Class<*>\n    get() = classes.single { it.simpleName == \"Model1\" }\n\n  private val Class<*>.model2: Class<*>\n    get() = classes.single { it.simpleName == \"Model2\" }\n\n  private val Class<*>.rendererComponent: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$APP_PLATFORM_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).replace(\".\", \"\") +\n          \"Component\"\n      )\n\n  private val Class<*>.defaultImpl: Class<*>\n    get() = classLoader.loadClass(\"$canonicalName\\$DefaultImpls\")\n\n  private val Class<*>.modelType: KClass<*>\n    get() {\n      // This reflection code is somewhat disgusting, but it works. Our processor generates\n      // an interface with functions that have a default implementation. We load the class\n      // for the default implementation that is an output of the Kotlin compiler.\n      //\n      // Then, in the class for default implementations we find the function that returns\n      // the binding for map-multibindings, which is a Pair<KClass<*>, Function0<Renderer>.\n      // The function is static, but requires two parameters. We stub the parameters by\n      // instantiating a Proxy instance.\n      //\n      // After invoking the function we get the actual Pair<KClass<*>, ..>, which key is\n      // the model type we're looking for.\n      val defaultImpls = rendererComponent.defaultImpl\n\n      val proxy =\n        Proxy.newProxyInstance(classLoader, arrayOf(rendererComponent, Function0::class.java)) {\n          _,\n          _,\n          _ ->\n          throw NotImplementedError()\n        }\n\n      val mapBindingMethod =\n        defaultImpls.methods.single { it.name == \"provideSoftwareAmazonTestTestRendererModel\" }\n\n      return (mapBindingMethod.invoke(null, proxy, proxy) as Pair<*, *>).first as KClass<*>\n    }\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/processor/ContributesRobotGeneratorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsOnly\nimport assertk.assertions.isEmpty\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport me.tatarka.inject.annotations.IntoMap\nimport me.tatarka.inject.annotations.Provides\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.APP_PLATFORM_LOOKUP_PACKAGE\nimport software.amazon.app.platform.inject.compile\nimport software.amazon.app.platform.inject.componentInterface\nimport software.amazon.app.platform.inject.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.newComponent\nimport software.amazon.app.platform.inject.origin\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.robot.RobotComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\nclass ContributesRobotGeneratorTest {\n\n  @Test\n  fun `a component interface is generated without @Inject constructor`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            @ContributesRobot(AppScope::class)\n            class TestRobot : Robot\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val robotComponent = testRobot.component\n\n      assertThat(robotComponent.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n      assertThat(robotComponent.origin).isEqualTo(testRobot)\n\n      with(robotComponent.declaredNonSyntheticMethods.single { it.name == \"provideTestRobot\" }) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRobot)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        robotComponent.declaredNonSyntheticMethods.single { it.name == \"provideTestRobotIntoMap\" }\n      ) {\n        assertThat(parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(componentInterface.newComponent<RobotComponent>().robots.keys)\n        .containsOnly(testRobot.kotlin)\n    }\n  }\n\n  @Test\n  fun `a component interface is generated with @Inject constructor`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            @Inject\n            @ContributesRobot(AppScope::class)\n            class TestRobot : Robot\n            \"\"\",\n      componentInterfaceSource,\n    ) {\n      val robotComponent = testRobot.component\n\n      assertThat(robotComponent.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n      assertThat(robotComponent.origin).isEqualTo(testRobot)\n\n      assertThat(\n          robotComponent.declaredNonSyntheticMethods.singleOrNull { it.name == \"provideTestRobot\" }\n        )\n        .isNull()\n\n      with(\n        robotComponent.declaredNonSyntheticMethods.single { it.name == \"provideTestRobotIntoMap\" }\n      ) {\n        assertThat(parameters.single().type.canonicalName)\n          .isEqualTo(\"kotlin.jvm.functions.Function0\")\n        assertThat(returnType).isEqualTo(Pair::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(componentInterface.newComponent<RobotComponent>().robots.keys)\n        .containsOnly(testRobot.kotlin)\n    }\n  }\n\n  @Test\n  fun `a component interface is generated without direct super type`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface BaseRobot1 : Robot\n            abstract class BaseRobot2 : BaseRobot1\n\n            @ContributesRobot(AppScope::class)\n            class TestRobot : BaseRobot2()\n            \"\"\"\n    ) {\n      assertThat(testRobot.component).isNotNull()\n    }\n  }\n\n  @Test\n  fun `the robot class must be a super type`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            interface BaseRobot1\n            abstract class BaseRobot2 : BaseRobot1\n\n            @ContributesRobot(AppScope::class)\n            class TestRobot : BaseRobot2()\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"In order to use @ContributesRobot, TestRobot must implement \" +\n            \"software.amazon.app.platform.robot.Robot.\"\n        )\n    }\n  }\n\n  @Test\n  fun `a Robot must not be a singleton`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import me.tatarka.inject.annotations.Inject\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n            import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n            @Inject\n            @SingleIn(AppScope::class)\n            @ContributesRobot(AppScope::class)\n            class TestRobot : Robot\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"It's not allowed allowed for a robot to be a singleton, because \" +\n            \"the lifetime of the robot is scoped to the robot() factory function. \" +\n            \"Remove the @SingleIn annotation.\"\n        )\n    }\n  }\n\n  @Test\n  fun `only the app scope is supported for now`() {\n    compile(\n      \"\"\"\n            package software.amazon.test\n\n            import software.amazon.app.platform.inject.robot.ContributesRobot\n            import software.amazon.app.platform.robot.Robot\n            import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n            @ContributesRobot(String::class)\n            class TestRobot : Robot\n            \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Robots can only be contributed to the AppScope for now. \" +\n            \"Scope kotlin.String is unsupported.\"\n        )\n    }\n  }\n\n  @Language(\"kotlin\")\n  private val componentInterfaceSource =\n    \"\"\"\n        package software.amazon.test\n\n        import software.amazon.app.platform.renderer.RendererComponent\n        import me.tatarka.inject.annotations.Component\n        import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n        import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\n        import software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n        @Component\n        @MergeComponent(AppScope::class, exclude = [RendererComponent::class])\n        @SingleIn(AppScope::class)\n        interface ComponentInterface : ComponentInterfaceMerged\n    \"\"\"\n\n  private val JvmCompilationResult.testRobot: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.TestRobot\")\n\n  private val Class<*>.component: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$APP_PLATFORM_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).split(\".\").joinToString(\n            separator = \"\"\n          ) {\n            it.capitalize()\n          } +\n          \"Component\"\n      )\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/api/android/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesMockImpl : java/lang/annotation/Annotation {\n\tpublic abstract fun boundType ()Ljava/lang/Class;\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesMockImpl$Container : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()[Lsoftware/amazon/app/platform/inject/mock/ContributesMockImpl;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesRealImpl : java/lang/annotation/Annotation {\n\tpublic abstract fun boundType ()Ljava/lang/Class;\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesRealImpl$Container : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()[Lsoftware/amazon/app/platform/inject/mock/ContributesRealImpl;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/MockMode : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/RealImpl : java/lang/annotation/Annotation {\n}\n\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/api/desktop/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesMockImpl : java/lang/annotation/Annotation {\n\tpublic abstract fun boundType ()Ljava/lang/Class;\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesMockImpl$Container : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()[Lsoftware/amazon/app/platform/inject/mock/ContributesMockImpl;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesRealImpl : java/lang/annotation/Annotation {\n\tpublic abstract fun boundType ()Ljava/lang/Class;\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/ContributesRealImpl$Container : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()[Lsoftware/amazon/app/platform/inject/mock/ContributesRealImpl;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/MockMode : java/lang/annotation/Annotation {\n}\n\npublic abstract interface annotation class software/amazon/app/platform/inject/mock/RealImpl : java/lang/annotation/Annotation {\n}\n\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enablePublishing true\n}\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/src/commonMain/kotlin/software/amazon/app/platform/inject/mock/ContributesMockImpl.kt",
    "content": "package software.amazon.app.platform.inject.mock\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation\n\n/**\n * Used to contribute a mocked implementation to a given interface that has a real implementation as\n * well.\n *\n * ```\n * @ContributesMockImpl(AppScope::class)\n * @Inject\n * @SingleIn(AppScope::class)\n * class MockVts : Vts\n * ```\n *\n * This annotation will generate the following kotlin-inject component:\n * ```\n * @ContributesTo(AppScope::class)\n * interface MockVtsMockComponent {\n *      @Provides\n *      fun provideTestVts(\n *          @MockMode mockMode: Boolean,\n *          mockVts: () -> MockVts,\n *          @RealImpl realVts: () -> Vts,\n *       ): Vts = if (mockMode) mockVts() else realVts()\n * }\n * ```\n *\n * This annotation is also repeatable:\n * ```\n * @ContributesMockImpl(AppScope::class, boundType = Vts::class)\n * @ContributesMockImpl(AppScope::class, boundType = Vts2::class)\n * class MockVts : Vts, Vts2\n * ```\n *\n * It is safe to implement the [Scoped] interface. [Scoped.onEnterScope] and [Scoped.onExitScope]\n * will only be called if the mock implementation is used at runtime:\n * ```\n * @ContributesMockImpl(AppScope::class)\n * @Inject\n * @SingleIn(AppScope::class)\n * class MockVts : Vts, Scoped\n * ```\n */\n@Repeatable\n@ContributingAnnotation\npublic annotation class ContributesMockImpl(\n  /** The scope in which to include this contributed binding. */\n  val scope: KClass<*>,\n\n  /**\n   * The type that this class is bound to, this is required when there is more than a single\n   * superType or the superType is not an interface.\n   */\n  val boundType: KClass<*> = Unit::class,\n)\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/src/commonMain/kotlin/software/amazon/app/platform/inject/mock/ContributesRealImpl.kt",
    "content": "package software.amazon.app.platform.inject.mock\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation\n\n/**\n * Used to contribute a real implementation to a given interface that has a mocked implementation as\n * well.\n *\n * ```\n * @ContributesRealImpl(AppScope::class)\n * @Inject\n * @SingleIn(AppScope::class)\n * class RealVts : Vts\n * ```\n *\n * This annotation will generate the following kotlin-inject component:\n * ```\n * @ContributesTo(AppScope::class)\n * interface RealVtsRealImplComponent {\n *      @Provides\n *      @RealImpl\n *      fun provideVts(realVts: RealVts): Vts = realVts\n * }\n * ```\n *\n * This annotation is also repeatable, where for each bound type a provider method will be\n * generated:\n * ```\n * @ContributesRealImpl(AppScope::class, boundType = Vts::class)\n * @ContributesRealImpl(AppScope::class, boundType = Vts2::class)\n * class RealVts : Vts, Vts2\n * ```\n *\n * It is safe to implement the [Scoped] interface. [Scoped.onEnterScope] and [Scoped.onExitScope]\n * will only be called if the real implementation is used at runtime:\n * ```\n * @ContributesRealImpl(AppScope::class)\n * @Inject\n * @SingleIn(AppScope::class)\n * class RealVts : Vts, Scoped\n * ```\n */\n@Repeatable\n@ContributingAnnotation\npublic annotation class ContributesRealImpl(\n  /** The scope in which to include this contributed binding. */\n  val scope: KClass<*>,\n\n  /**\n   * The type that this class is bound to, this is required when there is more than a single\n   * superType or the superType is not an interface.\n   */\n  val boundType: KClass<*> = Unit::class,\n)\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/src/commonMain/kotlin/software/amazon/app/platform/inject/mock/MockMode.kt",
    "content": "package software.amazon.app.platform.inject.mock\n\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier\n\n/**\n * Annotation to produce a Boolean that signals if available mocked implementations should be used.\n *\n * Example of @MockMode being used to decide the implementation to use for ExampleService:\n * ```\n * @Provide\n * fun provideExampleService (\n *      realService: () -> RealExampleService,\n *      mockService: () -> FakeExampleService,\n *      @MockMode mockMode: Boolean,\n * ): ExampleService {\n *      return if (mockMode) mockService() else realService()\n * }\n * ```\n */\n@Qualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class MockMode\n"
  },
  {
    "path": "kotlin-inject-extensions/contribute/public/src/commonMain/kotlin/software/amazon/app/platform/inject/mock/RealImpl.kt",
    "content": "package software.amazon.app.platform.inject.mock\n\nimport kotlin.annotation.AnnotationRetention.RUNTIME\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.annotation.AnnotationTarget.FUNCTION\nimport kotlin.annotation.AnnotationTarget.PROPERTY\nimport kotlin.annotation.AnnotationTarget.PROPERTY_GETTER\nimport kotlin.annotation.AnnotationTarget.TYPE\nimport kotlin.annotation.AnnotationTarget.VALUE_PARAMETER\nimport me.tatarka.inject.annotations.Qualifier\n\n/**\n * A qualifier that is used when generating a binds method using [ContributesRealImpl] to denote the\n * realImpl of an interface.\n *\n * This annotation should not be used directly and only used within ContributesRealImplGenerator and\n * ContributesMockImplGenerator.\n */\n@Qualifier\n@Retention(RUNTIME)\n@Target(CLASS, FUNCTION, PROPERTY_GETTER, VALUE_PARAMETER, TYPE, PROPERTY)\npublic annotation class RealImpl\n"
  },
  {
    "path": "ksp-common/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib.jvm'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ndependencies {\n    api libs.ksp.api\n\n    api libs.kotlin.poet\n    api libs.kotlin.poet.ksp\n\n    // Gives us access to annotations.\n    api project(':di-common:public')\n    api project(':scope:public')\n}\n\n// We don't need the apiCheck in this module.\ntasks.named('apiCheck').configure {\n    it.enabled = false\n}\ntasks.named('apiDump').configure {\n    it.enabled = false\n}\n"
  },
  {
    "path": "ksp-common/public/src/main/kotlin/software/amazon/app/platform/ksp/CompositeSymbolProcessor.kt",
    "content": "package software.amazon.app.platform.ksp\n\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\n\npublic class CompositeSymbolProcessor(vararg symbolProcessors: SymbolProcessor) : SymbolProcessor {\n\n  private val symbolProcessors = symbolProcessors.sortedBy { it::class.qualifiedName }\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    return symbolProcessors.flatMap { it.process(resolver) }\n  }\n\n  override fun finish() {\n    symbolProcessors.forEach { it.finish() }\n  }\n\n  override fun onError() {\n    symbolProcessors.forEach { it.onError() }\n  }\n}\n"
  },
  {
    "path": "ksp-common/public/src/main/kotlin/software/amazon/app/platform/ksp/ContextAware.kt",
    "content": "package software.amazon.app.platform.ksp\n\nimport com.google.devtools.ksp.getVisibility\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSDeclaration\nimport com.google.devtools.ksp.symbol.KSFile\nimport com.google.devtools.ksp.symbol.KSNode\nimport com.google.devtools.ksp.symbol.KSType\nimport com.google.devtools.ksp.symbol.KSTypeAlias\nimport com.google.devtools.ksp.symbol.Visibility\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.asClassName\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.scope.Scoped\n\n@Suppress(\"TooManyFunctions\")\npublic interface ContextAware {\n  public val logger: KSPLogger\n\n  private val anyFqName\n    get() = Any::class.requireQualifiedName()\n\n  public val scopedFqName: String\n    get() = Scoped::class.requireQualifiedName()\n\n  public val scopedClassName: ClassName\n    get() = Scoped::class.asClassName()\n\n  public fun <T : Any> requireNotNull(value: T?, symbol: KSNode?, lazyMessage: () -> String): T {\n    if (value == null) {\n      val message = lazyMessage()\n      logger.error(message, symbol)\n      throw IllegalArgumentException(message)\n    }\n\n    return value\n  }\n\n  public fun check(condition: Boolean, symbol: KSNode?, lazyMessage: () -> String) {\n    if (!condition) {\n      val message = lazyMessage()\n      logger.error(message, symbol)\n      throw IllegalStateException(message)\n    }\n  }\n\n  public fun checkIsPublic(clazz: KSClassDeclaration) {\n    check(clazz.getVisibility() == Visibility.PUBLIC, clazz) {\n      \"Contributed component interfaces must be public.\"\n    }\n  }\n\n  public fun checkHasScope(clazz: KSClassDeclaration) {\n    // Ensures that the value is non-null.\n    clazz.scope()\n  }\n\n  public fun KSClassDeclaration.scope(): MergeScope {\n    return requireNotNull(scopeOrNull(), this) { \"Couldn't find scope for $this.\" }\n  }\n\n  private fun KSClassDeclaration.scopeOrNull(): MergeScope? {\n    val annotationsWithScopeParameter =\n      annotations\n        .filter { it.hasScopeParameter() }\n        .toList()\n        .ifEmpty {\n          return null\n        }\n\n    return scopeForAnnotationsWithScopeParameters(this, annotationsWithScopeParameter)\n  }\n\n  private fun KSAnnotation.hasScopeParameter(): Boolean {\n    return (annotationType.resolve().declaration as? KSClassDeclaration)\n      ?.primaryConstructor\n      ?.parameters\n      ?.firstOrNull()\n      ?.name\n      ?.asString() == \"scope\"\n  }\n\n  private fun scopeForAnnotationsWithScopeParameters(\n    clazz: KSClassDeclaration,\n    annotations: List<KSAnnotation>,\n  ): MergeScope {\n    val explicitScopes = annotations.map { annotation -> annotation.scopeParameter() }\n\n    explicitScopes.scan(explicitScopes.first().declaration.requireQualifiedName()) { previous, next\n      ->\n      check(previous == next.declaration.requireQualifiedName(), clazz) {\n        \"All scopes on annotations must be the same.\"\n      }\n      previous\n    }\n\n    return MergeScope(explicitScopes.first())\n  }\n\n  private fun KSAnnotation.scopeParameter(): KSType {\n    return requireNotNull(scopeParameterOrNull(), this) { \"Couldn't find a scope parameter.\" }\n  }\n\n  private fun KSAnnotation.scopeParameterOrNull(): KSType? {\n    return arguments.firstOrNull { it.name?.asString() == \"scope\" }?.let { it.value as? KSType }\n  }\n\n  public fun KSClassDeclaration.findAnnotation(annotation: KClass<out Annotation>): KSAnnotation =\n    findAnnotations(annotation).single()\n\n  public fun KSClassDeclaration.findAnnotations(\n    annotation: KClass<out Annotation>\n  ): List<KSAnnotation> {\n    val fqName = annotation.requireQualifiedName()\n    return annotations\n      .filter { it.isAnnotation(fqName) }\n      .toList()\n      .also {\n        check(it.isNotEmpty(), this) {\n          \"Couldn't find the @${annotation.simpleName} annotation for $this.\"\n        }\n      }\n  }\n\n  public fun KSAnnotation.isAnnotation(fqName: String): Boolean {\n    return annotationType.resolve().declaration.requireQualifiedName() == fqName\n  }\n\n  public fun KSDeclaration.requireContainingFile(): KSFile =\n    requireNotNull(containingFile, this) { \"Containing file was null for $this\" }\n\n  public fun KSDeclaration.requireQualifiedName(): String =\n    requireNotNull(qualifiedName?.asString(), this) { \"Qualified name was null for $this\" }\n\n  public fun KClass<*>.requireQualifiedName(): String =\n    requireNotNull(qualifiedName) { \"Qualified name was null for $this\" }\n\n  public fun Resolver.getSymbolsWithAnnotation(annotation: KClass<*>): Sequence<KSAnnotated> =\n    getSymbolsWithAnnotation(annotation.requireQualifiedName())\n\n  public fun KSDeclaration.innerClassNames(separator: String = \"\"): String {\n    val classNames = requireQualifiedName().substring(packageName.asString().length + 1)\n    return classNames.replace(\".\", separator)\n  }\n\n  public fun KSType.isScoped(): Boolean {\n    return declaration.requireQualifiedName() == scopedFqName ||\n      (declaration as? KSTypeAlias)?.type?.resolve()?.declaration?.requireQualifiedName() ==\n        scopedFqName\n  }\n\n  @Suppress(\"ReturnCount\")\n  public fun boundType(clazz: KSClassDeclaration, annotation: KSAnnotation): KSType {\n    boundTypeFromAnnotation(annotation)?.let {\n      return it\n    }\n\n    // The bound type is not defined in the annotation, let's inspect the super types.\n    val superTypes =\n      clazz.superTypes\n        .map { it.resolve() }\n        .filter { it.declaration.requireQualifiedName() != anyFqName }\n        .toList()\n\n    when (superTypes.size) {\n      0 -> {\n        val message =\n          \"The bound type could not be determined for \" +\n            \"${clazz.simpleName.asString()}. There are no super types.\"\n        logger.error(message, clazz)\n        throw IllegalArgumentException(message)\n      }\n\n      1 -> {\n        return superTypes.single()\n      }\n\n      else -> {\n        if (superTypes.size == 2) {\n          // Ignore Scoped as super type.\n          superTypes\n            .singleOrNull { !it.isScoped() }\n            ?.let {\n              return it\n            }\n        }\n\n        val message =\n          \"The bound type could not be determined for \" +\n            \"${clazz.simpleName.asString()}. There are multiple super types: \" +\n            superTypes.joinToString { it.declaration.simpleName.asString() } +\n            \".\"\n        logger.error(message, clazz)\n        throw IllegalArgumentException(message)\n      }\n    }\n  }\n\n  public fun boundTypeFromAnnotation(annotation: KSAnnotation): KSType? {\n    return annotation.arguments\n      .firstOrNull { it.name?.asString() == \"boundType\" }\n      ?.let { it.value as? KSType }\n      ?.takeIf { it.declaration.requireQualifiedName() != Unit::class.requireQualifiedName() }\n  }\n\n  public fun checkNoDuplicateBoundTypes(\n    clazz: KSClassDeclaration,\n    annotations: List<KSAnnotation>,\n  ) {\n    annotations\n      .mapNotNull { boundTypeFromAnnotation(it) }\n      .map { it.declaration.requireQualifiedName() }\n      .takeIf { it.isNotEmpty() }\n      ?.reduce { previous, next ->\n        check(previous != next, clazz) { \"The same type should not be contributed twice: $next.\" }\n\n        previous\n      }\n  }\n\n  public fun KSClassDeclaration.findAnnotationsAtLeastOne(\n    annotation: KClass<out Annotation>\n  ): List<KSAnnotation> {\n    return findAnnotations(annotation).also {\n      check(it.isNotEmpty(), this) {\n        \"Couldn't find the @${annotation.simpleName} annotation for $this.\"\n      }\n    }\n  }\n\n  /** Return `software.amazon.Test` into `ComAmazonTest`. */\n  public val KSClassDeclaration.safeClassName: String\n    get() = requireQualifiedName().split(\".\").joinToString(separator = \"\") { it.capitalize() }\n}\n"
  },
  {
    "path": "ksp-common/public/src/main/kotlin/software/amazon/app/platform/ksp/MergeScope.kt",
    "content": "package software.amazon.app.platform.ksp\n\nimport com.google.devtools.ksp.symbol.KSType\n\n/**\n * Represents the destination of contributed types and which types should be merged during the merge\n * phase, e.g.\n *\n * ```\n * @ContributesTo(AppScope::class)\n * interface ContributedComponentInterface\n *\n * @Component\n * @MergeComponent(AppScope::class)\n * interface MergedComponent\n * ```\n *\n * Where `AppScope` would represent the \"MergeScope\".\n */\npublic data class MergeScope(val type: KSType)\n"
  },
  {
    "path": "ksp-common/public/src/main/kotlin/software/amazon/app/platform/ksp/Util.kt",
    "content": "package software.amazon.app.platform.ksp\n\nimport com.google.devtools.ksp.isDefault\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtools.ksp.symbol.KSValueArgument\nimport java.util.Locale\n\npublic fun String.decapitalize(): String = replaceFirstChar { it.lowercase(Locale.US) }\n\npublic fun String.capitalize(): String = replaceFirstChar {\n  if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString()\n}\n\npublic inline fun <reified T> KSAnnotation.argumentOfTypeAt(\n  context: ContextAware,\n  name: String,\n): T? {\n  return argumentOfTypeWithMapperAt<T, T>(context, name) { _, value -> value }\n}\n\npublic inline fun <reified T, R> KSAnnotation.argumentOfTypeWithMapperAt(\n  context: ContextAware,\n  name: String,\n  mapper: (arg: KSValueArgument, value: T) -> R,\n): R? {\n  return argumentAt(name)?.let { arg ->\n    val value = arg.value\n    context.check(value is T, arg) {\n      \"Expected argument '$name' of type '${T::class.qualifiedName} but was '${arg.javaClass.name}'.\"\n    }\n    (value as T)?.let { mapper(arg, it) }\n  }\n}\n\npublic fun KSAnnotation.argumentAt(name: String): KSValueArgument? {\n  return arguments.find { it.name?.asString() == name }?.takeUnless { it.isDefault() }\n}\n"
  },
  {
    "path": "ksp-common/testing/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib.jvm'\n}\n\ndependencies {\n    api libs.assertk\n    api libs.kotlin.compile.testing.core\n}\n"
  },
  {
    "path": "ksp-common/testing/src/main/kotlin/software/amazon/app/platform/ksp/CommonSourceCode.kt",
    "content": "package software.amazon.app.platform.ksp\n\nval Class<*>.inner: Class<*>\n  get() = classes.single { it.simpleName == \"Inner\" }\n"
  },
  {
    "path": "ksp-common/testing/src/main/kotlin/software/amazon/app/platform/ksp/Util.kt",
    "content": "@file:JvmName(\"UtilUnitTest\")\n@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.ksp\n\nimport assertk.Assert\nimport assertk.assertions.contains\nimport assertk.assertions.doesNotContain\nimport assertk.assertions.isEqualTo\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode\nimport java.lang.reflect.AnnotatedElement\nimport kotlin.reflect.KClass\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\n\nfun Assert<AnnotatedElement>.isAnnotatedWith(annotation: KClass<*>) {\n  transform { element -> element.annotations.map { it.annotationClass } }.contains(annotation)\n}\n\nfun Assert<ExitCode>.isOk() {\n  isEqualTo(ExitCode.OK)\n}\n\nfun Assert<ExitCode>.isError() {\n  transform { element ->\n      when (element) {\n        ExitCode.OK -> element\n        ExitCode.INTERNAL_ERROR,\n        ExitCode.COMPILATION_ERROR,\n        ExitCode.SCRIPT_EXECUTION_ERROR -> ExitCode.COMPILATION_ERROR\n      }\n    }\n    .isEqualTo(ExitCode.COMPILATION_ERROR)\n}\n\nfun Assert<AnnotatedElement>.isNotAnnotatedWith(annotation: KClass<*>) {\n  transform { element -> element.annotations.map { it.annotationClass } }.doesNotContain(annotation)\n}\n"
  },
  {
    "path": "metro/impl/api/android/impl.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph {\n\tpublic fun providePresenterCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope : software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun mirrorFunction (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph {\n\tpublic fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun provideAppScopeCoroutineScopeScoped (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope : software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph {\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun mirrorFunction (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n\tpublic final fun mirrorFunction (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph {\n\tpublic fun provideDefaultCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideIoCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideMainCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope : software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph {\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\n"
  },
  {
    "path": "metro/impl/api/desktop/impl.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph {\n\tpublic fun providePresenterCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope : software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$MetroContributionToAppScope;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun mirrorFunction (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph$ProvidePresenterCoroutineScopeMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun providePresenterCoroutineScope (Lsoftware/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph {\n\tpublic fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun provideAppScopeCoroutineScopeScoped (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope : software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph {\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$MetroContributionToAppScope;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun mirrorFunction (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppCoroutineScopeMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideAppCoroutineScope (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)Lkotlinx/coroutines/CoroutineScope;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n\tpublic final fun mirrorFunction (Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph$ProvideAppScopeCoroutineScopeScopedMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideAppScopeCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph;Lkotlinx/coroutines/CoroutineDispatcher;)Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph {\n\tpublic fun provideDefaultCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideIoCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic fun provideMainCoroutineDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope : software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph {\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope$DefaultImpls {\n\tpublic static fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic static fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$MetroContributionToAppScope;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideDefaultCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideDefaultCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideIoCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideIoCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lkotlinx/coroutines/CoroutineDispatcher;\n\tpublic final fun mirrorFunction ()Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph$ProvideMainCoroutineDispatcherMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideMainCoroutineDispatcher (Lsoftware/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph;)Lkotlinx/coroutines/CoroutineDispatcher;\n}\n\n"
  },
  {
    "path": "metro/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableMetro true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n}\n"
  },
  {
    "path": "metro/impl/src/commonMain/kotlin/software/amazon/app/platform/presenter/metro/PresenterCoroutineScopeGraph.kt",
    "content": "package software.amazon.app.platform.presenter.metro\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Provides\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.plus\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.app.platform.scope.coroutine.MainCoroutineDispatcher\n\n/** Provides the coroutine scope to run presenters. */\n@ContributesTo(AppScope::class)\npublic interface PresenterCoroutineScopeGraph {\n  /**\n   * Bind the app coroutine scope as default scope for presenters to allow them to run as long as\n   * the app is alive. The coroutine scope will use the main dispatcher by default, because\n   * presenters produce state for the UI and computing their models should have the highest\n   * priority.\n   */\n  @Provides\n  @PresenterCoroutineScope\n  public fun providePresenterCoroutineScope(\n    @ForScope(AppScope::class) scope: CoroutineScope,\n    @MainCoroutineDispatcher mainDispatcher: CoroutineDispatcher,\n  ): CoroutineScope = scope + mainDispatcher\n}\n"
  },
  {
    "path": "metro/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/metro/AppScopeCoroutineScopeGraph.kt",
    "content": "package software.amazon.app.platform.scope.coroutine.metro\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.SupervisorJob\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.IoCoroutineDispatcher\n\n/** Graph providing coroutine scopes in the App scope. */\n@ContributesTo(AppScope::class)\npublic interface AppScopeCoroutineScopeGraph {\n  /**\n   * Provides the [CoroutineScopeScoped] for the app scope. This is a single instance for the app\n   * scope.\n   */\n  @Provides\n  @SingleIn(AppScope::class)\n  @ForScope(AppScope::class)\n  public fun provideAppScopeCoroutineScopeScoped(\n    @IoCoroutineDispatcher dispatcher: CoroutineDispatcher\n  ): CoroutineScopeScoped {\n    return CoroutineScopeScoped(dispatcher + SupervisorJob() + CoroutineName(\"AppScope\"))\n  }\n\n  /**\n   * Provides the [CoroutineScope] for the app scope. A new child scope is created every time an\n   * instance is injected so that the parent cannot be canceled accidentally.\n   */\n  @Provides\n  @ForScope(AppScope::class)\n  public fun provideAppCoroutineScope(\n    @ForScope(AppScope::class) appScopeCoroutineScopeScoped: CoroutineScopeScoped\n  ): CoroutineScope {\n    return appScopeCoroutineScopeScoped.createChild()\n  }\n}\n"
  },
  {
    "path": "metro/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/metro/CoroutineDispatcherGraph.kt",
    "content": "package software.amazon.app.platform.scope.coroutine.metro\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.Provides\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport software.amazon.app.platform.scope.coroutine.DefaultCoroutineDispatcher\nimport software.amazon.app.platform.scope.coroutine.IoCoroutineDispatcher\nimport software.amazon.app.platform.scope.coroutine.MainCoroutineDispatcher\n\n/** Provides default dispatchers for coroutine scopes. */\n@ContributesTo(AppScope::class)\npublic interface CoroutineDispatcherGraph {\n  /** Provides the IO dispatcher in the dependency graph. */\n  @Provides\n  @IoCoroutineDispatcher\n  public fun provideIoCoroutineDispatcher(): CoroutineDispatcher = ioDispatcher\n\n  /** Provides the default dispatcher in the dependency graph. */\n  @Provides\n  @DefaultCoroutineDispatcher\n  public fun provideDefaultCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Default\n\n  /** Provides the main dispatcher in the dependency graph. */\n  @Provides\n  @MainCoroutineDispatcher\n  public fun provideMainCoroutineDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate\n}\n"
  },
  {
    "path": "metro/impl/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/metro/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine.metro\n\nimport kotlinx.coroutines.CoroutineDispatcher\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\ninternal expect val ioDispatcher: CoroutineDispatcher\n"
  },
  {
    "path": "metro/impl/src/noWasmJsMain/kotlin/software/amazon/app/platform/scope/coroutine/metro/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine.metro\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.IO\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\ninternal actual val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n"
  },
  {
    "path": "metro/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/scope/coroutine/metro/IoDispatcher.kt",
    "content": "package software.amazon.app.platform.scope.coroutine.metro\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\n\n/** Expect declaration for the IO dispatcher, because it doesn't exist for WASM. */\n// Fallback to the Default dispatcher.\ninternal actual val ioDispatcher: CoroutineDispatcher = Dispatchers.Default\n"
  },
  {
    "path": "metro/public/api/android/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/metro/ContributesScoped : java/lang/annotation/Annotation {\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/renderer/metro/RendererKey : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/renderer/metro/RobotKey : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()Ljava/lang/Class;\n}\n\npublic final class software/amazon/app/platform/scope/di/metro/MetroServiceKt {\n\tpublic static final field METRO_DEPENDENCY_GRAPH_KEY Ljava/lang/String;\n\tpublic static final fun addMetroDependencyGraph (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n}\n\n"
  },
  {
    "path": "metro/public/api/desktop/public.api",
    "content": "public abstract interface annotation class software/amazon/app/platform/inject/metro/ContributesScoped : java/lang/annotation/Annotation {\n\tpublic abstract fun scope ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/renderer/metro/RendererKey : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()Ljava/lang/Class;\n}\n\npublic abstract interface annotation class software/amazon/app/platform/renderer/metro/RobotKey : java/lang/annotation/Annotation {\n\tpublic abstract fun value ()Ljava/lang/Class;\n}\n\npublic final class software/amazon/app/platform/scope/di/metro/MetroServiceKt {\n\tpublic static final field METRO_DEPENDENCY_GRAPH_KEY Ljava/lang/String;\n\tpublic static final fun addMetroDependencyGraph (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Object;)V\n}\n\n"
  },
  {
    "path": "metro/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonMainApi project(':scope:public')\n    commonMainImplementation libs.metro.runtime\n\n    commonTestImplementation project(':internal:testing')\n}\n"
  },
  {
    "path": "metro/public/src/commonMain/kotlin/software/amazon/app/platform/inject/metro/ContributesScoped.kt",
    "content": "package software.amazon.app.platform.inject.metro\n\nimport kotlin.annotation.AnnotationTarget.CLASS\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.scope.Scoped\n\n/**\n * Used to contribute a class implementing the [Scoped] interface to the given [scope], e.g.\n *\n * ```\n * @Inject\n * @SingleIn(AppScope::class)\n * @ContributesScoped(AppScope::class)\n * class MyClass(..) : SuperType, Scoped\n * ```\n *\n * This annotation is a shortcut for using `@ContributesBinding` and `@ContributesIntoSet`, but with\n * a qualifier for the multibinding alone. This can in Metro only be expressed with a contributed\n * graph:\n * ```\n * @Inject\n * @SingleIn(AppScope::class)\n * class MyClass(..) : SuperType, Scoped\n *\n * @ContributesTo(AppScope::class)\n * interface MyClassGraph {\n *   @Binds val MyClass.bindSuperType: SuperType\n *\n *   @Binds @IntoSet @ForScope(AppScope::class) val MyClass.bindScoped: Scoped\n * }\n * ```\n *\n * Note that this annotation is only applicable for Metro and not kotlin-inject, because for\n * kotlin-inject we provide a custom code generator out of the box when using `@ContributesBinding`\n * that can handle the [Scoped] multibinding interface.\n */\n@Target(CLASS)\npublic annotation class ContributesScoped(\n  /** The scope in which to include this contributed binding. */\n  val scope: KClass<*>\n)\n"
  },
  {
    "path": "metro/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/metro/RendererKey.kt",
    "content": "package software.amazon.app.platform.renderer.metro\n\nimport dev.zacsweers.metro.MapKey\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * DO NOT USE DIRECTLY.\n *\n * This is a multibindings key used in Metro for identifying renderers by their model type. This key\n * is used by our custom code generator for `@ContributesRenderer`. [value] refers to the concrete\n * [BaseModel] handled by the renderer.\n */\n@MapKey public annotation class RendererKey(val value: KClass<out BaseModel>)\n"
  },
  {
    "path": "metro/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/metro/RobotKey.kt",
    "content": "package software.amazon.app.platform.renderer.metro\n\nimport dev.zacsweers.metro.MapKey\nimport kotlin.reflect.KClass\n\n/**\n * DO NOT USE DIRECTLY.\n *\n * This is a multibindings key used in Metro for identifying robots by their type. This key is used\n * by our custom code generator for `@ContributesRobot`. [value] refers to the concrete `Robot`\n * type.\n */\n@MapKey public annotation class RobotKey(val value: KClass<*>)\n"
  },
  {
    "path": "metro/public/src/commonMain/kotlin/software/amazon/app/platform/scope/di/metro/MetroService.kt",
    "content": "package software.amazon.app.platform.scope.di.metro\n\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.parents\n\n@PublishedApi internal const val METRO_DEPENDENCY_GRAPH_KEY: String = \"metroDependencyGraph\"\n\n/**\n * Provides the Metro dependency graph that has been added to this [Scope]. A common pattern is to\n * use this function to look up graph interfaces in static contexts like test methods, static\n * functions or where constructor injection cannot be used, e.g.\n *\n * ```\n * interface HudGraph {\n *     val hudManager: HudManager\n * }\n *\n * rootScope.metroDependencyGraph<HudGraph>().hudManager\n * ```\n *\n * The given graph type [T] of the DI graph can be provided by this scope or a parent scope.\n */\npublic inline fun <reified T : Any> Scope.metroDependencyGraph(): T {\n  parents(includeSelf = true)\n    .firstNotNullOfOrNull { scope -> scope.getService<Any>(METRO_DEPENDENCY_GRAPH_KEY) as? T }\n    ?.let {\n      return it\n    }\n\n  val diGraphs =\n    parents(includeSelf = true)\n      .map { it.getService<Any>(METRO_DEPENDENCY_GRAPH_KEY) }\n      .filterNotNull()\n      .map { it::class }\n\n  // The replace() will align inner class references across platforms. Native uses a '.',\n  // whereas the JVM platform use '$'.\n  throw NoSuchElementException(\n    \"Couldn't find dependency graph implementing ${T::class}. Inspected: \" +\n      \"[${diGraphs.joinToString { it.simpleName.toString() }}] (fully qualified \" +\n      \"names: [${diGraphs.joinToString { it.toString().replace('\\$', '.') }}])\"\n  )\n}\n\n/**\n * Adds the given [dependencyGraph] to this builder. The instance can be later retrieved with\n * [metroDependencyGraph].\n */\npublic fun Scope.Builder.addMetroDependencyGraph(dependencyGraph: Any) {\n  addService(METRO_DEPENDENCY_GRAPH_KEY, dependencyGraph)\n}\n"
  },
  {
    "path": "metro/public/src/commonTest/kotlin/software/amazon/app/platform/scope/di/metro/MetroServiceTest.kt",
    "content": "package software.amazon.app.platform.scope.di.metro\n\nimport assertk.assertThat\nimport assertk.assertions.hasMessage\nimport assertk.assertions.isSameInstanceAs\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.internal.Platform\nimport software.amazon.app.platform.internal.platform\nimport software.amazon.app.platform.scope.Scope\n\nclass MetroServiceTest {\n\n  @Test\n  fun `a metro graph can be registered in a scope`() {\n    val graph = ParentGraphImpl()\n\n    val scope = Scope.buildRootScope { addMetroDependencyGraph(graph) }\n\n    assertThat(scope.metroDependencyGraph<ParentGraph>()).isSameInstanceAs(graph)\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `if a metro graph cannot be found then an exception is thrown with a helpful error message`() {\n    val parentGraph = ParentGraphImpl()\n    val childGraph = ChildGraphImpl()\n\n    val parentScope = Scope.buildRootScope { addMetroDependencyGraph(parentGraph) }\n    val childScope = parentScope.buildChild(\"child\") { addMetroDependencyGraph(childGraph) }\n\n    val exception =\n      assertFailsWith<NoSuchElementException> { childScope.metroDependencyGraph<Unit>() }\n\n    val kotlinReflectWarning =\n      when (platform) {\n        Platform.JVM -> \" (Kotlin reflection is not available)\"\n        Platform.Native,\n        Platform.Web -> \"\"\n      }\n\n    assertThat(exception)\n      .hasMessage(\n        \"Couldn't find dependency graph implementing class kotlin.Unit$kotlinReflectWarning. \" +\n          \"Inspected: [ChildGraphImpl, ParentGraphImpl] (fully qualified names: \" +\n          \"[class software.amazon.app.platform.scope.di.metro.MetroServiceTest.\" +\n          \"ChildGraphImpl$kotlinReflectWarning, class software.amazon.app.\" +\n          \"platform.scope.di.metro.MetroServiceTest.ParentGraphImpl\" +\n          \"$kotlinReflectWarning])\"\n      )\n  }\n\n  @Test\n  fun `a DI graph can be retrieved from a scope`() {\n    val parentGraph = ParentGraphImpl()\n    val childGraph = ChildGraphImpl()\n\n    val parentScope = Scope.buildRootScope { addMetroDependencyGraph(parentGraph) }\n    val childScope = parentScope.buildChild(\"child\") { addMetroDependencyGraph(childGraph) }\n\n    assertThat(childScope.metroDependencyGraph<ChildGraph>()).isSameInstanceAs(childGraph)\n    assertThat(childScope.metroDependencyGraph<ParentGraph>()).isSameInstanceAs(parentGraph)\n\n    assertThat(parentScope.metroDependencyGraph<ParentGraph>()).isSameInstanceAs(parentGraph)\n    assertFailsWith<NoSuchElementException> { parentScope.metroDependencyGraph<ChildGraph>() }\n  }\n\n  private interface ParentGraph\n\n  private class ParentGraphImpl : ParentGraph\n\n  private interface ChildGraph\n\n  private class ChildGraphImpl : ChildGraph\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/build.gradle",
    "content": "//file:noinspection UnnecessaryQualifiedReference\nplugins {\n    id 'software.amazon.app.platform.lib.jvm'\n    id 'com.google.devtools.ksp'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ntest {\n    useJUnitPlatform()\n\n    // Since Kotlin 2.0 we need more memory to run our tests.\n    maxHeapSize = \"2g\"\n}\n\ndependencies {\n    implementation libs.ksp.api\n\n    implementation libs.kotlin.poet\n    implementation libs.kotlin.poet.ksp\n\n    implementation libs.auto.service.annotations\n    ksp libs.auto.service.ksp\n\n    // Gives us access to annotations.\n    implementation project(':di-common:public')\n    implementation project(':ksp-common:public')\n    implementation project(':metro:public')\n    implementation project(':scope:public')\n    implementation libs.metro.runtime\n\n    testImplementation project(':ksp-common:testing')\n    testImplementation project(':metro:public')\n    testImplementation project(':presenter:public')\n    testImplementation project(':renderer:public')\n    testImplementation project(':robot:public')\n    testImplementation libs.kotlin.compile.testing.core\n    testImplementation libs.kotlin.compile.testing.ksp\n\n    // Added so that the compiler plugin is picked up in tests.\n    testImplementation libs.metro.compiler\n\n    // Bump transitive dependency.\n    testImplementation libs.kotlin.compiler.embeddable\n    testImplementation libs.ksp\n    testImplementation libs.ksp.embeddable\n}\n\n// We don't need the apiCheck in this module.\ntasks.named('apiCheck').configure {\n    it.enabled = false\n}\ntasks.named('apiDump').configure {\n    it.enabled = false\n}\n\n// Configure the JVM target only for tests. We need 21 for unit tests, because they import the Metro compiler,\n// which requires 21.\ndef jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21\n\nconfigurations.named('testCompileClasspath').configure {\n    attributes.attribute(\n            org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,\n            jvmTarget.target.toInteger(),\n    )\n}\n\nconfigurations.named('testRuntimeClasspath').configure {\n    attributes.attribute(\n            org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,\n            jvmTarget.target.toInteger(),\n    )\n}\n\ntasks.named('compileTestKotlin', org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile).configure {\n    compilerOptions {\n        it.jvmTarget.set(jvmTarget)\n    }\n}\n\ntasks.named('compileTestJava', JavaCompile).configure {\n    sourceCompatibility = jvmTarget.target\n    targetCompatibility = jvmTarget.target\n    javaCompiler = javaToolchains.compilerFor {\n        languageVersion = JavaLanguageVersion.of(jvmTarget.target)\n    }\n}\n\ntasks.named('test', Test).configure {\n    javaLauncher = javaToolchains.launcherFor {\n        languageVersion = JavaLanguageVersion.of(jvmTarget.target)\n    }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/MetroContextAware.kt",
    "content": "package software.amazon.app.platform.metro\n\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtools.ksp.symbol.KSType\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.Scope\nimport software.amazon.app.platform.ksp.ContextAware\n\ninternal interface MetroContextAware : ContextAware {\n  val injectFqName\n    get() = Inject::class.requireQualifiedName()\n\n  private val scopeFqName\n    get() = Scope::class.requireQualifiedName()\n\n  fun KSAnnotation.isMetroScopeAnnotation(): Boolean {\n    return annotationType.resolve().isMetroScopeAnnotation()\n  }\n\n  private fun KSType.isMetroScopeAnnotation(): Boolean {\n    return declaration.annotations.any {\n      // Don't use requireQualifiedName(), because @ContributingAnnotation might not be\n      // on the compile classpath.\n      it.annotationType.resolve().declaration.qualifiedName?.asString() == scopeFqName\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/MetroExtensionSymbolProcessorProvider.kt",
    "content": "package software.amazon.app.platform.metro\n\nimport com.google.auto.service.AutoService\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\nimport software.amazon.app.platform.ksp.CompositeSymbolProcessor\nimport software.amazon.app.platform.metro.processor.ContributesRendererProcessor\nimport software.amazon.app.platform.metro.processor.ContributesRobotProcessor\nimport software.amazon.app.platform.metro.processor.ContributesScopedProcessor\n\n/** Entry point for KSP to pick up our [SymbolProcessor]. */\n@AutoService(SymbolProcessorProvider::class)\n@Suppress(\"unused\")\npublic class MetroExtensionSymbolProcessorProvider : SymbolProcessorProvider {\n  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {\n    return CompositeSymbolProcessor(\n      ContributesRendererProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesRobotProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n      ContributesScopedProcessor(\n        codeGenerator = environment.codeGenerator,\n        logger = environment.logger,\n      ),\n    )\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/Util.kt",
    "content": "package software.amazon.app.platform.metro\n\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport dev.zacsweers.metro.Origin\n\n/** The package in which the App Platform extensions generate code. */\ninternal const val METRO_LOOKUP_PACKAGE = \"app.platform.inject.metro\"\n\ninternal fun TypeSpec.Builder.addMetroOriginAnnotation(\n  clazz: KSClassDeclaration\n): TypeSpec.Builder =\n  addAnnotation(\n    AnnotationSpec.builder(Origin::class).addMember(\"%T::class\", clazz.toClassName()).build()\n  )\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/processor/ContributesRendererProcessor.kt",
    "content": "package software.amazon.app.platform.metro.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAllSuperTypes\nimport com.google.devtools.ksp.getAnnotationsByType\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.google.devtools.ksp.symbol.KSType\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.STAR\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.WildcardTypeName\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.IntoMap\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.metro.MetroContextAware\nimport software.amazon.app.platform.metro.addMetroOriginAnnotation\nimport software.amazon.app.platform.renderer.metro.RendererKey\n\n/**\n * Generates the code for [ContributesRenderer].\n *\n * In the lookup package [METRO_LOOKUP_PACKAGE] a new interface is generated with a provider method\n * for the renderer, e.g.\n *\n * ```\n * package software.amazon.test\n *\n * @ContributesRenderer\n * class TestRenderer : Renderer<Model>\n * ```\n *\n * Will generate:\n * ```\n * package $METRO_LOOKUP_PACKAGE.software.amazon.test\n *\n * @ContributesTo(RendererScope::class)\n * interface TestRendererGraph {\n *     @Provides\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     fun provideTestRendererIntoMap(\n *         renderer: Provider<TestRenderer>,\n *     ): Renderer<*> = renderer()\n *\n *     @Provides\n *     fun provideTestRenderer(): TestRenderer = TestRenderer()\n *\n *     @Provides\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     @ForScope(RendererScope::class)\n *     fun provideRendererModelKey(): KClass<out Renderer<*>> =\n *         TestRenderer::class\n * }\n * ```\n */\ninternal class ContributesRendererProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, MetroContextAware {\n\n  private val baseModel = ClassName(\"software.amazon.app.platform.presenter\", \"BaseModel\")\n  private val baseModelFqName = baseModel.canonicalName\n\n  private val rendererWildcard =\n    ClassName(\"software.amazon.app.platform.renderer\", \"Renderer\").parameterizedBy(STAR)\n\n  private val rendererScope = ClassName(\"software.amazon.app.platform.renderer\", \"RendererScope\")\n  private val rendererKey = RendererKey::class.asClassName()\n\n  private val singleIn = SingleIn::class.asClassName()\n\n  private val unitFqName = Unit::class.requireQualifiedName()\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesRenderer::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkNoSingleton(it)\n      }\n      .forEach { generateGraphInterface(it) }\n\n    return emptyList()\n  }\n\n  @OptIn(KspExperimental::class)\n  private fun generateGraphInterface(clazz: KSClassDeclaration) {\n    val packageName = \"${METRO_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val graphClassName = ClassName(packageName, \"${clazz.innerClassNames()}Graph\")\n    val hasInjectAnnotation = clazz.isAnnotationPresent(Inject::class)\n\n    if (hasInjectAnnotation) {\n      checkNoZeroArgConstructor(clazz)\n    } else {\n      checkZeroArgConstructor(clazz)\n    }\n\n    val includeSealedSubtypes =\n      try {\n        clazz.getAnnotationsByType(ContributesRenderer::class).single().includeSealedSubtypes\n      } catch (_: NoSuchElementException) {\n        /*\n        Caused by: java.util.NoSuchElementException: Collection contains no element matching the predicate.\n          at com.google.devtools.ksp.UtilsKt.createInvocationHandler$lambda$8(utils.kt:591)\n          at jdk.proxy105/jdk.proxy105.$Proxy1029.includeSealedSubtypes(Unknown Source)\n          at software.amazon.app.platform.inject.processor.ContributesRendererProcessor.generateComponentInterface(ContributesRendererProcessor.kt:120)\n\n        We're seeing this exception when trying to read 'includeSealedSubtypes' for an annotation\n        where the value is not declared, e.g. '@ContributesRenderer' (without any arguments).\n        This happens only on iOS for some reason. Fallback to the default value 'true'.\n         */\n        true\n      }\n\n    val allModels =\n      if (includeSealedSubtypes) {\n        generateSequence(listOf(modelType(clazz))) { classes ->\n            classes.flatMap { it.getSealedSubclasses() }.takeIf { it.isNotEmpty() }\n          }\n          .flatten()\n      } else {\n        sequenceOf(modelType(clazz))\n      }\n\n    val fileSpec =\n      FileSpec.builder(graphClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(graphClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addMetroOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", rendererScope)\n                .build()\n            )\n            .apply {\n              if (!hasInjectAnnotation) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.safeClassName}\")\n                    .addAnnotation(Provides::class)\n                    .returns(clazz.toClassName())\n                    .addStatement(\"return %T()\", clazz.toClassName())\n                    .build()\n                )\n              }\n            }\n            .addFunctions(allModels.map { createModelBindingFunction(clazz, it) }.toList())\n            .addFunctions(allModels.map { createModelKeyFunction(clazz, it) }.toList())\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private fun modelType(clazz: KSClassDeclaration): KSClassDeclaration {\n    val annotation = clazz.findAnnotation(ContributesRenderer::class)\n    val explicitModelType =\n      (annotation.arguments.firstOrNull { it.name?.asString() == \"modelType\" }\n          ?: annotation.arguments.firstOrNull())\n        ?.let { (it.value as? KSType)?.declaration as? KSClassDeclaration }\n        ?.takeIf { it.requireQualifiedName() != unitFqName }\n\n    if (explicitModelType != null) {\n      return explicitModelType\n    }\n\n    val implicitModelTypes =\n      clazz\n        .getAllSuperTypes()\n        .flatMap { superType ->\n          superType.arguments.filter { it.type?.resolve()?.extendsBaseModel() ?: false }\n        }\n        .mapNotNull { it.type?.resolve()?.declaration as? KSClassDeclaration }\n        .distinctBy { it.requireQualifiedName() }\n        .toList()\n\n    check(implicitModelTypes.size == 1, clazz) {\n      buildString {\n        append(\n          \"Couldn't find BaseModel type for ${clazz.simpleName.asString()}. \" +\n            \"Consider adding an explicit parameter.\"\n        )\n        if (implicitModelTypes.size > 1) {\n          append(\"Found: \")\n          append(implicitModelTypes.joinToString { it.requireQualifiedName() })\n        }\n      }\n    }\n\n    return implicitModelTypes[0]\n  }\n\n  private fun createModelBindingFunction(\n    clazz: KSClassDeclaration,\n    modelType: KSClassDeclaration,\n  ): FunSpec {\n    return FunSpec.builder(\"provide${clazz.safeClassName}\" + modelType.innerClassNames())\n      .addAnnotation(Provides::class)\n      .addAnnotation(IntoMap::class)\n      .addAnnotation(\n        AnnotationSpec.builder(rendererKey).addMember(\"%T::class\", modelType.toClassName()).build()\n      )\n      .addParameter(\n        name = \"renderer\",\n        type = Provider::class.asClassName().parameterizedBy(clazz.toClassName()),\n      )\n      .returns(rendererWildcard)\n      .addStatement(\"return renderer()\")\n      .build()\n  }\n\n  private fun createModelKeyFunction(\n    clazz: KSClassDeclaration,\n    modelType: KSClassDeclaration,\n  ): FunSpec {\n    return FunSpec.builder(\"provide${clazz.safeClassName}\" + modelType.innerClassNames() + \"Key\")\n      .addAnnotation(Provides::class)\n      .addAnnotation(IntoMap::class)\n      .addAnnotation(\n        AnnotationSpec.builder(rendererKey).addMember(\"%T::class\", modelType.toClassName()).build()\n      )\n      .addAnnotation(\n        AnnotationSpec.builder(ForScope::class)\n          .addMember(\"scope = %T::class\", rendererScope)\n          .build()\n      )\n      .returns(\n        KClass::class.asClassName().parameterizedBy(WildcardTypeName.producerOf(rendererWildcard))\n      )\n      .addStatement(\"return %T::class\", clazz.toClassName())\n      .build()\n  }\n\n  private fun checkNoSingleton(clazz: KSClassDeclaration) {\n    val hasSingleInAnnotation =\n      clazz.annotations.any { annotation ->\n        annotation.isAnnotation(singleIn.canonicalName) &&\n          clazz.scope().type.declaration.requireQualifiedName() == rendererScope.canonicalName\n      }\n\n    if (hasSingleInAnnotation) {\n      logger.error(\n        \"Renderers should not be singletons in the RendererScope. The \" +\n          \"RendererFactory will cache the Renderer when necessary. Remove the \" +\n          \"@SingleIn(RendererScope::class) annotation.\",\n        clazz,\n      )\n    }\n  }\n\n  private fun checkNoZeroArgConstructor(clazz: KSClassDeclaration) {\n    val parameterCount = clazz.primaryConstructor?.parameters?.size ?: 0\n    check(parameterCount > 0, clazz) {\n      \"It's redundant to use @Inject when using \" +\n        \"@ContributesRenderer for a Renderer with a zero-arg constructor.\"\n    }\n  }\n\n  private fun checkZeroArgConstructor(clazz: KSClassDeclaration) {\n    val parameterCount = clazz.primaryConstructor?.parameters?.size ?: 0\n    check(parameterCount == 0, clazz) {\n      \"When using @ContributesRenderer and you need to inject types in the constructor, \" +\n        \"then it's necessary to add the @Inject annotation.\"\n    }\n  }\n\n  private fun KSType.extendsBaseModel(): Boolean {\n    val superTypes =\n      (this.declaration as? KSClassDeclaration)?.getAllSuperTypes() ?: emptySequence()\n\n    return superTypes.any { it.declaration.qualifiedName?.asString() == baseModelFqName }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/processor/ContributesRobotProcessor.kt",
    "content": "package software.amazon.app.platform.metro.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAllSuperTypes\nimport com.google.devtools.ksp.isAnnotationPresent\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.FunSpec\nimport com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.asClassName\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.IntoMap\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.ksp.decapitalize\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.metro.MetroContextAware\nimport software.amazon.app.platform.metro.addMetroOriginAnnotation\nimport software.amazon.app.platform.renderer.metro.RobotKey\n\n/**\n * Generates the necessary code in order to support [ContributesRobot].\n *\n * If you use `@ContributesRobot(AbcScope::class)`, then this processor will generate a graph\n * interface, which gets contributed to this scope.\n *\n * ```\n * package app.platform.inject.metro.software.amazon.test\n *\n * @ContributesTo(scope = AbcScope::class)\n * public interface AbcRobotGraph {\n *     @Provide\n *     fun provideAbcRobot(): AbcRobot = AbcRobot()\n *\n *     @Provides\n *     @IntoMap\n *     @RobotKey(AbcRobot::class)\n *     fun provideAbcRobotIntoMap(\n *         robot: Provider<AbcRobot>,\n *     ): Robot = robot()\n * }\n * ```\n */\n@OptIn(KspExperimental::class)\ninternal class ContributesRobotProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, MetroContextAware {\n\n  private val robotClassName = ClassName(\"software.amazon.app.platform.robot\", \"Robot\")\n  private val robotFqName = robotClassName.canonicalName\n\n  private val robotKey = RobotKey::class.asClassName()\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesRobot::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkHasInjectAnnotation(it)\n        checkNotSingleton(it)\n        checkSuperType(it)\n        checkAppScope(it)\n      }\n      .forEach { generateGraph(it) }\n\n    return emptyList()\n  }\n\n  private fun generateGraph(clazz: KSClassDeclaration) {\n    val packageName = \"${METRO_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val graphClassName = ClassName(packageName, \"${clazz.innerClassNames()}Graph\")\n\n    val fileSpec =\n      FileSpec.builder(graphClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(graphClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addMetroOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", clazz.scope().type.toClassName())\n                .build()\n            )\n            .apply {\n              if (!clazz.isAnnotationPresent(Inject::class)) {\n                addFunction(\n                  FunSpec.builder(\"provide${clazz.innerClassNames()}\")\n                    .addAnnotation(Provides::class)\n                    .returns(clazz.toClassName())\n                    .addStatement(\"return %T()\", clazz.toClassName())\n                    .build()\n                )\n              }\n            }\n            .addFunction(\n              FunSpec.builder(\"provide${clazz.innerClassNames()}IntoMap\")\n                .addAnnotation(Provides::class)\n                .addAnnotation(IntoMap::class)\n                .addAnnotation(\n                  AnnotationSpec.builder(robotKey)\n                    .addMember(\"%T::class\", clazz.toClassName())\n                    .build()\n                )\n                .addParameter(\n                  name = \"robot\",\n                  type = Provider::class.asClassName().parameterizedBy(clazz.toClassName()),\n                )\n                .returns(robotClassName)\n                .addStatement(\"return robot()\")\n                .build()\n            )\n            .addProperty(name = clazz.innerClassNames().decapitalize(), type = clazz.toClassName())\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private fun checkHasInjectAnnotation(clazz: KSClassDeclaration) {\n    if (clazz.primaryConstructor?.parameters?.isNotEmpty() == true) {\n      check(clazz.annotations.any { it.isAnnotation(injectFqName) }, clazz) {\n        \"${clazz.simpleName.asString()} must be annotated with @Inject when \" +\n          \"injecting arguments into a robot.\"\n      }\n    }\n  }\n\n  private fun checkNotSingleton(clazz: KSClassDeclaration) {\n    check(clazz.annotations.none { it.isMetroScopeAnnotation() }, clazz) {\n      \"It's not allowed allowed for a robot to be a singleton, because the lifetime \" +\n        \"of the robot is scoped to the robot() factory function. Remove the @\" +\n        clazz.annotations.first { it.isMetroScopeAnnotation() }.shortName.asString() +\n        \" annotation.\"\n    }\n  }\n\n  private fun checkSuperType(clazz: KSClassDeclaration) {\n    val extendsRobot =\n      clazz.getAllSuperTypes().any { it.declaration.requireQualifiedName() == robotFqName }\n\n    check(extendsRobot, clazz) {\n      \"In order to use @ContributesRobot, ${clazz.simpleName.asString()} must \" +\n        \"implement $robotFqName.\"\n    }\n  }\n\n  private fun checkAppScope(clazz: KSClassDeclaration) {\n    val scope = clazz.scope().type.declaration.requireQualifiedName()\n    check(scope == AppScope::class.requireQualifiedName(), clazz) {\n      \"Robots can only be contributed to the AppScope for now. Scope $scope is unsupported.\"\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/main/kotlin/software/amazon/app/platform/metro/processor/ContributesScopedProcessor.kt",
    "content": "package software.amazon.app.platform.metro.processor\n\nimport com.google.devtools.ksp.KspExperimental\nimport com.google.devtools.ksp.getAllSuperTypes\nimport com.google.devtools.ksp.processing.CodeGenerator\nimport com.google.devtools.ksp.processing.KSPLogger\nimport com.google.devtools.ksp.processing.Resolver\nimport com.google.devtools.ksp.processing.SymbolProcessor\nimport com.google.devtools.ksp.symbol.KSAnnotated\nimport com.google.devtools.ksp.symbol.KSClassDeclaration\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotlinpoet.ClassName\nimport com.squareup.kotlinpoet.FileSpec\nimport com.squareup.kotlinpoet.PropertySpec\nimport com.squareup.kotlinpoet.TypeSpec\nimport com.squareup.kotlinpoet.ksp.addOriginatingKSFile\nimport com.squareup.kotlinpoet.ksp.toClassName\nimport com.squareup.kotlinpoet.ksp.writeTo\nimport dev.zacsweers.metro.Binds\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.IntoSet\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.metro.MetroContextAware\nimport software.amazon.app.platform.metro.addMetroOriginAnnotation\n\n/**\n * Generates the necessary code in order to support [ContributesScoped].\n *\n * ```\n * package app.platform.inject.metro.software.amazon.test\n *\n * @ContributesTo(scope = AbcScope::class)\n * public interface TestClassGraph {\n *\n *   @Binds\n *   val TestClass.bindSuperType: SuperType\n *\n *   @Binds @IntoSet @ForScope(UserScope::class)\n *   val TestClass.bindScoped: Scoped\n * }\n * ```\n */\n@OptIn(KspExperimental::class)\ninternal class ContributesScopedProcessor(\n  private val codeGenerator: CodeGenerator,\n  override val logger: KSPLogger,\n) : SymbolProcessor, MetroContextAware {\n\n  override fun process(resolver: Resolver): List<KSAnnotated> {\n    resolver\n      .getSymbolsWithAnnotation(ContributesScoped::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .onEach {\n        checkIsPublic(it)\n        checkHasInjectAnnotation(it)\n        checkImplementsScoped(it)\n        checkSuperType(it)\n      }\n      .forEach { generateGraph(it) }\n\n    resolver\n      .getSymbolsWithAnnotation(ContributesBinding::class)\n      .filterIsInstance<KSClassDeclaration>()\n      .forEach { checkDoesNotImplementScoped(it) }\n\n    return emptyList()\n  }\n\n  private fun generateGraph(clazz: KSClassDeclaration) {\n    val packageName = \"${METRO_LOOKUP_PACKAGE}.${clazz.packageName.asString()}\"\n    val graphClassName = ClassName(packageName, \"${clazz.innerClassNames()}Graph\")\n    val scopeClassName = clazz.scope().type.toClassName()\n\n    val fileSpec =\n      FileSpec.builder(graphClassName)\n        .addType(\n          TypeSpec.interfaceBuilder(graphClassName)\n            .addOriginatingKSFile(clazz.requireContainingFile())\n            .addMetroOriginAnnotation(clazz)\n            .addAnnotation(\n              AnnotationSpec.builder(ContributesTo::class)\n                .addMember(\"%T::class\", scopeClassName)\n                .build()\n            )\n            .addProperties(\n              clazz.superTypes\n                .filter { it.resolve().declaration.requireQualifiedName() != scopedFqName }\n                .map {\n                  val type = it.resolve()\n                  PropertySpec.builder(\n                      \"bind${type.declaration.innerClassNames()}\",\n                      type.toClassName(),\n                    )\n                    .addAnnotation(Binds::class)\n                    .receiver(clazz.toClassName())\n                    .build()\n                }\n                .toList()\n            )\n            .addProperty(\n              PropertySpec.builder(\"bind${clazz.innerClassNames()}Scoped\", scopedClassName)\n                .addAnnotation(Binds::class)\n                .addAnnotation(IntoSet::class)\n                .addAnnotation(\n                  AnnotationSpec.builder(ForScope::class)\n                    .addMember(\"%T::class\", scopeClassName)\n                    .build()\n                )\n                .receiver(clazz.toClassName())\n                .build()\n            )\n            .build()\n        )\n        .build()\n\n    fileSpec.writeTo(codeGenerator, aggregating = false)\n  }\n\n  private fun checkHasInjectAnnotation(clazz: KSClassDeclaration) {\n    check(clazz.annotations.any { it.isAnnotation(injectFqName) }, clazz) {\n      \"${clazz.simpleName.asString()} must be annotated with @Inject when \" +\n        \"using @ContributesScoped.\"\n    }\n  }\n\n  private fun checkImplementsScoped(clazz: KSClassDeclaration) {\n    val extendsScoped =\n      clazz.getAllSuperTypes().any { it.declaration.qualifiedName?.asString() == scopedFqName }\n\n    check(extendsScoped, clazz) {\n      \"In order to use @ContributesScoped, ${clazz.simpleName.asString()} must \" +\n        \"implement $scopedFqName.\"\n    }\n  }\n\n  private fun checkSuperType(clazz: KSClassDeclaration) {\n    val superTypeCount =\n      clazz.superTypes\n        .filter { it.resolve().declaration.requireQualifiedName() != scopedFqName }\n        .count()\n\n    check(superTypeCount < 2, clazz) {\n      \"In order to use @ContributesScoped, ${clazz.simpleName.asString()} is allowed to have only one \" +\n        \"other super type besides Scoped.\"\n    }\n  }\n\n  private fun checkDoesNotImplementScoped(clazz: KSClassDeclaration) {\n    check(\n      clazz.superTypes.none { it.resolve().declaration.requireQualifiedName() == scopedFqName },\n      clazz,\n    ) {\n      \"${clazz.simpleName.asString()} implements Scoped, but uses @ContributesBinding instead \" +\n        \"of @ContributesScoped. When implementing Scoped the annotation @ContributesScoped \" +\n        \"must be used instead of @ContributesBinding to bind both super types correctly. It's \" +\n        \"not necessary to use @ContributesBinding.\"\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/CommonSourceCode.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.metro\n\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\n\ninternal val JvmCompilationResult.graphInterface: Class<*>\n  get() = classLoader.loadClass(\"software.amazon.test.GraphInterface\")\n\ninternal fun <T : Any> Class<*>.newMetroGraph(): T {\n  val companionObject = fields.single().get(null)\n  @Suppress(\"UNCHECKED_CAST\")\n  return classes\n    .single { it.simpleName == \"Companion\" }\n    .declaredMethods\n    .single { it.name == \"create\" }\n    .invoke(companionObject) as T\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/Compilation.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.metro\n\nimport assertk.assertThat\nimport com.google.devtools.ksp.processing.SymbolProcessorProvider\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation\nimport com.tschuchort.compiletesting.PluginOption\nimport com.tschuchort.compiletesting.SourceFile\nimport com.tschuchort.compiletesting.addPreviousResultToClasspath\nimport com.tschuchort.compiletesting.configureKsp\nimport dev.zacsweers.metro.compiler.MetroCommandLineProcessor\nimport dev.zacsweers.metro.compiler.MetroCompilerPluginRegistrar\nimport java.io.File\nimport java.io.OutputStream\nimport java.nio.file.Files\nimport java.util.ServiceLoader\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.jetbrains.kotlin.config.JvmTarget\nimport software.amazon.app.platform.ksp.isError\nimport software.amazon.app.platform.ksp.isOk\n\n/** A simple API over a [KotlinCompilation] with extra configuration support for KSP processors. */\n// Inspired by Anvil:\n// https://github.com/square/anvil/blob/97e2cc0430311c6b0ed5341da95bb243b582fab8/compiler-utils/src/testFixtures/java/com/squareup/anvil/compiler/internal/testing/AnvilCompilation.kt\nclass Compilation internal constructor(val kotlinCompilation: KotlinCompilation) {\n\n  private var isCompiled = false\n  private var processorsConfigured = false\n\n  /** Configures the behavior of this compilation. */\n  fun configureAppPlatformProcessor(): Compilation = apply {\n    checkNotCompiled()\n    check(!processorsConfigured) { \"Processor should not be configured twice.\" }\n\n    processorsConfigured = true\n\n    val metroCommandLineProcessor = MetroCommandLineProcessor()\n    kotlinCompilation.compilerPluginRegistrars = listOf(MetroCompilerPluginRegistrar())\n    kotlinCompilation.commandLineProcessors = listOf(metroCommandLineProcessor)\n    kotlinCompilation.pluginOptions +=\n      PluginOption(\n        pluginId = metroCommandLineProcessor.pluginId,\n        optionName = \"unused-graph-inputs-severity\",\n        optionValue = \"NONE\",\n      )\n\n    // KSP1 isn't supported with Metro, likely because we run KSP within the kotlinc. That's fine,\n    // we shouldn't bother about KSP1 anymore.\n    kotlinCompilation.configureKsp {\n      symbolProcessorProviders +=\n        ServiceLoader.load(\n          SymbolProcessorProvider::class.java,\n          SymbolProcessorProvider::class.java.classLoader,\n        )\n\n      // Run KSP embedded directly within this kotlinc invocation\n      withCompilation = true\n      incremental = true\n    }\n  }\n\n  /** Adds the given sources to this compilation with their packages and names inferred. */\n  fun addSources(@Language(\"kotlin\") vararg sources: String): Compilation = apply {\n    checkNotCompiled()\n    kotlinCompilation.sources += sources.mapIndexed { index, content ->\n      val packageDir =\n        content\n          .lines()\n          .firstOrNull { it.trim().startsWith(\"package \") }\n          ?.substringAfter(\"package \")\n          ?.replace('.', '/')\n          ?.let { \"$it/\" } ?: \"\"\n\n      val name =\n        \"${kotlinCompilation.workingDir.absolutePath}/sources/src/main/java/\" +\n          \"$packageDir/Source$index.kt\"\n\n      Files.createDirectories(File(name).parentFile.toPath())\n\n      SourceFile.kotlin(name, contents = content, trimIndent = true)\n    }\n  }\n\n  fun addPreviousCompilationResult(result: JvmCompilationResult): Compilation = apply {\n    checkNotCompiled()\n    kotlinCompilation.addPreviousResultToClasspath(result)\n  }\n\n  private fun checkNotCompiled() {\n    check(!isCompiled) {\n      \"Already compiled! Create a new compilation if you want to compile again.\"\n    }\n  }\n\n  /**\n   * Compiles the underlying [KotlinCompilation]. Note that if [configureAppPlatformProcessor] has\n   * not been called prior to this, it will be configured with default behavior.\n   */\n  fun compile(\n    @Language(\"kotlin\") vararg sources: String,\n    block: JvmCompilationResult.() -> Unit = {},\n  ): JvmCompilationResult {\n    checkNotCompiled()\n    if (!processorsConfigured) {\n      // Configure with default behaviors\n      configureAppPlatformProcessor()\n    }\n    addSources(*sources)\n    isCompiled = true\n\n    return kotlinCompilation.compile().apply(block)\n  }\n\n  companion object {\n    operator fun invoke(): Compilation {\n      return Compilation(\n        KotlinCompilation().apply {\n          // Sensible default behaviors\n          inheritClassPath = true\n          jvmTarget = JvmTarget.JVM_21.description\n          verbose = false\n        }\n      )\n    }\n  }\n}\n\n/**\n * Helpful for testing code generators in unit tests end to end.\n *\n * This covers common cases, but is built upon reusable logic in [Compilation] and\n * [Compilation.configureAppPlatformProcessor]. Consider using those APIs if more advanced\n * configuration is needed.\n */\nfun compile(\n  @Language(\"kotlin\") vararg sources: String,\n  allWarningsAsErrors: Boolean = true,\n  messageOutputStream: OutputStream = System.out,\n  workingDir: File? = null,\n  previousCompilationResult: JvmCompilationResult? = null,\n  moduleName: String? = null,\n  exitCode: KotlinCompilation.ExitCode = KotlinCompilation.ExitCode.OK,\n  block: JvmCompilationResult.() -> Unit = {},\n): JvmCompilationResult {\n  return Compilation()\n    .apply {\n      kotlinCompilation.apply {\n        this.allWarningsAsErrors = allWarningsAsErrors\n        this.messageOutputStream = messageOutputStream\n        if (workingDir != null) {\n          this.workingDir = workingDir\n        }\n        if (moduleName != null) {\n          this.moduleName = moduleName\n        }\n      }\n\n      if (previousCompilationResult != null) {\n        addPreviousCompilationResult(previousCompilationResult)\n      }\n    }\n    .configureAppPlatformProcessor()\n    .compile(*sources)\n    .also {\n      if (exitCode == KotlinCompilation.ExitCode.OK) {\n        assertThat(it.exitCode).isOk()\n      } else {\n        assertThat(it.exitCode).isError()\n      }\n    }\n    .also(block)\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/CompilerTestUtil.kt",
    "content": "package software.amazon.app.platform.inject.metro\n\nimport java.lang.reflect.Method\n\n// Following changes to Kotlin starting in 2.2.0,\n// https://kotlinlang.org/docs/whatsnew22.html#changes-to-default-method-generation-for-interface-functions\n// default methods are generated where they previously weren't. For testing we only validate the non\n// synthetic methods.\ninternal val Class<*>.declaredNonSyntheticMethods: List<Method>\n  get() = declaredMethods.filterNot { it.isSynthetic }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/processor/ContributesRendererProcessorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.metro.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsExactlyInAnyOrder\nimport assertk.assertions.containsOnly\nimport assertk.assertions.isEmpty\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNull\nimport assertk.assertions.startsWith\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.IntoMap\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport kotlin.reflect.KClass\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.metro.compile\nimport software.amazon.app.platform.inject.metro.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.metro.graphInterface\nimport software.amazon.app.platform.inject.metro.newMetroGraph\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererScope\nimport software.amazon.app.platform.renderer.metro.RendererKey\nimport software.amazon.test.TestRendererGraph\n\nclass ContributesRendererProcessorTest {\n\n  @Test\n  fun `a graph interface is generated in the lookup package for a contributed renderer`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n    \n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.inject.ContributesRenderer\n\n      class Model : BaseModel\n\n      @ContributesRenderer\n      class TestRenderer : Renderer<Model> {\n          override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.rendererGraph\n\n      assertThat(generatedGraph.packageName).startsWith(METRO_LOOKUP_PACKAGE)\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererModel\"\n        }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(returnType).isEqualTo(Renderer::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(this.getAnnotation(RendererKey::class.java).value).isEqualTo(model)\n      }\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererModelKey\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(KClass::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(getAnnotation(ForScope::class.java).scope).isEqualTo(RendererScope::class)\n        assertThat(getAnnotation(RendererKey::class.java).value).isEqualTo(model)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(model)\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.keys)\n        .containsOnly(model)\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.values)\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated in the lookup package for a contributed renderer as inner class`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.inject.ContributesRenderer\n\n      class Model : BaseModel\n\n      class TestRenderer {\n          @ContributesRenderer\n          class Inner : Renderer<Model> {\n              override fun render(model: Model) = Unit\n          }\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.inner.rendererGraph\n\n      assertThat(generatedGraph.packageName).startsWith(METRO_LOOKUP_PACKAGE)\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererInner\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer.inner)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererInnerModel\"\n        }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(returnType).isEqualTo(Renderer::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(getAnnotation(RendererKey::class.java).value).isEqualTo(model)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated in the lookup package for a contributed renderer with a model as inner class`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.inject.ContributesRenderer\n\n      class Presenter {\n          class Model : BaseModel\n      }\n\n      @ContributesRenderer\n      class TestRenderer : Renderer<Presenter.Model> {\n          override fun render(model: Presenter.Model) = Unit\n      }\n            \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.rendererGraph\n\n      assertThat(generatedGraph.packageName).startsWith(METRO_LOOKUP_PACKAGE)\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRendererPresenterModel\"\n        }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(returnType).isEqualTo(Renderer::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(presenter.model.kotlin)\n    }\n  }\n\n  @Test\n  fun `the explicit model type has a higher priority`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model : BaseModel\n      class Model2 : BaseModel\n\n      @ContributesRenderer(Model::class)\n      class TestRenderer : Renderer<Model2> {\n        override fun render(model: Model2) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `the model type can be inferred from the class hierarchy`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model : BaseModel\n\n      interface OtherRenderer : Renderer<Model>\n\n      @ContributesRenderer\n      class TestRenderer : OtherRenderer {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\"\n    ) {\n      assertThat(testRenderer.modelType).isEqualTo(model)\n    }\n  }\n\n  @Test\n  fun `the model type can be inferred from the class hierarchy with multiple levels`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model : BaseModel\n\n      interface OtherRenderer<S : Any, T : BaseModel, U : CharSequence> : Renderer<T>\n      interface OtherRenderer2<S : BaseModel, T : Any> : OtherRenderer<T, S, String>\n      interface OtherRenderer3 : OtherRenderer2<Model, Any>\n      interface OtherRenderer4 : OtherRenderer3\n\n\n      @ContributesRenderer\n      class TestRenderer : OtherRenderer4 {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\"\n    ) {\n      assertThat(testRenderer.modelType).isEqualTo(model)\n    }\n  }\n\n  @Test\n  fun `the model type must be explicit when it cannot be inferred`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model1 : BaseModel\n      class Model2 : BaseModel\n\n      interface OtherRenderer<S : BaseModel, T : BaseModel> : Renderer<S>\n\n      @ContributesRenderer\n      class TestRenderer : OtherRenderer<Model1, Model2> {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Couldn't find BaseModel type for TestRenderer. Consider adding \" +\n            \"an explicit parameter.Found: software.amazon.test.Model1, software.amazon.test.Model2\"\n        )\n    }\n  }\n\n  @Test\n  fun `the graph interface contains multiple binding methods for model hierarchies`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.inject.ContributesRenderer\n\n      interface Presenter {\n        sealed interface Model : BaseModel {\n          sealed interface Inner : Model {\n            data object Model1 : Inner\n            data object Model2 : Inner\n          }\n          data object Model2 : Model\n\n          // Note that this class doesn't extend Model.\n          class OtherSubclass\n        }\n      }\n\n      @ContributesRenderer\n      class TestRenderer : Renderer<Presenter.Model> {\n        override fun render(model: Presenter.Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.rendererGraph\n\n      with(\n        generatedGraph.declaredNonSyntheticMethods.single {\n          it.name == \"provideSoftwareAmazonTestTestRenderer\"\n        }\n      ) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRenderer)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      val bindingMethods =\n        generatedGraph.declaredNonSyntheticMethods.filter {\n          it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n            !it.name.endsWith(\"Key\")\n        }\n      assertThat(bindingMethods.map { it.name })\n        .containsExactlyInAnyOrder(\n          \"provideSoftwareAmazonTestTestRendererPresenterModel\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInner\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel1\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel2\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelModel2\",\n        )\n\n      bindingMethods.forEach {\n        assertThat(it.parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(it.returnType).isEqualTo(Renderer::class.java)\n        assertThat(it).isAnnotatedWith(Provides::class)\n        assertThat(it).isAnnotatedWith(IntoMap::class)\n        assertThat(it).isAnnotatedWith(RendererKey::class)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsExactlyInAnyOrder(\n          presenter.model.kotlin,\n          presenter.model.inner.kotlin,\n          presenter.model.inner.model1.kotlin,\n          presenter.model.inner.model2.kotlin,\n          presenter.model.model2.kotlin,\n        )\n\n      val keyBindingMethods =\n        generatedGraph.declaredNonSyntheticMethods.filter {\n          it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n            it.name.endsWith(\"Key\")\n        }\n      assertThat(keyBindingMethods.map { it.name })\n        .containsExactlyInAnyOrder(\n          \"provideSoftwareAmazonTestTestRendererPresenterModelKey\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerKey\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel1Key\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelInnerModel2Key\",\n          \"provideSoftwareAmazonTestTestRendererPresenterModelModel2Key\",\n        )\n\n      keyBindingMethods.forEach {\n        assertThat(it.parameters).isEmpty()\n        assertThat(it.returnType).isEqualTo(KClass::class.java)\n        assertThat(it).isAnnotatedWith(Provides::class)\n        assertThat(it).isAnnotatedWith(IntoMap::class)\n        assertThat(it).isAnnotatedWith(ForScope::class)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.keys)\n        .containsExactlyInAnyOrder(\n          presenter.model.kotlin,\n          presenter.model.inner.kotlin,\n          presenter.model.inner.model1.kotlin,\n          presenter.model.inner.model2.kotlin,\n          presenter.model.model2.kotlin,\n        )\n\n      assertThat(\n          graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.values.distinct()\n        )\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `the binding methods for subtypes are not generated when disabled`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.inject.ContributesRenderer\n\n      interface Presenter {\n        sealed interface Model : BaseModel {\n          data object Model1 : Model\n          data object Model2 : Model\n        }\n      }\n\n      @ContributesRenderer(includeSealedSubtypes = false)\n      class TestRenderer : Renderer<Presenter.Model> {\n        override fun render(model: Presenter.Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.rendererGraph\n\n      assertThat(\n          generatedGraph.declaredNonSyntheticMethods\n            .filter {\n              it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModel\") &&\n                !it.name.endsWith(\"Key\")\n            }\n            .map { it.name }\n        )\n        .containsOnly(\"provideSoftwareAmazonTestTestRendererPresenterModel\")\n\n      assertThat(\n          generatedGraph.declaredNonSyntheticMethods\n            .filter { it.name.startsWith(\"provideSoftwareAmazonTestTestRendererPresenterModelKey\") }\n            .map { it.name }\n        )\n        .containsOnly(\"provideSoftwareAmazonTestTestRendererPresenterModelKey\")\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(presenter.model.kotlin)\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.keys)\n        .containsOnly(presenter.model.kotlin)\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().modelToRendererMapping.values)\n        .containsOnly(testRenderer.kotlin)\n    }\n  }\n\n  @Test\n  fun `the graph does not contain a binding for the renderer if it is annotated with @Inject`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import dev.zacsweers.metro.Inject\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model : BaseModel\n\n      @ContributesRenderer\n      @Inject\n      class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val generatedGraph = testRenderer.rendererGraph\n\n      assertThat(generatedGraph.packageName).startsWith(METRO_LOOKUP_PACKAGE)\n\n      assertThat(generatedGraph.declaredNonSyntheticMethods.map { it.name })\n        .containsOnly(\n          \"provideSoftwareAmazonTestTestRendererModel\",\n          \"provideSoftwareAmazonTestTestRendererModelKey\",\n        )\n\n      assertThat(graphInterface.newMetroGraph<TestRendererGraph>().renderers.keys)\n        .containsOnly(model)\n    }\n  }\n\n  @Test\n  fun `when using @SingleIn(RendererScope_class) then a warning is printed`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import software.amazon.app.platform.renderer.RendererScope\n\n      class Model : BaseModel\n\n      @Inject\n      @SingleIn(RendererScope::class)\n      @ContributesRenderer\n      class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Source0.kt:15: Renderers should not be singletons in the \" +\n            \"RendererScope. The RendererFactory will cache the Renderer when \" +\n            \"necessary. Remove the @SingleIn(RendererScope::class) annotation.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it is redundant to add @Inject for a zero arg constructor`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n      import dev.zacsweers.metro.Inject\n\n      class Model : BaseModel\n\n      @ContributesRenderer\n      @Inject\n      class TestRenderer : Renderer<Model> {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"It's redundant to use @Inject when using @ContributesRenderer \" +\n            \"for a Renderer with a zero-arg constructor.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it is required to use @Inject for a non-zero arg constructor`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.ContributesRenderer\n      import software.amazon.app.platform.presenter.BaseModel\n      import software.amazon.app.platform.renderer.Renderer\n\n      class Model : BaseModel\n\n      @ContributesRenderer\n      class TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n        override fun render(model: Model) = Unit\n      }\n      \"\"\",\n      graphInterfaceSource,\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"When using @ContributesRenderer and you need to inject types \" +\n            \"in the constructor, then it's necessary to add the @Inject annotation.\"\n        )\n    }\n  }\n\n  @Language(\"kotlin\")\n  private val graphInterfaceSource =\n    \"\"\"\n        package software.amazon.test\n\n        import software.amazon.app.platform.presenter.BaseModel\n        import software.amazon.app.platform.renderer.Renderer\n        import software.amazon.app.platform.renderer.RendererScope\n        import kotlin.reflect.KClass\n        import dev.zacsweers.metro.AppScope\n        import dev.zacsweers.metro.createGraph\n        import dev.zacsweers.metro.DependencyGraph\n        import dev.zacsweers.metro.ForScope\n        import dev.zacsweers.metro.Provider\n        import dev.zacsweers.metro.Provides\n        import dev.zacsweers.metro.SingleIn\n        import software.amazon.test.TestRendererGraph\n\n        @DependencyGraph(RendererScope::class)\n        @SingleIn(RendererScope::class)\n        interface GraphInterface : TestRendererGraph {\n            @Provides fun provideString(): String = \"abc\"\n\n            companion object {\n                fun create(): GraphInterface = createGraph<GraphInterface>()\n            }\n        }\n    \"\"\"\n\n  private val JvmCompilationResult.testRenderer: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.TestRenderer\")\n\n  private val JvmCompilationResult.model: KClass<out Any>\n    get() = classLoader.loadClass(\"software.amazon.test.Model\").kotlin\n\n  private val JvmCompilationResult.presenter: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.Presenter\")\n\n  private val Class<*>.model: Class<*>\n    get() = classes.single { it.simpleName == \"Model\" }\n\n  private val Class<*>.model1: Class<*>\n    get() = classes.single { it.simpleName == \"Model1\" }\n\n  private val Class<*>.model2: Class<*>\n    get() = classes.single { it.simpleName == \"Model2\" }\n\n  private val Class<*>.rendererGraph: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$METRO_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).replace(\".\", \"\") +\n          \"Graph\"\n      )\n\n  private val Class<*>.modelType: KClass<*>\n    get() =\n      rendererGraph.declaredNonSyntheticMethods\n        .single { it.name == \"provideSoftwareAmazonTestTestRendererModel\" }\n        .getAnnotation(RendererKey::class.java)\n        .value\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/processor/ContributesRobotGeneratorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.metro.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsOnly\nimport assertk.assertions.isEmpty\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.IntoMap\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport org.intellij.lang.annotations.Language\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.metro.compile\nimport software.amazon.app.platform.inject.metro.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.metro.graphInterface\nimport software.amazon.app.platform.inject.metro.newMetroGraph\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.isAnnotatedWith\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.renderer.metro.RobotKey\nimport software.amazon.app.platform.robot.Robot\nimport software.amazon.test.TestRobotGraph\n\nclass ContributesRobotGeneratorTest {\n\n  @Test\n  fun `a graph interface is generated without @Inject constructor`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import dev.zacsweers.metro.AppScope\n\n      @ContributesRobot(AppScope::class)\n      class TestRobot : Robot\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val robotGraph = testRobot.graph\n\n      assertThat(robotGraph.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      with(robotGraph.declaredNonSyntheticMethods.single { it.name == \"provideTestRobot\" }) {\n        assertThat(parameters).isEmpty()\n        assertThat(returnType).isEqualTo(testRobot)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(getAnnotation(SingleIn::class.java)).isNull()\n      }\n\n      with(robotGraph.declaredNonSyntheticMethods.single { it.name == \"provideTestRobotIntoMap\" }) {\n        assertThat(parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(returnType).isEqualTo(Robot::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(getAnnotation(RobotKey::class.java).value.java).isEqualTo(testRobot)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRobotGraph>().robots.keys)\n        .containsOnly(testRobot.kotlin)\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated with @Inject constructor`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n\n      @Inject\n      @ContributesRobot(AppScope::class)\n      class TestRobot : Robot\n      \"\"\",\n      graphInterfaceSource,\n    ) {\n      val robotGraph = testRobot.graph\n\n      assertThat(robotGraph.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      assertThat(\n          robotGraph.declaredNonSyntheticMethods.singleOrNull { it.name == \"provideTestRobot\" }\n        )\n        .isNull()\n\n      with(robotGraph.declaredNonSyntheticMethods.single { it.name == \"provideTestRobotIntoMap\" }) {\n        assertThat(parameters.single().type).isEqualTo(Provider::class.java)\n        assertThat(returnType).isEqualTo(Robot::class.java)\n        assertThat(this).isAnnotatedWith(Provides::class)\n        assertThat(this).isAnnotatedWith(IntoMap::class)\n        assertThat(getAnnotation(RobotKey::class.java).value.java).isEqualTo(testRobot)\n      }\n\n      assertThat(graphInterface.newMetroGraph<TestRobotGraph>().robots.keys)\n        .containsOnly(testRobot.kotlin)\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated without direct super type`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import dev.zacsweers.metro.AppScope\n\n      interface BaseRobot1 : Robot\n      abstract class BaseRobot2 : BaseRobot1\n\n      @ContributesRobot(AppScope::class)\n      class TestRobot : BaseRobot2()\n      \"\"\"\n    ) {\n      assertThat(testRobot.graph).isNotNull()\n    }\n  }\n\n  @Test\n  fun `the robot class must be a super type`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import dev.zacsweers.metro.AppScope\n\n      interface BaseRobot1\n      abstract class BaseRobot2 : BaseRobot1\n\n      @ContributesRobot(AppScope::class)\n      class TestRobot : BaseRobot2()\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"In order to use @ContributesRobot, TestRobot must implement \" +\n            \"software.amazon.app.platform.robot.Robot.\"\n        )\n    }\n  }\n\n  @Test\n  fun `a Robot must not be a singleton`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesRobot(AppScope::class)\n      class TestRobot : Robot\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"It's not allowed allowed for a robot to be a singleton, because \" +\n            \"the lifetime of the robot is scoped to the robot() factory function. \" +\n            \"Remove the @SingleIn annotation.\"\n        )\n    }\n  }\n\n  @Test\n  fun `only the app scope is supported for now`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.robot.ContributesRobot\n      import software.amazon.app.platform.robot.Robot\n      import software.amazon.lastmile.kotlin.inject.anvil.AppScope\n\n      @ContributesRobot(String::class)\n      class TestRobot : Robot\n              \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Robots can only be contributed to the AppScope for now. \" +\n            \"Scope kotlin.String is unsupported.\"\n        )\n    }\n  }\n\n  @Language(\"kotlin\")\n  private val graphInterfaceSource =\n    \"\"\"\n        package software.amazon.test\n\n        import dev.zacsweers.metro.AppScope\n        import dev.zacsweers.metro.createGraph\n        import dev.zacsweers.metro.DependencyGraph\n        import dev.zacsweers.metro.SingleIn\n\n        @DependencyGraph(AppScope::class)\n        @SingleIn(AppScope::class)\n        interface GraphInterface : TestRobotGraph {\n            companion object {\n                fun create(): GraphInterface = createGraph<GraphInterface>()\n            }\n        }\n    \"\"\"\n\n  private val JvmCompilationResult.testRobot: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.TestRobot\")\n\n  private val Class<*>.graph: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$METRO_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).split(\".\").joinToString(\n            separator = \"\"\n          ) {\n            it.capitalize()\n          } +\n          \"Graph\"\n      )\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/app/platform/inject/metro/processor/ContributesScopedProcessorTest.kt",
    "content": "@file:OptIn(ExperimentalCompilerApi::class)\n\npackage software.amazon.app.platform.inject.metro.processor\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.hasSize\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport com.tschuchort.compiletesting.JvmCompilationResult\nimport com.tschuchort.compiletesting.KotlinCompilation.ExitCode.COMPILATION_ERROR\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi\nimport org.junit.jupiter.api.Test\nimport software.amazon.app.platform.inject.metro.compile\nimport software.amazon.app.platform.inject.metro.declaredNonSyntheticMethods\nimport software.amazon.app.platform.inject.metro.graphInterface\nimport software.amazon.app.platform.inject.metro.newMetroGraph\nimport software.amazon.app.platform.ksp.capitalize\nimport software.amazon.app.platform.ksp.inner\nimport software.amazon.app.platform.metro.METRO_LOOKUP_PACKAGE\nimport software.amazon.app.platform.scope.Scoped\n\nclass ContributesScopedProcessorTest {\n\n  @Test\n  fun `a graph interface is generated`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.createGraph\n      import dev.zacsweers.metro.DependencyGraph\n      import dev.zacsweers.metro.ForScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : SuperType, Scoped\n\n      @DependencyGraph(AppScope::class)\n      @SingleIn(AppScope::class)\n      interface GraphInterface {\n        val superTypeInstance: SuperType\n        \n        @ForScope(AppScope::class)\n        val allScoped: Set<Scoped>\n      \n        companion object {\n          fun create(): GraphInterface = createGraph<GraphInterface>()\n        }\n      }\n      \"\"\"\n    ) {\n      val scopedGraph = testClass.graph\n\n      assertThat(scopedGraph.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      // The annotations for these functions are defined in other kotlinc generated classes.\n      // Instead of relying on reflection, we verify them by running the Metro compiler and\n      // instantiating the Metro graph below.\n      with(scopedGraph.declaredNonSyntheticMethods.single { it.name == \"getBindSuperType\" }) {\n        assertThat(parameters.single().type).isEqualTo(testClass)\n        assertThat(returnType).isEqualTo(superType)\n      }\n\n      with(scopedGraph.declaredNonSyntheticMethods.single { it.name == \"getBindTestClassScoped\" }) {\n        assertThat(parameters.single().type).isEqualTo(testClass)\n        assertThat(returnType).isEqualTo(Scoped::class.java)\n      }\n\n      val graph = graphInterface.newMetroGraph<Any>()\n\n      @Suppress(\"UNCHECKED_CAST\")\n      val scopedInstances =\n        graphInterface.declaredNonSyntheticMethods\n          .single { it.name == \"getAllScoped\" }\n          .invoke(graph) as Set<Scoped>\n      assertThat(scopedInstances.single()::class.java).isEqualTo(testClass)\n\n      @Suppress(\"UNCHECKED_CAST\")\n      assertThat(\n          graphInterface.declaredNonSyntheticMethods\n              .single { it.name == \"getSuperTypeInstance\" }\n              .invoke(graph)::class\n            .java\n        )\n        .isEqualTo(testClass)\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated for an inner class`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType\n\n      interface TestClass {\n        @Inject\n        @SingleIn(AppScope::class)\n        @ContributesScoped(AppScope::class)\n        class Inner : SuperType, Scoped\n      }\n      \"\"\"\n    ) {\n      val scopedGraph = testClass.inner.graph\n\n      assertThat(scopedGraph.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      with(scopedGraph.declaredNonSyntheticMethods.single { it.name == \"getBindSuperType\" }) {\n        assertThat(parameters.single().type).isEqualTo(testClass.inner)\n        assertThat(returnType).isEqualTo(superType)\n      }\n\n      with(\n        scopedGraph.declaredNonSyntheticMethods.single { it.name == \"getBindTestClassInnerScoped\" }\n      ) {\n        assertThat(parameters.single().type).isEqualTo(testClass.inner)\n        assertThat(returnType).isEqualTo(Scoped::class.java)\n      }\n    }\n  }\n\n  @Test\n  fun `a graph interface is generated when only Scoped is implemented`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : Scoped\n      \"\"\"\n    ) {\n      val scopedGraph = testClass.graph\n\n      assertThat(scopedGraph.getAnnotation(ContributesTo::class.java).scope)\n        .isEqualTo(AppScope::class)\n\n      assertThat(scopedGraph.declaredNonSyntheticMethods).hasSize(1)\n\n      with(scopedGraph.declaredNonSyntheticMethods.single { it.name == \"getBindTestClassScoped\" }) {\n        assertThat(parameters.single().type).isEqualTo(testClass)\n        assertThat(returnType).isEqualTo(Scoped::class.java)\n      }\n    }\n  }\n\n  @Test\n  fun `it's an error when there is no super type`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"In order to use @ContributesScoped, TestClass must implement software.amazon.app.platform.scope.Scoped.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it's an error when Scoped is not implemented`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : SuperType\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"In order to use @ContributesScoped, TestClass must implement software.amazon.app.platform.scope.Scoped.\"\n        )\n    }\n  }\n\n  @Test\n  fun `it's an error when there are multiple super types`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType\n      interface SuperType2\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : SuperType, SuperType2, Scoped\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"In order to use @ContributesScoped, TestClass is allowed to have only one other super type besides Scoped.\"\n        )\n    }\n  }\n\n  @Test\n  fun `Scoped can be implemented through another type`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType2 : Scoped\n      interface SuperType3 : SuperType2\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : SuperType2\n      \"\"\"\n    ) {\n      assertThat(testClass.graph).isNotNull()\n    }\n  }\n\n  @Test\n  fun `using @ContributesBinding when implementing Scoped is an error`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.ContributesBinding\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesBinding(AppScope::class)\n      class TestClass : SuperType, Scoped\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"TestClass implements Scoped, but uses @ContributesBinding instead of \" +\n            \"@ContributesScoped. When implementing Scoped the annotation @ContributesScoped \" +\n            \"must be used instead of @ContributesBinding to bind both super types correctly. \" +\n            \"It's not necessary to use @ContributesBinding.\"\n        )\n    }\n  }\n\n  @Test\n  fun `classes using @ContributesScoped can be excluded`() {\n    compile(\n      \"\"\"\n      package software.amazon.test\n\n      import software.amazon.app.platform.inject.metro.ContributesScoped\n      import software.amazon.app.platform.scope.Scoped\n      import dev.zacsweers.metro.AppScope\n      import dev.zacsweers.metro.createGraph\n      import dev.zacsweers.metro.DependencyGraph\n      import dev.zacsweers.metro.ForScope\n      import dev.zacsweers.metro.Inject\n      import dev.zacsweers.metro.SingleIn\n\n      interface SuperType\n\n      @Inject\n      @SingleIn(AppScope::class)\n      @ContributesScoped(AppScope::class)\n      class TestClass : SuperType, Scoped\n\n      @DependencyGraph(AppScope::class, excludes = [TestClass::class])\n      @SingleIn(AppScope::class)\n      interface GraphInterface {\n        val superTypeInstance: SuperType\n        \n        @ForScope(AppScope::class)\n        val allScoped: Set<Scoped>\n      \n        companion object {\n          fun create(): GraphInterface = createGraph<GraphInterface>()\n        }\n      }\n      \"\"\",\n      exitCode = COMPILATION_ERROR,\n    ) {\n      assertThat(messages)\n        .contains(\n          \"Cannot find an @Inject constructor or @Provides-annotated \" +\n            \"function/property for: software.amazon.test.SuperType\"\n        )\n    }\n  }\n\n  private val JvmCompilationResult.testClass: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.TestClass\")\n\n  private val JvmCompilationResult.superType: Class<*>\n    get() = classLoader.loadClass(\"software.amazon.test.SuperType\")\n\n  private val Class<*>.graph: Class<*>\n    get() =\n      classLoader.loadClass(\n        \"$METRO_LOOKUP_PACKAGE.$packageName.\" +\n          canonicalName.substringAfter(packageName).substring(1).split(\".\").joinToString(\n            separator = \"\"\n          ) {\n            it.capitalize()\n          } +\n          \"Graph\"\n      )\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/test/TestRendererGraph.kt",
    "content": "package software.amazon.test\n\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Provider\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererScope\n\ninterface TestRendererGraph {\n  val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>>\n\n  @ForScope(RendererScope::class)\n  val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>>\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-code-generators/src/test/kotlin/software/amazon/test/TestRobotGraph.kt",
    "content": "package software.amazon.test\n\nimport dev.zacsweers.metro.Provider\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.robot.Robot\n\ninterface TestRobotGraph {\n  val robots: Map<KClass<*>, Provider<Robot>>\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/build.gradle",
    "content": "//file:noinspection UnnecessaryQualifiedReference\nplugins {\n    id 'software.amazon.app.platform.lib.jvm'\n    id 'com.google.devtools.ksp'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\nconfigurations {\n    annotationsRuntimeClasspath {\n        transitive = false\n    }\n    metroRuntimeClasspath {\n        transitive = false\n    }\n}\n\ndependencies {\n    compileOnly libs.kotlin.compiler.embeddable\n    compileOnly libs.metro.compiler\n\n    implementation libs.auto.service.annotations\n    ksp libs.auto.service.ksp\n\n    annotationsRuntimeClasspath project(':di-common:public')\n    annotationsRuntimeClasspath project(':presenter:public')\n    annotationsRuntimeClasspath project(':renderer:public')\n    annotationsRuntimeClasspath project(':metro:public')\n    annotationsRuntimeClasspath project(':robot:public')\n    annotationsRuntimeClasspath project(':scope:public')\n    metroRuntimeClasspath libs.metro.runtime\n\n    testImplementation project(':di-common:public')\n    testImplementation project(':presenter:public')\n    testImplementation project(':renderer:public')\n    testImplementation project(':metro:public')\n    testImplementation project(':robot:public')\n    testImplementation project(':scope:public')\n    testImplementation libs.kotlin.compiler\n    testImplementation libs.kotlin.compiler.internal.test.framework\n    testImplementation libs.kotlin.test.junit5\n    testImplementation libs.metro.compiler\n\n    testRuntimeOnly libs.kotlin.test\n    testRuntimeOnly libs.kotlin.annotations.jvm\n    testRuntimeOnly libs.kotlin.reflect\n    testRuntimeOnly libs.kotlin.script.runtime\n}\n\ntasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {\n    compilerOptions {\n        freeCompilerArgs.add('-Xcontext-parameters')\n        optIn.add('org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi')\n    }\n}\n\ndef jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21\n\nconfigurations\n        .matching {\n            it.name in ['compileClasspath', 'runtimeClasspath', 'testCompileClasspath', 'testRuntimeClasspath']\n        }\n        .configureEach {\n            attributes.attribute(\n                    org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,\n                    jvmTarget.target.toInteger(),\n            )\n        }\n\ntasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile).configureEach {\n    compilerOptions {\n        it.jvmTarget.set(jvmTarget)\n    }\n}\n\ntasks.withType(JavaCompile).configureEach {\n    sourceCompatibility = jvmTarget.target\n    targetCompatibility = jvmTarget.target\n    javaCompiler = javaToolchains.compilerFor {\n        languageVersion = JavaLanguageVersion.of(jvmTarget.target)\n    }\n}\n\ndef annotationsClasspathFiles = configurations.annotationsRuntimeClasspath\ndef metroRuntimeClasspathFiles = configurations.metroRuntimeClasspath\ndef testClasspathFiles = configurations.testRuntimeClasspath\ndef testSupportClasspathFiles = sourceSets.test.output.classesDirs\n\ntasks.withType(Test).configureEach { testTask ->\n    useJUnitPlatform()\n    workingDir = rootDir\n    maxHeapSize = '2g'\n    javaLauncher = javaToolchains.launcherFor {\n        languageVersion = JavaLanguageVersion.of(jvmTarget.target)\n    }\n\n    systemProperty('idea.ignore.disabled.plugins', 'true')\n    systemProperty('idea.home.path', rootDir)\n\n    if (project.hasProperty('updateTestData')) {\n        systemProperty('kotlin.test.update.test.data', 'true')\n    }\n\n    doFirst {\n        def annotationsClasspath = annotationsClasspathFiles.asPath\n        def metroRuntimeClasspath = metroRuntimeClasspathFiles.asPath\n        def testSupportClasspath = testSupportClasspathFiles.asPath\n        def classpathFiles = testClasspathFiles.files\n\n        systemProperty(\n                'annotationsRuntime.classpath',\n                annotationsClasspath,\n        )\n        systemProperty('metroRuntime.classpath', metroRuntimeClasspath)\n        systemProperty('testSupport.classpath', testSupportClasspath)\n\n        def setLib = { String propName, String jarName ->\n            def prefix = \"${jarName}-\"\n            def path = classpathFiles.find { file ->\n                file.name.startsWith(prefix) &&\n                        file.name.endsWith('.jar') &&\n                        Character.isDigit(file.name.substring(prefix.length()).charAt(0))\n            }?.absolutePath\n            if (path != null) {\n                systemProperty(propName, path)\n            }\n        }\n\n        setLib('org.jetbrains.kotlin.test.kotlin-stdlib', 'kotlin-stdlib')\n        setLib('org.jetbrains.kotlin.test.kotlin-stdlib-jdk8', 'kotlin-stdlib-jdk8')\n        setLib('org.jetbrains.kotlin.test.kotlin-reflect', 'kotlin-reflect')\n        setLib('org.jetbrains.kotlin.test.kotlin-test', 'kotlin-test')\n        setLib('org.jetbrains.kotlin.test.kotlin-script-runtime', 'kotlin-script-runtime')\n        setLib('org.jetbrains.kotlin.test.kotlin-annotations-jvm', 'kotlin-annotations-jvm')\n    }\n}\n\ntasks.register('generateTests', JavaExec) {\n    inputs\n            .dir(layout.projectDirectory.dir('src/test/resources'))\n            .withPropertyName('testData')\n            .withPathSensitivity(PathSensitivity.RELATIVE)\n\n    outputs\n            .dir(layout.projectDirectory.dir('src/test/java'))\n            .withPropertyName('generatedTests')\n\n    classpath = sourceSets.test.runtimeClasspath\n    mainClass = 'software.amazon.app.platform.metro.compiler.GenerateTestsKt'\n    workingDir = rootDir\n\n    minHeapSize = '128m'\n    maxHeapSize = '1g'\n    jvmArgs('-Xss1m')\n}\n\n// We don't need the apiCheck in this module.\ntasks.named('apiCheck').configure {\n    it.enabled = false\n}\ntasks.named('apiDump').configure {\n    it.enabled = false\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/AppPlatformMetroExtensionsPluginComponentRegistrar.kt",
    "content": "package software.amazon.app.platform.metro.compiler\n\nimport com.google.auto.service.AutoService\nimport org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension\nimport org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar\nimport org.jetbrains.kotlin.config.CompilerConfiguration\nimport org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter\nimport software.amazon.app.platform.metro.compiler.renderer.ContributesRendererIrExtension\nimport software.amazon.app.platform.metro.compiler.robot.ContributesRobotIrExtension\n\n@AutoService(CompilerPluginRegistrar::class)\npublic class AppPlatformMetroExtensionsPluginComponentRegistrar : CompilerPluginRegistrar() {\n  override val pluginId: String = \"software.amazon.app.platform.metro.compiler\"\n  override val supportsK2: Boolean = true\n\n  override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {\n    FirExtensionRegistrarAdapter.registerExtension(AppPlatformMetroExtensionsPluginRegistrar())\n    IrGenerationExtension.registerExtension(ContributesRendererIrExtension())\n    IrGenerationExtension.registerExtension(ContributesRobotIrExtension())\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/AppPlatformMetroExtensionsPluginRegistrar.kt",
    "content": "package software.amazon.app.platform.metro.compiler\n\nimport org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar\nimport software.amazon.app.platform.metro.compiler.fir.AppPlatformMetroExtensionsFirCheckers\n\npublic class AppPlatformMetroExtensionsPluginRegistrar : FirExtensionRegistrar() {\n  override fun ExtensionRegistrarContext.configurePlugin() {\n    +::AppPlatformMetroExtensionsFirCheckers\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/ClassIds.kt",
    "content": "package software.amazon.app.platform.metro.compiler\n\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.FqName\nimport org.jetbrains.kotlin.name.Name\n\ninternal object ClassIds {\n  val APP_SCOPE = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"AppScope\"))\n\n  val BASE_MODEL =\n    ClassId(FqName(\"software.amazon.app.platform.presenter\"), Name.identifier(\"BaseModel\"))\n\n  val BINDS = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Binds\"))\n\n  val CONTRIBUTES_RENDERER =\n    ClassId(FqName(\"software.amazon.app.platform.inject\"), Name.identifier(\"ContributesRenderer\"))\n\n  val CONTRIBUTES_ROBOT =\n    ClassId(\n      FqName(\"software.amazon.app.platform.inject.robot\"),\n      Name.identifier(\"ContributesRobot\"),\n    )\n\n  val CONTRIBUTES_SCOPED =\n    ClassId(\n      FqName(\"software.amazon.app.platform.inject.metro\"),\n      Name.identifier(\"ContributesScoped\"),\n    )\n\n  val CONTRIBUTES_BINDING =\n    ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"ContributesBinding\"))\n\n  val CONTRIBUTES_TO = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"ContributesTo\"))\n\n  val DEPENDENCY_GRAPH = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"DependencyGraph\"))\n\n  val FOR_SCOPE = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"ForScope\"))\n\n  val INJECT = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Inject\"))\n\n  val INTO_MAP = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"IntoMap\"))\n\n  val INTO_SET = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"IntoSet\"))\n\n  val ORIGIN = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Origin\"))\n\n  val PROVIDER = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Provider\"))\n\n  val PROVIDES = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Provides\"))\n\n  val RENDERER =\n    ClassId(FqName(\"software.amazon.app.platform.renderer\"), Name.identifier(\"Renderer\"))\n\n  val RENDERER_KEY =\n    ClassId(FqName(\"software.amazon.app.platform.renderer.metro\"), Name.identifier(\"RendererKey\"))\n\n  val RENDERER_SCOPE =\n    ClassId(FqName(\"software.amazon.app.platform.renderer\"), Name.identifier(\"RendererScope\"))\n\n  val ROBOT = ClassId(FqName(\"software.amazon.app.platform.robot\"), Name.identifier(\"Robot\"))\n\n  val ROBOT_KEY =\n    ClassId(FqName(\"software.amazon.app.platform.renderer.metro\"), Name.identifier(\"RobotKey\"))\n\n  val ROBOT_FQ_NAMES: Set<ClassId> = setOf(ROBOT)\n\n  val SINGLE_IN = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"SingleIn\"))\n\n  val SCOPED = ClassId(FqName(\"software.amazon.app.platform.scope\"), Name.identifier(\"Scoped\"))\n\n  val SCOPE = ClassId(FqName(\"dev.zacsweers.metro\"), Name.identifier(\"Scope\"))\n\n  val UNIT = ClassId(FqName(\"kotlin\"), Name.identifier(\"Unit\"))\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/Keys.kt",
    "content": "package software.amazon.app.platform.metro.compiler\n\nimport org.jetbrains.kotlin.GeneratedDeclarationKey\n\ninternal object Keys {\n  data object ContributesRendererGeneratorKey : GeneratedDeclarationKey() {\n    override fun toString(): String = \"ContributesRendererGenerator\"\n  }\n\n  data object ContributesRobotGeneratorKey : GeneratedDeclarationKey() {\n    override fun toString(): String = \"ContributesRobotGenerator\"\n  }\n\n  data object ContributesScopedGeneratorKey : GeneratedDeclarationKey() {\n    override fun toString(): String = \"ContributesScopedGenerator\"\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/fir/AppPlatformMetroExtensionsDiagnostics.kt",
    "content": "package software.amazon.app.platform.metro.compiler.fir\n\nimport org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap\nimport org.jetbrains.kotlin.diagnostics.KtDiagnosticsContainer\nimport org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies\nimport org.jetbrains.kotlin.diagnostics.error1\nimport org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory\nimport org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers\nimport org.jetbrains.kotlin.psi.KtElement\n\ninternal object AppPlatformMetroExtensionsDiagnostics : KtDiagnosticsContainer() {\n  val CONTRIBUTES_RENDERER_ERROR by\n    error1<KtElement, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)\n\n  val CONTRIBUTES_ROBOT_ERROR by\n    error1<KtElement, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)\n\n  val CONTRIBUTES_SCOPED_ERROR by\n    error1<KtElement, String>(SourceElementPositioningStrategies.NAME_IDENTIFIER)\n\n  override fun getRendererFactory(): BaseDiagnosticRendererFactory {\n    return AppPlatformMetroExtensionsErrorMessages\n  }\n}\n\nprivate object AppPlatformMetroExtensionsErrorMessages : BaseDiagnosticRendererFactory() {\n  override val MAP by\n    KtDiagnosticFactoryToRendererMap(\"AppPlatformMetroExtensions\") { map ->\n      map.put(\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n        \"{0}\",\n        CommonRenderers.STRING,\n      )\n      map.put(\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"{0}\",\n        CommonRenderers.STRING,\n      )\n      map.put(\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n        \"{0}\",\n        CommonRenderers.STRING,\n      )\n    }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/fir/AppPlatformMetroExtensionsFirCheckers.kt",
    "content": "package software.amazon.app.platform.metro.compiler.fir\n\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers\nimport org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker\nimport org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension\nimport software.amazon.app.platform.metro.compiler.renderer.ContributesRendererChecker\nimport software.amazon.app.platform.metro.compiler.robot.ContributesRobotChecker\nimport software.amazon.app.platform.metro.compiler.scoped.ContributesScopedChecker\n\ninternal class AppPlatformMetroExtensionsFirCheckers(session: FirSession) :\n  FirAdditionalCheckersExtension(session) {\n  override val declarationCheckers: DeclarationCheckers =\n    object : DeclarationCheckers() {\n      override val classCheckers: Set<FirClassChecker> =\n        setOf(ContributesRendererChecker, ContributesRobotChecker, ContributesScopedChecker)\n    }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/fir/FirHelpers.kt",
    "content": "package software.amazon.app.platform.metro.compiler.fir\n\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess\nimport org.jetbrains.kotlin.fir.declarations.FirDeclaration\nimport org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin\nimport org.jetbrains.kotlin.fir.declarations.FirFile\nimport org.jetbrains.kotlin.fir.declarations.FirRegularClass\nimport org.jetbrains.kotlin.fir.declarations.FirResolvePhase\nimport org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter\nimport org.jetbrains.kotlin.fir.declarations.toAnnotationClassIdSafe\nimport org.jetbrains.kotlin.fir.expressions.FirAnnotation\nimport org.jetbrains.kotlin.fir.expressions.FirAnnotationCall\nimport org.jetbrains.kotlin.fir.expressions.FirAnnotationResolvePhase\nimport org.jetbrains.kotlin.fir.expressions.FirExpression\nimport org.jetbrains.kotlin.fir.expressions.FirGetClassCall\nimport org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression\nimport org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression\nimport org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier\nimport org.jetbrains.kotlin.fir.expressions.buildResolvedArgumentList\nimport org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping\nimport org.jetbrains.kotlin.fir.expressions.builder.buildGetClassCall\nimport org.jetbrains.kotlin.fir.expressions.builder.buildResolvedQualifier\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.references.FirResolvedNamedReference\nimport org.jetbrains.kotlin.fir.references.builder.buildResolvedNamedReference\nimport org.jetbrains.kotlin.fir.resolve.fullyExpandedType\nimport org.jetbrains.kotlin.fir.resolve.providers.firProvider\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.resolve.toRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.FirBasedSymbol\nimport org.jetbrains.kotlin.fir.symbols.SymbolInternals\nimport org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol\nimport org.jetbrains.kotlin.fir.toFirResolvedTypeRef\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.FqName\nimport org.jetbrains.kotlin.name.Name\n\ninternal fun hasAnnotation(\n  classSymbol: FirClassSymbol<*>,\n  annotationClassId: ClassId,\n  session: FirSession,\n): Boolean {\n  return classSymbol.resolvedCompilerAnnotationsWithClassIds.any {\n    it.toAnnotationClassIdSafe(session) == annotationClassId\n  }\n}\n\ninternal fun findAnnotation(\n  classSymbol: FirClassSymbol<*>,\n  annotationClassId: ClassId,\n  session: FirSession,\n): FirAnnotation? {\n  return classSymbol.resolvedAnnotationsWithArguments.firstOrNull { annotation ->\n    annotation.toAnnotationClassIdSafe(session) == annotationClassId\n  }\n}\n\n@OptIn(DirectDeclarationsAccess::class, SymbolInternals::class)\ninternal fun buildAnnotationCallWithArgument(\n  classId: ClassId,\n  argName: Name,\n  argument: FirExpression,\n  containingSymbol: FirBasedSymbol<*>,\n  session: FirSession,\n): FirAnnotationCall {\n  val annotationType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(classId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n  val annotationClassSymbol =\n    session.symbolProvider.getClassLikeSymbolByClassId(classId)\n      ?: error(\"Annotation class $classId not found on the classpath\")\n  val constructorSymbol =\n    (annotationClassSymbol as? FirClassSymbol<*>)\n      ?.declarationSymbols\n      ?.filterIsInstance<FirConstructorSymbol>()\n      ?.firstOrNull() ?: error(\"No constructor found for annotation class $classId\")\n  val argumentParameter = constructorSymbol.fir.valueParameters.first { it.name == argName }\n\n  return org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationCall {\n    annotationTypeRef = annotationType.toFirResolvedTypeRef()\n    argumentMapping = buildAnnotationArgumentMapping { mapping[argName] = argument }\n    argumentList =\n      buildResolvedArgumentList(\n        original = null,\n        mapping = linkedMapOf(argument to argumentParameter),\n      )\n    calleeReference = buildResolvedNamedReference {\n      name = classId.shortClassName\n      resolvedSymbol = constructorSymbol\n    }\n    containingDeclarationSymbol = containingSymbol\n    annotationResolvePhase = FirAnnotationResolvePhase.Types\n  }\n}\n\n@OptIn(DirectDeclarationsAccess::class)\ninternal fun buildSimpleAnnotationCall(\n  classId: ClassId,\n  containingSymbol: FirBasedSymbol<*>,\n  session: FirSession,\n): FirAnnotationCall {\n  val annotationType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(classId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n  val annotationClassSymbol =\n    session.symbolProvider.getClassLikeSymbolByClassId(classId)\n      ?: error(\"Annotation class $classId not found on the classpath\")\n  val constructorSymbol =\n    (annotationClassSymbol as? FirClassSymbol<*>)\n      ?.declarationSymbols\n      ?.filterIsInstance<FirConstructorSymbol>()\n      ?.firstOrNull() ?: error(\"No constructor found for annotation class $classId\")\n\n  return org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationCall {\n    annotationTypeRef = annotationType.toFirResolvedTypeRef()\n    argumentMapping = buildAnnotationArgumentMapping()\n    calleeReference = buildResolvedNamedReference {\n      name = classId.shortClassName\n      resolvedSymbol = constructorSymbol\n    }\n    containingDeclarationSymbol = containingSymbol\n    annotationResolvePhase = FirAnnotationResolvePhase.Types\n  }\n}\n\ninternal fun extractScopeArgument(\n  classSymbol: FirClassSymbol<*>,\n  annotationClassId: ClassId,\n  session: FirSession,\n): FirExpression? {\n  val annotation = findAnnotation(classSymbol, annotationClassId, session) ?: return null\n  val annotationCall = annotation as? FirAnnotationCall ?: return null\n  val firstArgument = annotationCall.argumentList.arguments.firstOrNull() ?: return null\n  return if (firstArgument is FirNamedArgumentExpression) {\n    firstArgument.expression\n  } else {\n    firstArgument\n  }\n}\n\ninternal fun extractScopeClassId(\n  classSymbol: FirRegularClassSymbol,\n  annotationClassId: ClassId,\n  session: FirSession,\n): ClassId? {\n  val annotation = findAnnotation(classSymbol, annotationClassId, session) ?: return null\n  val annotationCall = annotation as? FirAnnotationCall ?: return null\n\n  val rawScopeExpression =\n    annotationCall.argumentMapping.mapping[Name.identifier(\"scope\")]\n      ?: annotationCall.argumentList.arguments.firstOrNull()\n      ?: return null\n\n  return resolveClassIdArgument(rawScopeExpression, classSymbol, session)\n}\n\ninternal fun unwrapArgumentExpression(expression: FirExpression): FirExpression {\n  return if (expression is FirNamedArgumentExpression) expression.expression else expression\n}\n\ninternal data class ResolvedClassReference(\n  val classId: ClassId,\n  val classSymbol: FirRegularClassSymbol?,\n)\n\ninternal fun resolveClassIdArgument(\n  rawExpression: FirExpression,\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): ClassId? {\n  return resolveClassReferenceArgument(rawExpression, classSymbol, session)?.classId\n}\n\ninternal fun resolveClassReferenceArgument(\n  rawExpression: FirExpression,\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): ResolvedClassReference? {\n  val expression = unwrapArgumentExpression(rawExpression)\n  val getClassCall = expression as? FirGetClassCall ?: return null\n  val innerArgument = getClassCall.argumentList.arguments.firstOrNull() ?: return null\n\n  return when (innerArgument) {\n    is FirResolvedQualifier ->\n      innerArgument.classId?.let { classId ->\n        ResolvedClassReference(\n          classId = classId,\n          classSymbol =\n            (innerArgument.symbol as? FirRegularClassSymbol)\n              ?: (session.symbolProvider.getClassLikeSymbolByClassId(classId)\n                as? FirRegularClassSymbol)\n              ?: findClassLikeSymbolInContainingFile(classSymbol, classId, session)\n              ?: findClassLikeSymbolInPackageFiles(\n                classSymbol.classId.packageFqName,\n                classId,\n                session,\n              ),\n        )\n      }\n\n    is FirPropertyAccessExpression -> {\n      val reference = innerArgument.calleeReference\n      if (\n        reference is FirResolvedNamedReference && reference.resolvedSymbol is FirRegularClassSymbol\n      ) {\n        val resolvedSymbol = reference.resolvedSymbol as FirRegularClassSymbol\n        ResolvedClassReference(resolvedSymbol.classId, resolvedSymbol)\n      } else {\n        val name = reference.name\n        val file = session.firProvider.getFirClassifierContainerFileIfAny(classSymbol)\n        val samePackageClassId =\n          if (name.asString() == \"Unit\") {\n            ClassId(FqName(\"kotlin\"), name)\n          } else {\n            ClassId(classSymbol.classId.packageFqName, name)\n          }\n        val explicitImportClassIds =\n          file\n            ?.imports\n            ?.filter { !it.isAllUnder }\n            ?.mapNotNull { import ->\n              val importedFqName = import.importedFqName ?: return@mapNotNull null\n              ClassId.topLevel(importedFqName).takeIf { importedFqName.shortName() == name }\n            }\n            .orEmpty()\n        val allUnderImportClassIds =\n          file\n            ?.imports\n            ?.filter { it.isAllUnder }\n            ?.mapNotNull { import ->\n              val importedFqName = import.importedFqName ?: return@mapNotNull null\n              ClassId(importedFqName, name)\n            }\n            .orEmpty()\n        val classId =\n          sequenceOf(samePackageClassId)\n            .plus(explicitImportClassIds.asSequence())\n            .plus(allUnderImportClassIds.asSequence())\n            .firstOrNull { candidateClassId ->\n              session.symbolProvider.getClassLikeSymbolByClassId(candidateClassId) != null ||\n                findClassLikeSymbolInContainingFile(classSymbol, candidateClassId, session) !=\n                  null ||\n                findClassLikeSymbolInPackageFiles(\n                  classSymbol.classId.packageFqName,\n                  candidateClassId,\n                  session,\n                ) != null\n            } ?: samePackageClassId\n        ResolvedClassReference(\n          classId,\n          (session.symbolProvider.getClassLikeSymbolByClassId(classId) as? FirRegularClassSymbol)\n            ?: findClassLikeSymbolInContainingFile(classSymbol, classId, session)\n            ?: findClassLikeSymbolInPackageFiles(\n              classSymbol.classId.packageFqName,\n              classId,\n              session,\n            ),\n        )\n      }\n    }\n\n    else -> null\n  }\n}\n\ninternal fun buildClassExpression(\n  classSymbol: FirClassSymbol<*>,\n  session: FirSession,\n): FirExpression {\n  val classId = classSymbol.classId\n  val classType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(classId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n  val kClassClassId = ClassId(FqName(\"kotlin.reflect\"), Name.identifier(\"KClass\"))\n  val kClassType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(kClassClassId),\n      arrayOf(classType),\n      isMarkedNullable = false,\n    )\n\n  return buildGetClassCall {\n    coneTypeOrNull = kClassType\n    val qualifier = buildResolvedQualifier {\n      packageFqName = classId.packageFqName\n      relativeClassFqName = classId.relativeClassName\n      coneTypeOrNull = classType\n      symbol = classSymbol\n      resolvedToCompanionObject = false\n      isFullyQualified = true\n    }\n    argumentList =\n      buildResolvedArgumentList(\n        original = null,\n        mapping =\n          linkedMapOf(\n            qualifier to buildSyntheticClassLiteralParameter(classType, classSymbol, session)\n          ),\n      )\n  }\n}\n\ninternal fun buildClassExpression(\n  classId: ClassId,\n  session: FirSession,\n  ownerSymbol: FirRegularClassSymbol? = null,\n): FirExpression {\n  val classType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(classId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n  val kClassClassId = ClassId(FqName(\"kotlin.reflect\"), Name.identifier(\"KClass\"))\n  val kClassType =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(kClassClassId),\n      arrayOf(classType),\n      isMarkedNullable = false,\n    )\n\n  val classSymbol =\n    session.symbolProvider.getClassLikeSymbolByClassId(classId)\n      ?: ownerSymbol?.let { findClassLikeSymbolInContainingFile(it, classId, session) }\n      ?: ownerSymbol?.let {\n        findClassLikeSymbolInPackageFiles(it.classId.packageFqName, classId, session)\n      }\n\n  return buildGetClassCall {\n    coneTypeOrNull = kClassType\n    val qualifier = buildResolvedQualifier {\n      packageFqName = classId.packageFqName\n      relativeClassFqName = classId.relativeClassName\n      coneTypeOrNull = classType\n      symbol = classSymbol\n      resolvedToCompanionObject = false\n      isFullyQualified = classSymbol != null\n    }\n    argumentList =\n      buildResolvedArgumentList(\n        original = null,\n        mapping =\n          linkedMapOf(\n            qualifier to\n              buildSyntheticClassLiteralParameter(\n                classType = classType,\n                containingSymbol = classSymbol ?: ownerSymbol,\n                session = session,\n              )\n          ),\n      )\n  }\n}\n\nprivate fun buildSyntheticClassLiteralParameter(\n  classType: ConeClassLikeTypeImpl,\n  containingSymbol: FirBasedSymbol<*>?,\n  session: FirSession,\n) = buildValueParameter {\n  moduleData = session.moduleData\n  resolvePhase = FirResolvePhase.BODY_RESOLVE\n  origin = FirDeclarationOrigin.Synthetic.PluginFile\n  returnTypeRef = classType.toFirResolvedTypeRef()\n  name = Name.identifier(\"value\")\n  symbol = FirValueParameterSymbol()\n  containingDeclarationSymbol =\n    containingSymbol ?: error(\"Unable to determine containing symbol for generated class literal\")\n}\n\n@OptIn(DirectDeclarationsAccess::class)\ninternal fun findClassLikeSymbolInContainingFile(\n  ownerSymbol: FirRegularClassSymbol,\n  classId: ClassId,\n  session: FirSession,\n): FirRegularClassSymbol? {\n  val file = session.firProvider.getFirClassifierContainerFileIfAny(ownerSymbol) ?: return null\n  return findClassLikeSymbolInFile(file, classId)\n}\n\n@OptIn(DirectDeclarationsAccess::class)\ninternal fun findClassLikeSymbolInPackageFiles(\n  packageFqName: FqName,\n  classId: ClassId,\n  session: FirSession,\n): FirRegularClassSymbol? {\n  return session.firProvider.getFirFilesByPackage(packageFqName).firstNotNullOfOrNull { file ->\n    findClassLikeSymbolInFile(file, classId)\n  }\n}\n\n@OptIn(DirectDeclarationsAccess::class)\nprivate fun findClassLikeSymbolInFile(file: FirFile, classId: ClassId): FirRegularClassSymbol? {\n  return file.declarations.firstNotNullOfOrNull { declaration ->\n    findClassLikeSymbolInDeclaration(declaration, classId)\n  }\n}\n\n@OptIn(DirectDeclarationsAccess::class)\nprivate fun findClassLikeSymbolInDeclaration(\n  declaration: FirDeclaration,\n  classId: ClassId,\n): FirRegularClassSymbol? {\n  val regularClass = declaration as? FirRegularClass\n  if (regularClass?.symbol?.classId == classId) {\n    return regularClass.symbol\n  }\n  if (regularClass != null) {\n    return regularClass.declarations.firstNotNullOfOrNull { nestedDeclaration ->\n      findClassLikeSymbolInDeclaration(nestedDeclaration, classId)\n    }\n  }\n  return null\n}\n\ninternal fun hasTransitiveSupertype(\n  type: ConeKotlinType,\n  session: FirSession,\n  targetIds: Collection<ClassId>,\n  visited: MutableSet<ClassId> = mutableSetOf(),\n): Boolean {\n  val classSymbol = type.toRegularClassSymbol(session) ?: return false\n  val classId = classSymbol.classId\n  if (!visited.add(classId)) return false\n  if (classId in targetIds) return true\n\n  return classSymbol.resolvedSuperTypeRefs.any { superTypeRef ->\n    val superConeType = superTypeRef.coneType.fullyExpandedType(session)\n    hasTransitiveSupertype(superConeType, session, targetIds, visited)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/fir/TypeResolution.kt",
    "content": "package software.amazon.app.platform.metro.compiler.fir\n\nimport org.jetbrains.kotlin.fir.FirModuleData\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.FirFile\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.resolve.ScopeSession\nimport org.jetbrains.kotlin.fir.resolve.SupertypeSupplier\nimport org.jetbrains.kotlin.fir.resolve.TypeResolutionConfiguration\nimport org.jetbrains.kotlin.fir.resolve.fullyExpandedType\nimport org.jetbrains.kotlin.fir.resolve.providers.firProvider\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap\nimport org.jetbrains.kotlin.fir.resolve.toRegularClassSymbol\nimport org.jetbrains.kotlin.fir.resolve.typeResolver\nimport org.jetbrains.kotlin.fir.scopes.createImportingScopes\nimport org.jetbrains.kotlin.fir.scopes.getSingleClassifier\nimport org.jetbrains.kotlin.fir.symbols.SymbolInternals\nimport org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeConflictingProjection\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjection\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjectionIn\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjectionOut\nimport org.jetbrains.kotlin.fir.types.ConeStarProjection\nimport org.jetbrains.kotlin.fir.types.FirResolvedTypeRef\nimport org.jetbrains.kotlin.fir.types.FirStarProjection\nimport org.jetbrains.kotlin.fir.types.FirTypeProjection\nimport org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance\nimport org.jetbrains.kotlin.fir.types.FirTypeRef\nimport org.jetbrains.kotlin.fir.types.FirUserTypeRef\nimport org.jetbrains.kotlin.fir.types.classId\nimport org.jetbrains.kotlin.fir.types.coneTypeOrNull\nimport org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport org.jetbrains.kotlin.name.StandardClassIds\nimport org.jetbrains.kotlin.types.Variance\n\n@OptIn(SymbolInternals::class)\ninternal fun resolveDeclaredSuperTypes(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n  actualType: ConeKotlinType? = null,\n): List<ConeKotlinType> {\n  val resolutionSession = classSymbol.fir.moduleData.session\n  val substitution =\n    actualType\n      ?.let { actual ->\n        classSymbol.typeParameterSymbols.zip(actual.typeArguments).mapNotNull { (parameter, arg) ->\n          (arg as? ConeKotlinTypeProjection)?.type?.let { parameter to it }\n        }\n      }\n      .orEmpty()\n  val substitutor =\n    substitution.takeIf { it.isNotEmpty() }?.let { substitutorByMap(it.toMap(), session) }\n\n  return classSymbol.fir.superTypeRefs.mapNotNull { superTypeRef ->\n    val resolvedType =\n      resolveSuperTypeRef(superTypeRef, classSymbol, resolutionSession) ?: return@mapNotNull null\n    val substitutedType = substitutor?.substituteOrNull(resolvedType) ?: resolvedType\n    val expandedType = substitutedType.fullyExpandedType(session)\n    expandedType.takeUnless { it.toRegularClassSymbol(session)?.classId == StandardClassIds.Any }\n  }\n}\n\ninternal fun findContainingFile(classSymbol: FirClassLikeSymbol<*>, session: FirSession): FirFile? {\n  return allSessions(session).firstNotNullOfOrNull { candidate ->\n    candidate.firProvider.getFirClassifierContainerFileIfAny(classSymbol)\n  }\n}\n\ninternal fun allSessions(session: FirSession): List<FirSession> {\n  val visitedModules = linkedSetOf<FirModuleData>()\n  val visited = linkedSetOf<FirSession>()\n\n  fun visit(moduleData: FirModuleData) {\n    visited.add(moduleData.session)\n    if (!visitedModules.add(moduleData)) return\n\n    moduleData.dependencies.forEach(::visit)\n    moduleData.friendDependencies.forEach(::visit)\n    moduleData.dependsOnDependencies.forEach(::visit)\n  }\n\n  visit(session.moduleData)\n  return visited.toList()\n}\n\nprivate fun resolveSuperTypeRef(\n  typeRef: FirTypeRef,\n  owner: FirRegularClassSymbol,\n  session: FirSession,\n): ConeKotlinType? {\n  return when (typeRef) {\n    is FirResolvedTypeRef -> typeRef.coneType\n    is FirUserTypeRef -> resolveUserType(typeRef, owner, session)\n    else -> typeRef.coneTypeOrNull\n  }?.fullyExpandedType(session)\n}\n\nprivate fun resolveUserType(\n  typeRef: FirUserTypeRef,\n  owner: FirRegularClassSymbol,\n  session: FirSession,\n): ConeKotlinType? {\n  val manualType = resolveUserTypeManually(typeRef, owner, session)\n  val file = findContainingFile(owner, session) ?: return typeRef.coneTypeOrNull ?: manualType\n  val scopes = createImportingScopes(file, session, ScopeSession())\n  val configuration = TypeResolutionConfiguration(scopes, emptyList(), useSiteFile = file)\n  val resolvedType =\n    runCatching {\n        session.typeResolver\n          .resolveType(\n            typeRef = typeRef,\n            configuration = configuration,\n            areBareTypesAllowed = true,\n            isOperandOfIsOperator = false,\n            resolveDeprecations = false,\n            supertypeSupplier = SupertypeSupplier.Default,\n            expandTypeAliases = false,\n          )\n          .type\n      }\n      .getOrElse {\n        return manualType ?: throw it\n      }\n\n  if (resolvedType.classId?.shortClassName?.asString() != \"<error>\") {\n    return resolvedType\n  }\n\n  return manualType ?: resolvedType\n}\n\nprivate fun resolveUserTypeManually(\n  typeRef: FirUserTypeRef,\n  owner: FirRegularClassSymbol,\n  session: FirSession,\n): ConeKotlinType? {\n  val fallbackClassId = resolveUserTypeClassId(typeRef, owner, session) ?: return null\n  val typeArguments =\n    typeRef.qualifier.lastOrNull()?.typeArgumentList?.typeArguments?.map { argument ->\n      resolveTypeProjection(argument, owner, session)\n    }\n  return ConeClassLikeTypeImpl(\n    ConeClassLikeLookupTagImpl(fallbackClassId),\n    typeArguments.orEmpty().toTypedArray(),\n    isMarkedNullable = typeRef.isMarkedNullable,\n  )\n}\n\nprivate fun resolveTypeProjection(\n  projection: FirTypeProjection,\n  owner: FirRegularClassSymbol,\n  session: FirSession,\n) =\n  when (projection) {\n    is FirStarProjection -> ConeStarProjection\n    is FirTypeProjectionWithVariance -> {\n      val resolvedType =\n        resolveSuperTypeRef(projection.typeRef, owner, session) ?: projection.typeRef.coneTypeOrNull\n      when {\n        resolvedType == null -> ConeStarProjection\n        projection.variance == Variance.IN_VARIANCE -> ConeKotlinTypeProjectionIn(resolvedType)\n        projection.variance == Variance.OUT_VARIANCE -> ConeKotlinTypeProjectionOut(resolvedType)\n        else -> ConeKotlinTypeConflictingProjection(resolvedType)\n      }\n    }\n    else -> ConeStarProjection\n  }\n\nprivate fun resolveUserTypeClassId(\n  typeRef: FirUserTypeRef,\n  owner: FirRegularClassSymbol,\n  session: FirSession,\n): ClassId? {\n  val qualifierNames = typeRef.qualifier.map { it.name }\n  if (qualifierNames.isEmpty()) return null\n\n  val importedClassId =\n    findContainingFile(owner, session)?.let { file ->\n      val scopes = createImportingScopes(file, session, ScopeSession())\n      scopes.firstNotNullOfOrNull { scope ->\n        val importedSymbol =\n          scope.getSingleClassifier(qualifierNames.first()) as? FirClassLikeSymbol<*>\n            ?: return@firstNotNullOfOrNull null\n        qualifierNames.drop(1).fold(importedSymbol.classId) { classId, name ->\n          classId.createNestedClassId(name)\n        }\n      }\n    }\n  if (importedClassId != null) return importedClassId\n\n  return userTypeClassIdCandidates(owner, qualifierNames).firstOrNull { classId ->\n    session.symbolProvider.getClassLikeSymbolByClassId(classId) != null ||\n      findClassLikeSymbolInContainingFile(owner, classId, session) != null ||\n      findClassLikeSymbolInPackageFiles(owner.classId.packageFqName, classId, session) != null\n  }\n}\n\nprivate fun userTypeClassIdCandidates(\n  owner: FirRegularClassSymbol,\n  qualifierNames: List<Name>,\n): Sequence<ClassId> {\n  fun nestedClassId(base: ClassId, nestedNames: List<Name>): ClassId {\n    return nestedNames.fold(base) { classId, name -> classId.createNestedClassId(name) }\n  }\n\n  val topLevelCandidate =\n    nestedClassId(\n      ClassId.topLevel(owner.classId.packageFqName.child(qualifierNames.first())),\n      qualifierNames.drop(1),\n    )\n\n  return sequence {\n    yield(topLevelCandidate)\n\n    var parentClassId = owner.classId.parentClassId\n    while (parentClassId != null) {\n      yield(nestedClassId(parentClassId, qualifierNames))\n      parentClassId = parentClassId.parentClassId\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererChecker.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.diagnostics.DiagnosticReporter\nimport org.jetbrains.kotlin.diagnostics.reportOn\nimport org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind\nimport org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext\nimport org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker\nimport org.jetbrains.kotlin.fir.declarations.FirClass\nimport org.jetbrains.kotlin.fir.declarations.toAnnotationClassId\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.fir.AppPlatformMetroExtensionsDiagnostics\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\n\ninternal object ContributesRendererChecker : FirClassChecker(MppCheckerKind.Common) {\n\n  context(context: CheckerContext, reporter: DiagnosticReporter)\n  override fun check(declaration: FirClass) {\n    declaration.source ?: return\n    val session = context.session\n\n    val annotation =\n      declaration.annotations.firstOrNull { candidate ->\n        candidate.toAnnotationClassId(session) ==\n          ContributesRendererIds.CONTRIBUTES_RENDERER_CLASS_ID\n      } ?: return\n\n    val classSymbol = declaration.symbol as? FirRegularClassSymbol ?: return\n\n    if (declaration.classKind != ClassKind.CLASS) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n        \"@ContributesRenderer can only be applied to classes, not \" +\n          \"${declaration.classKind.name.lowercase().replace('_', ' ')}s.\",\n      )\n      return\n    }\n\n    when (val modelTypeResolution = resolveRendererModelType(classSymbol, session)) {\n      is RendererModelTypeResolution.Error -> {\n        reporter.reportOn(\n          annotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n          modelTypeResolution.message,\n        )\n      }\n\n      is RendererModelTypeResolution.Success -> Unit\n    }\n\n    if (isSingleInRendererScope(classSymbol, session)) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n        \"Renderers should not be singletons in the RendererScope. The RendererFactory will \" +\n          \"cache the Renderer when necessary. Remove the @SingleIn(RendererScope::class) \" +\n          \"annotation.\",\n      )\n    }\n\n    val parameterCount = constructorParameterCount(classSymbol)\n    if (hasAnnotation(classSymbol, ClassIds.INJECT, session)) {\n      if (parameterCount == 0) {\n        reporter.reportOn(\n          annotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n          \"It's redundant to use @Inject when using @ContributesRenderer for a Renderer with \" +\n            \"a zero-arg constructor.\",\n        )\n      }\n    } else if (parameterCount > 0) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_RENDERER_ERROR,\n        \"When using @ContributesRenderer and you need to inject types in the constructor, \" +\n          \"then it's necessary to add the @Inject annotation.\",\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererFir.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroFirDeclarationGenerationExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.descriptors.Modality\nimport org.jetbrains.kotlin.descriptors.Visibilities\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.FirFunction\nimport org.jetbrains.kotlin.fir.declarations.FirResolvePhase\nimport org.jetbrains.kotlin.fir.declarations.builder.buildNamedFunction\nimport org.jetbrains.kotlin.fir.declarations.builder.buildRegularClass\nimport org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter\nimport org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl\nimport org.jetbrains.kotlin.fir.declarations.origin\nimport org.jetbrains.kotlin.fir.expressions.builder.buildAnnotation\nimport org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider\nimport org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol\nimport org.jetbrains.kotlin.fir.toEffectiveVisibility\nimport org.jetbrains.kotlin.fir.toFirResolvedTypeRef\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjectionOut\nimport org.jetbrains.kotlin.fir.types.ConeStarProjection\nimport org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl\nimport org.jetbrains.kotlin.name.CallableId\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.FqName\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.Keys\nimport software.amazon.app.platform.metro.compiler.fir.buildAnnotationCallWithArgument\nimport software.amazon.app.platform.metro.compiler.fir.buildClassExpression\nimport software.amazon.app.platform.metro.compiler.fir.buildSimpleAnnotationCall\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\n\n/**\n * Generates the declaration shape for `@ContributesRenderer` classes.\n *\n * Pseudo Kotlin:\n * ```kotlin\n * @ContributesRenderer\n * class TestRenderer : Renderer<Model> {\n *\n *   @ContributesTo(RendererScope::class)\n *   @Origin(TestRenderer::class)\n *   interface RendererContribution {\n *     @Provides\n *     fun provideTestRenderer(): TestRenderer\n *\n *     @Binds\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     fun provideTestRendererModel(renderer: TestRenderer): Renderer<*>\n *\n *     @Provides\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     @ForScope(RendererScope::class)\n *     fun provideTestRendererModelKey(): KClass<out Renderer<*>>\n *   }\n * }\n * ```\n *\n * No top-level graph interface is generated. If the renderer has an `@Inject` constructor, the\n * nested declaration omits `provideTestRenderer()` and only the map bindings are generated. When\n * `includeSealedSubtypes` is enabled, the `Model` binding pair is generated once per collected\n * model subtype.\n */\npublic class ContributesRendererFir(session: FirSession) :\n  MetroFirDeclarationGenerationExtension(session) {\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(ContributesRendererIds.PREDICATE)\n  }\n\n  override fun getContributionHints(): List<ContributionHint> {\n    return annotatedRendererClasses().map { classSymbol ->\n      ContributionHint(\n        contributingClassId =\n          classSymbol.classId.createNestedClassId(ContributesRendererIds.NESTED_INTERFACE_NAME),\n        scope = ClassIds.RENDERER_SCOPE,\n      )\n    }\n  }\n\n  override fun getNestedClassifiersNames(\n    classSymbol: FirClassSymbol<*>,\n    context: NestedClassGenerationContext,\n  ): Set<Name> {\n    return if (\n      hasAnnotation(classSymbol, ContributesRendererIds.CONTRIBUTES_RENDERER_CLASS_ID, session)\n    ) {\n      setOf(ContributesRendererIds.NESTED_INTERFACE_NAME)\n    } else {\n      emptySet()\n    }\n  }\n\n  override fun generateNestedClassLikeDeclaration(\n    owner: FirClassSymbol<*>,\n    name: Name,\n    context: NestedClassGenerationContext,\n  ): FirClassLikeSymbol<*>? {\n    if (name != ContributesRendererIds.NESTED_INTERFACE_NAME) return null\n    if (!hasAnnotation(owner, ContributesRendererIds.CONTRIBUTES_RENDERER_CLASS_ID, session))\n      return null\n\n    val rendererOwner = owner as? FirRegularClassSymbol ?: return null\n    val metadata = rendererContributionMetadata(rendererOwner, session) ?: return null\n    val nestedClassId = rendererOwner.classId.createNestedClassId(name)\n    val graphSymbol = FirRegularClassSymbol(nestedClassId)\n\n    buildRegularClass {\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRendererGeneratorKey.origin\n      source = rendererOwner.source\n      classKind = ClassKind.INTERFACE\n      scopeProvider = session.kotlinScopeProvider\n      this.name = nestedClassId.shortClassName\n      symbol = graphSymbol\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.ABSTRACT,\n          Visibilities.Public.toEffectiveVisibility(rendererOwner, forClass = true),\n        )\n      superTypeRefs += session.builtinTypes.anyType\n      annotations += buildReflectionContributesToAnnotation()\n      annotations += buildOriginAnnotation(rendererOwner, graphSymbol)\n      for (function in buildProvidesFunctions(nestedClassId, rendererOwner, metadata)) {\n        declarations += function\n      }\n    }\n\n    return graphSymbol\n  }\n\n  private fun annotatedRendererClasses(): List<FirRegularClassSymbol> {\n    return session.predicateBasedProvider\n      .getSymbolsByPredicate(ContributesRendererIds.PREDICATE)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  private fun buildProvidesFunctions(\n    graphClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    metadata: RendererContributionMetadata,\n    includeRendererProvider: Boolean = true,\n  ): List<FirFunction> {\n    val functions = mutableListOf<FirFunction>()\n    if (includeRendererProvider && !metadata.hasInjectAnnotation) {\n      functions += buildProvideRendererFunction(graphClassId, owner)\n    }\n    metadata.modelClasses.forEach { modelClass ->\n      functions += buildProvideRendererIntoMapFunction(graphClassId, owner, modelClass)\n      functions += buildProvideRendererKeyFunction(graphClassId, owner, modelClass)\n    }\n    return functions\n  }\n\n  private fun buildProvideRendererFunction(\n    graphClassId: ClassId,\n    owner: FirRegularClassSymbol,\n  ): FirFunction {\n    val functionName =\n      \"provide${ContributesRendererIds.generatedSafeClassNamePrefix(owner.classId)}\"\n    val callableId = CallableId(graphClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRendererGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = owner.defaultType().toFirResolvedTypeRef()\n      dispatchReceiverType = generatedGraphType(graphClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      annotations += buildSimpleAnnotationCall(ClassIds.PROVIDES, functionSymbol, session)\n    }\n  }\n\n  private fun buildProvideRendererIntoMapFunction(\n    graphClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    modelClass: ResolvedModelClass,\n  ): FirFunction {\n    val functionName =\n      \"provide${ContributesRendererIds.generatedSafeClassNamePrefix(owner.classId)}\" +\n        ContributesRendererIds.generatedModelClassNameSuffix(modelClass.classId)\n    val callableId = CallableId(graphClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n    val ownerType = owner.defaultType()\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRendererGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = rendererStarType().toFirResolvedTypeRef()\n      dispatchReceiverType = generatedGraphType(graphClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      valueParameters += buildValueParameter {\n        resolvePhase = FirResolvePhase.BODY_RESOLVE\n        moduleData = session.moduleData\n        origin = Keys.ContributesRendererGeneratorKey.origin\n        source = owner.source\n        returnTypeRef = ownerType.toFirResolvedTypeRef()\n        name = Name.identifier(\"renderer\")\n        symbol = FirValueParameterSymbol()\n        containingDeclarationSymbol = functionSymbol\n      }\n      annotations += buildSimpleAnnotationCall(ClassIds.BINDS, functionSymbol, session)\n      annotations += buildSimpleAnnotationCall(ClassIds.INTO_MAP, functionSymbol, session)\n      annotations += buildRendererKeyAnnotation(owner, modelClass, functionSymbol)\n    }\n  }\n\n  private fun buildProvideRendererKeyFunction(\n    graphClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    modelClass: ResolvedModelClass,\n  ): FirFunction {\n    val functionName =\n      \"provide${ContributesRendererIds.generatedSafeClassNamePrefix(owner.classId)}\" +\n        ContributesRendererIds.generatedModelClassNameSuffix(modelClass.classId) +\n        \"Key\"\n    val callableId = CallableId(graphClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRendererGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = kClassProducerOf(rendererStarType()).toFirResolvedTypeRef()\n      dispatchReceiverType = generatedGraphType(graphClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      annotations += buildSimpleAnnotationCall(ClassIds.PROVIDES, functionSymbol, session)\n      annotations += buildSimpleAnnotationCall(ClassIds.INTO_MAP, functionSymbol, session)\n      annotations += buildRendererKeyAnnotation(owner, modelClass, functionSymbol)\n      annotations += buildForScopeAnnotation(functionSymbol)\n    }\n  }\n\n  private fun generatedGraphType(graphClassId: ClassId) =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(graphClassId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n\n  private fun rendererStarType() =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(ClassIds.RENDERER),\n      arrayOf(ConeStarProjection),\n      isMarkedNullable = false,\n    )\n\n  private fun kClassProducerOf(type: ConeKotlinType) =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(ClassId(FqName(\"kotlin.reflect\"), Name.identifier(\"KClass\"))),\n      arrayOf(ConeKotlinTypeProjectionOut(type)),\n      isMarkedNullable = false,\n    )\n\n  private fun buildReflectionContributesToAnnotation() = buildAnnotation {\n    val contributesToSymbol =\n      session.symbolProvider.getClassLikeSymbolByClassId(ClassIds.CONTRIBUTES_TO)\n        as? FirRegularClassSymbol\n        ?: error(\"Annotation class ${ClassIds.CONTRIBUTES_TO} not found on the classpath\")\n    annotationTypeRef = contributesToSymbol.defaultType().toFirResolvedTypeRef()\n    argumentMapping = buildAnnotationArgumentMapping {\n      mapping[Name.identifier(\"scope\")] = buildClassExpression(ClassIds.RENDERER_SCOPE, session)\n    }\n  }\n\n  private fun buildForScopeAnnotation(containingSymbol: FirNamedFunctionSymbol) =\n    buildAnnotationCallWithArgument(\n      classId = ClassIds.FOR_SCOPE,\n      argName = Name.identifier(\"scope\"),\n      argument = buildClassExpression(ClassIds.RENDERER_SCOPE, session),\n      containingSymbol = containingSymbol,\n      session = session,\n    )\n\n  private fun buildOriginAnnotation(\n    owner: FirRegularClassSymbol,\n    containingSymbol: FirRegularClassSymbol,\n  ) =\n    buildAnnotationCallWithArgument(\n      classId = ClassIds.ORIGIN,\n      argName = Name.identifier(\"value\"),\n      argument = buildClassExpression(owner, session),\n      containingSymbol = containingSymbol,\n      session = session,\n    )\n\n  private fun buildRendererKeyAnnotation(\n    owner: FirRegularClassSymbol,\n    modelClass: ResolvedModelClass,\n    containingSymbol: FirNamedFunctionSymbol,\n  ) =\n    buildAnnotationCallWithArgument(\n      classId = ClassIds.RENDERER_KEY,\n      argName = Name.identifier(\"value\"),\n      argument =\n        modelClass.classSymbol?.let { buildClassExpression(it, session) }\n          ?: buildClassExpression(modelClass.classId, session, owner),\n      containingSymbol = containingSymbol,\n      session = session,\n    )\n\n  @AutoService(MetroFirDeclarationGenerationExtension.Factory::class)\n  public class Factory : MetroFirDeclarationGenerationExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroFirDeclarationGenerationExtension = ContributesRendererFir(session)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererIds.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\n\ninternal object ContributesRendererIds {\n  val CONTRIBUTES_RENDERER_CLASS_ID = ClassIds.CONTRIBUTES_RENDERER\n  val CONTRIBUTES_RENDERER_FQ_NAME = ClassIds.CONTRIBUTES_RENDERER.asSingleFqName()\n  val NESTED_INTERFACE_NAME: Name = Name.identifier(\"RendererContribution\")\n\n  val PREDICATE = LookupPredicate.create { annotated(CONTRIBUTES_RENDERER_FQ_NAME) }\n\n  fun generatedSafeClassNamePrefix(contributingClassId: ClassId): String {\n    return (contributingClassId.packageFqName.pathSegments() +\n        contributingClassId.relativeClassName.pathSegments())\n      .joinToString(separator = \"\") { it.asString().replaceFirstChar(Char::uppercase) }\n  }\n\n  fun generatedModelClassNameSuffix(modelClassId: ClassId): String {\n    return modelClassId.relativeClassName.pathSegments().joinToString(separator = \"\") {\n      it.asString()\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererIrExtension.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension\nimport org.jetbrains.kotlin.backend.common.extensions.IrPluginContext\nimport org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder\nimport org.jetbrains.kotlin.ir.IrStatement\nimport org.jetbrains.kotlin.ir.UNDEFINED_OFFSET\nimport org.jetbrains.kotlin.ir.builders.irBlockBody\nimport org.jetbrains.kotlin.ir.builders.irCallConstructor\nimport org.jetbrains.kotlin.ir.builders.irReturn\nimport org.jetbrains.kotlin.ir.declarations.IrClass\nimport org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin\nimport org.jetbrains.kotlin.ir.declarations.IrModuleFragment\nimport org.jetbrains.kotlin.ir.declarations.IrSimpleFunction\nimport org.jetbrains.kotlin.ir.expressions.IrClassReference\nimport org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl\nimport org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI\nimport org.jetbrains.kotlin.ir.types.IrSimpleType\nimport org.jetbrains.kotlin.ir.types.classOrNull\nimport org.jetbrains.kotlin.ir.types.defaultType\nimport org.jetbrains.kotlin.ir.types.typeWith\nimport org.jetbrains.kotlin.ir.util.constructors\nimport org.jetbrains.kotlin.ir.util.parentAsClass\nimport org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid\nimport org.jetbrains.kotlin.ir.visitors.transformChildrenVoid\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.Keys\n\n/**\n * Fills in the bodies for FIR-generated nested `@ContributesRenderer` graph functions.\n *\n * Pseudo Kotlin for `TestRenderer.RendererContribution`:\n * ```kotlin\n * @ContributesRenderer\n * class TestRenderer : Renderer<Model> {\n *\n *   @ContributesTo(RendererScope::class)\n *   @Origin(TestRenderer::class)\n *   interface RendererContribution {\n *     @Provides\n *     fun provideTestRenderer(): TestRenderer = TestRenderer()\n *\n *     @Binds\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     fun provideTestRendererModel(renderer: TestRenderer): Renderer<*>\n *\n *     @Provides\n *     @IntoMap\n *     @RendererKey(Model::class)\n *     @ForScope(RendererScope::class)\n *     fun provideTestRendererModelKey(): KClass<out Renderer<*>> = TestRenderer::class\n *   }\n * }\n * ```\n *\n * The direct constructor call is only generated for zero-arg renderers. The `@IntoMap` renderer\n * binding stays abstract and is handled by Metro's normal `@Binds` support.\n */\n@Suppress(\"DEPRECATION\")\ninternal class ContributesRendererIrExtension : IrGenerationExtension {\n  override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {\n    moduleFragment.transformChildrenVoid(ContributesRendererIrTransformer(pluginContext))\n  }\n}\n\n@Suppress(\"DEPRECATION\")\n@OptIn(UnsafeDuringIrConstructionAPI::class)\nprivate class ContributesRendererIrTransformer(private val pluginContext: IrPluginContext) :\n  IrElementTransformerVoid() {\n\n  override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {\n    val origin = declaration.origin\n    if (\n      origin !is IrDeclarationOrigin.GeneratedByPlugin ||\n        origin.pluginKey != Keys.ContributesRendererGeneratorKey\n    ) {\n      return super.visitSimpleFunction(declaration)\n    }\n    if (declaration.body != null) return super.visitSimpleFunction(declaration)\n    if (!declaration.name.asString().startsWith(\"provide\")) {\n      return super.visitSimpleFunction(declaration)\n    }\n\n    when {\n      declaration.name.asString().endsWith(\"Key\") -> generateProvideRendererKeyBody(declaration)\n      declaration.parameters.none { it.name.asString() == \"renderer\" } ->\n        generateProvideRendererBody(declaration)\n    }\n\n    return super.visitSimpleFunction(declaration)\n  }\n\n  private fun generateProvideRendererBody(declaration: IrSimpleFunction) {\n    val classSymbol = (declaration.returnType as? IrSimpleType)?.classOrNull ?: return\n    val constructor =\n      classSymbol.constructors.singleOrNull { it.owner.parameters.isEmpty() } ?: return\n    val irBuilder = irBuilderFor(declaration)\n\n    declaration.body = irBuilder.irBlockBody {\n      val constructorCall = irCallConstructor(constructor, emptyList())\n      constructorCall.startOffset = UNDEFINED_OFFSET\n      constructorCall.endOffset = UNDEFINED_OFFSET\n      +irReturn(constructorCall)\n    }\n  }\n\n  private fun generateProvideRendererKeyBody(declaration: IrSimpleFunction) {\n    val ownerClassSymbol = generatedOwnerClass(declaration)?.symbol ?: return\n    val irBuilder = irBuilderFor(declaration)\n\n    declaration.body = irBuilder.irBlockBody {\n      +irReturn(\n        IrClassReferenceImpl(\n          UNDEFINED_OFFSET,\n          UNDEFINED_OFFSET,\n          pluginContext.irBuiltIns.kClassClass.typeWith(ownerClassSymbol.defaultType),\n          ownerClassSymbol,\n          ownerClassSymbol.defaultType,\n        )\n      )\n    }\n  }\n\n  private fun generatedOwnerClass(declaration: IrSimpleFunction): IrClass? {\n    val parentClass = declaration.parent as? IrClass ?: return null\n    val originAnnotation =\n      parentClass.annotations.firstOrNull { annotation ->\n        annotation.symbol.owner.parentAsClass.name == ClassIds.ORIGIN.shortClassName\n      } ?: return null\n    val classReference = originAnnotation.arguments[0] as? IrClassReference ?: return null\n    return classReference.classType.classOrNull?.owner\n  }\n\n  private fun irBuilderFor(declaration: IrSimpleFunction) =\n    DeclarationIrBuilder(pluginContext, declaration.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererMetroExtension.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroContributionExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport dev.zacsweers.metro.compiler.fir.MetroFirTypeResolver\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.scopes.getSingleClassifier\nimport org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.name.ClassId\nimport software.amazon.app.platform.metro.compiler.ClassIds\n\npublic class ContributesRendererMetroExtension(private val session: FirSession) :\n  MetroContributionExtension {\n\n  private val predicate = ContributesRendererIds.PREDICATE\n\n  private val annotatedClasses by lazy {\n    session.predicateBasedProvider\n      .getSymbolsByPredicate(predicate)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(predicate)\n  }\n\n  override fun getContributions(\n    scopeClassId: ClassId,\n    typeResolverFactory: MetroFirTypeResolver.Factory,\n  ): List<MetroContributionExtension.Contribution> {\n    if (scopeClassId != ClassIds.RENDERER_SCOPE) return emptyList()\n\n    return annotatedClasses.mapNotNull { parentSymbol ->\n      val contributionInterfaceClassId =\n        parentSymbol.classId.createNestedClassId(ContributesRendererIds.NESTED_INTERFACE_NAME)\n      val contributionSymbol =\n        session.symbolProvider.getClassLikeSymbolByClassId(contributionInterfaceClassId)\n          as? FirRegularClassSymbol ?: return@mapNotNull null\n      val scope = contributionSymbol.declaredMemberScope(session, memberRequiredPhase = null)\n      val metroContributionName =\n        scope.getClassifierNames().firstOrNull { it.identifier.startsWith(\"MetroContributionTo\") }\n          ?: return@mapNotNull null\n      val metroContributionSymbol =\n        scope.getSingleClassifier(metroContributionName) as? FirRegularClassSymbol\n          ?: return@mapNotNull null\n\n      MetroContributionExtension.Contribution(\n        supertype = metroContributionSymbol.defaultType(),\n        replaces = emptyList(),\n        originClassId = parentSymbol.classId,\n      )\n    }\n  }\n\n  @AutoService(MetroContributionExtension.Factory::class)\n  public class Factory : MetroContributionExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroContributionExtension {\n      return ContributesRendererMetroExtension(session)\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/renderer/ContributesRendererSupport.kt",
    "content": "package software.amazon.app.platform.metro.compiler.renderer\n\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess\nimport org.jetbrains.kotlin.fir.declarations.FirDeclaration\nimport org.jetbrains.kotlin.fir.declarations.FirFile\nimport org.jetbrains.kotlin.fir.declarations.FirRegularClass\nimport org.jetbrains.kotlin.fir.declarations.getSealedClassInheritors\nimport org.jetbrains.kotlin.fir.declarations.utils.isSealed\nimport org.jetbrains.kotlin.fir.expressions.FirAnnotationCall\nimport org.jetbrains.kotlin.fir.expressions.FirLiteralExpression\nimport org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.resolve.fullyExpandedType\nimport org.jetbrains.kotlin.fir.resolve.providers.firProvider\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.resolve.toRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.SymbolInternals\nimport org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjection\nimport org.jetbrains.kotlin.fir.types.classId\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.fir.allSessions\nimport software.amazon.app.platform.metro.compiler.fir.findAnnotation\nimport software.amazon.app.platform.metro.compiler.fir.findClassLikeSymbolInContainingFile\nimport software.amazon.app.platform.metro.compiler.fir.findClassLikeSymbolInPackageFiles\nimport software.amazon.app.platform.metro.compiler.fir.findContainingFile\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\nimport software.amazon.app.platform.metro.compiler.fir.resolveClassIdArgument\nimport software.amazon.app.platform.metro.compiler.fir.resolveClassReferenceArgument\nimport software.amazon.app.platform.metro.compiler.fir.resolveDeclaredSuperTypes\nimport software.amazon.app.platform.metro.compiler.fir.unwrapArgumentExpression\n\ninternal data class ResolvedModelClass(\n  val classId: ClassId,\n  val classSymbol: FirRegularClassSymbol?,\n)\n\ninternal data class RendererContributionMetadata(\n  val hasInjectAnnotation: Boolean,\n  val modelClasses: List<ResolvedModelClass>,\n)\n\ninternal sealed interface RendererModelTypeResolution {\n  data class Success(val modelClass: ResolvedModelClass) : RendererModelTypeResolution\n\n  data class Error(val message: String) : RendererModelTypeResolution\n}\n\ninternal fun rendererContributionMetadata(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): RendererContributionMetadata? {\n  val modelType =\n    resolveRendererModelType(classSymbol, session) as? RendererModelTypeResolution.Success\n      ?: return null\n  val includeSealedSubtypes = contributesRendererIncludeSealedSubtypes(classSymbol, session)\n\n  return RendererContributionMetadata(\n    hasInjectAnnotation = hasAnnotation(classSymbol, ClassIds.INJECT, session),\n    modelClasses =\n      if (includeSealedSubtypes) {\n        collectModelClasses(modelType.modelClass, session)\n      } else {\n        listOf(modelType.modelClass)\n      },\n  )\n}\n\ninternal fun resolveRendererModelType(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): RendererModelTypeResolution {\n  explicitRendererModelType(classSymbol, session)?.let {\n    return RendererModelTypeResolution.Success(it)\n  }\n\n  val implicitModelTypes = implicitRendererModelTypes(classSymbol, session)\n  return if (implicitModelTypes.size == 1) {\n    RendererModelTypeResolution.Success(implicitModelTypes.single())\n  } else {\n    RendererModelTypeResolution.Error(\n      buildString {\n        append(\n          \"Couldn't find BaseModel type for ${classSymbol.name.asString()}. Consider adding \" +\n            \"an explicit parameter.\"\n        )\n        if (implicitModelTypes.size > 1) {\n          append(\"Found: \")\n          append(implicitModelTypes.joinToString { it.classId.asSingleFqName().asString() })\n        }\n      }\n    )\n  }\n}\n\ninternal fun isSingleInRendererScope(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): Boolean {\n  val annotation =\n    findAnnotation(classSymbol, ClassIds.SINGLE_IN, session) as? FirAnnotationCall ?: return false\n  val rawScopeArgument =\n    annotation.argumentMapping.mapping[Name.identifier(\"scope\")]\n      ?: annotation.argumentList.arguments.firstOrNull()\n      ?: return false\n  return resolveClassIdArgument(rawScopeArgument, classSymbol, session) == ClassIds.RENDERER_SCOPE\n}\n\n@OptIn(DirectDeclarationsAccess::class, SymbolInternals::class)\ninternal fun constructorParameterCount(classSymbol: FirRegularClassSymbol): Int {\n  val constructorSymbol =\n    classSymbol.declarationSymbols.filterIsInstance<FirConstructorSymbol>().firstOrNull {\n      it.isPrimary\n    } ?: classSymbol.declarationSymbols.filterIsInstance<FirConstructorSymbol>().firstOrNull()\n  return constructorSymbol?.fir?.valueParameters?.size ?: 0\n}\n\nprivate fun explicitRendererModelType(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): ResolvedModelClass? {\n  val annotation =\n    findAnnotation(classSymbol, ContributesRendererIds.CONTRIBUTES_RENDERER_CLASS_ID, session)\n      as? FirAnnotationCall ?: return null\n  val rawModelTypeArgument =\n    annotation.argumentMapping.mapping[Name.identifier(\"modelType\")]\n      ?: annotation.argumentList.arguments.firstOrNull()\n      ?: return null\n\n  return resolveClassReferenceArgument(rawModelTypeArgument, classSymbol, session)\n    ?.takeIf { it.classId != ClassIds.UNIT }\n    ?.let {\n      val resolvedClassSymbol =\n        it.classSymbol\n          ?: (session.symbolProvider.getClassLikeSymbolByClassId(it.classId)\n            as? FirRegularClassSymbol)\n          ?: findClassLikeSymbolInContainingFile(classSymbol, it.classId, session)\n          ?: findClassLikeSymbolInPackageFiles(\n            classSymbol.classId.packageFqName,\n            it.classId,\n            session,\n          )\n      ResolvedModelClass(classId = it.classId, classSymbol = resolvedClassSymbol)\n    }\n}\n\nprivate fun contributesRendererIncludeSealedSubtypes(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): Boolean {\n  val annotation =\n    findAnnotation(classSymbol, ContributesRendererIds.CONTRIBUTES_RENDERER_CLASS_ID, session)\n      as? FirAnnotationCall ?: return true\n  val rawArgument =\n    annotation.argumentMapping.mapping[Name.identifier(\"includeSealedSubtypes\")]\n      ?: annotation.argumentList.arguments.firstOrNull { argument ->\n        (argument as? FirNamedArgumentExpression)?.name == Name.identifier(\"includeSealedSubtypes\")\n      }\n  val expression = rawArgument?.let(::unwrapArgumentExpression) ?: return true\n  return (expression as? FirLiteralExpression)?.value as? Boolean ?: true\n}\n\nprivate fun implicitRendererModelTypes(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): List<ResolvedModelClass> {\n  val collected = linkedMapOf<ClassId, ResolvedModelClass>()\n  val visited = mutableSetOf<ConeKotlinType>()\n  val queue = ArrayDeque<ConeKotlinType>()\n\n  queue += resolveDeclaredSuperTypes(classSymbol, session)\n\n  while (queue.isNotEmpty()) {\n    val type = queue.removeFirst().fullyExpandedType(session)\n    if (!visited.add(type)) continue\n\n    collectImplicitModelTypes(type, session).forEach { modelClass ->\n      collected.putIfAbsent(modelClass.classId, modelClass)\n    }\n\n    val typeSymbol = type.toRegularClassSymbol(session) ?: continue\n    queue += resolveDeclaredSuperTypes(typeSymbol, session, actualType = type)\n  }\n\n  return collected.values.toList()\n}\n\nprivate fun collectImplicitModelTypes(\n  type: ConeKotlinType,\n  session: FirSession,\n): List<ResolvedModelClass> {\n  return type.typeArguments\n    .asSequence()\n    .mapNotNull { projection ->\n      val candidateType =\n        (projection as? ConeKotlinTypeProjection)?.type?.fullyExpandedType(session)\n          ?: return@mapNotNull null\n      val candidateSymbol = candidateType.toRegularClassSymbol(session)\n      if (candidateSymbol == null || candidateSymbol.classId == ClassIds.BASE_MODEL) {\n        return@mapNotNull null\n      }\n      if (!isBaseModelSubtype(candidateType, session)) return@mapNotNull null\n      ResolvedModelClass(candidateSymbol.classId, candidateSymbol)\n    }\n    .toList()\n}\n\nprivate fun isBaseModelSubtype(\n  type: ConeKotlinType,\n  session: FirSession,\n  visited: MutableSet<ConeKotlinType> = mutableSetOf(),\n): Boolean {\n  val expandedType = type.fullyExpandedType(session)\n  if (!visited.add(expandedType)) return false\n\n  val classSymbol = expandedType.toRegularClassSymbol(session) ?: return false\n  if (classSymbol.classId == ClassIds.BASE_MODEL) return true\n\n  return resolveDeclaredSuperTypes(classSymbol, session, actualType = expandedType).any {\n    isBaseModelSubtype(it, session, visited)\n  }\n}\n\n@OptIn(SymbolInternals::class)\nprivate fun collectModelClasses(\n  rootModelClass: ResolvedModelClass,\n  session: FirSession,\n): List<ResolvedModelClass> {\n  val collected = linkedMapOf<ClassId, ResolvedModelClass>()\n  val queue = ArrayDeque<ResolvedModelClass>()\n  queue += rootModelClass\n\n  while (queue.isNotEmpty()) {\n    val modelClass = queue.removeFirst()\n    if (collected.putIfAbsent(modelClass.classId, modelClass) != null) continue\n\n    val classSymbol =\n      modelClass.classSymbol\n        ?: (session.symbolProvider.getClassLikeSymbolByClassId(modelClass.classId)\n          as? FirRegularClassSymbol)\n        ?: continue\n    val sealedInheritors = findDirectSealedInheritors(classSymbol, session)\n    if (sealedInheritors.isEmpty()) continue\n\n    for ((classId, symbol) in sealedInheritors) {\n      queue += ResolvedModelClass(classId = classId, classSymbol = symbol)\n    }\n  }\n\n  return collected.values.toList()\n}\n\n@OptIn(SymbolInternals::class)\nprivate fun findDirectSealedInheritors(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): List<Pair<ClassId, FirRegularClassSymbol?>> {\n  if (!classSymbol.fir.isSealed) return emptyList()\n\n  val collected = linkedMapOf<ClassId, FirRegularClassSymbol?>()\n\n  findDirectSealedInheritorsFromMetadata(classSymbol, session).forEach { (classId, symbol) ->\n    collected.putIfAbsent(classId, symbol)\n  }\n  findDirectSealedInheritorsInSource(classSymbol, session).forEach { symbol ->\n    collected[symbol.classId] = collected[symbol.classId] ?: symbol\n  }\n\n  return collected.map { (classId, symbol) -> classId to symbol }\n}\n\n@OptIn(SymbolInternals::class)\nprivate fun findDirectSealedInheritorsFromMetadata(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): List<Pair<ClassId, FirRegularClassSymbol?>> {\n  val ownerSession = classSymbol.fir.moduleData.session\n  return classSymbol.fir.getSealedClassInheritors(ownerSession).distinct().map { classId ->\n    classId to\n      ((ownerSession.symbolProvider.getClassLikeSymbolByClassId(classId)\n        ?: session.symbolProvider.getClassLikeSymbolByClassId(classId))\n        as? FirRegularClassSymbol)\n  }\n}\n\n@OptIn(DirectDeclarationsAccess::class, SymbolInternals::class)\nprivate fun findDirectSealedInheritorsInSource(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): List<FirRegularClassSymbol> {\n  val visitedFiles = linkedSetOf<FirFile>()\n  val candidates = linkedMapOf<ClassId, FirRegularClassSymbol>()\n\n  fun visitFile(file: FirFile) {\n    if (!visitedFiles.add(file)) return\n\n    collectRegularClasses(file.declarations).forEach { candidate ->\n      if (candidate.classId == classSymbol.classId) return@forEach\n      val hasDirectSupertype =\n        resolveDeclaredSuperTypes(candidate, session).any { superType ->\n          superType.toRegularClassSymbol(session)?.classId == classSymbol.classId\n        }\n      if (hasDirectSupertype) {\n        candidates.putIfAbsent(candidate.classId, candidate)\n      }\n    }\n  }\n\n  findContainingFile(classSymbol, session)?.let(::visitFile)\n  allSessions(session).forEach { candidateSession ->\n    candidateSession.firProvider\n      .getFirFilesByPackage(classSymbol.classId.packageFqName)\n      .forEach(::visitFile)\n  }\n\n  return candidates.values.toList()\n}\n\n@OptIn(DirectDeclarationsAccess::class)\nprivate fun collectRegularClasses(\n  declarations: List<FirDeclaration>\n): Sequence<FirRegularClassSymbol> {\n  return declarations.asSequence().flatMap { declaration ->\n    val regularClass = declaration as? FirRegularClass ?: return@flatMap emptySequence()\n    sequenceOf(regularClass.symbol) + collectRegularClasses(regularClass.declarations)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/robot/ContributesRobotChecker.kt",
    "content": "package software.amazon.app.platform.metro.compiler.robot\n\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.diagnostics.DiagnosticReporter\nimport org.jetbrains.kotlin.diagnostics.reportOn\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind\nimport org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext\nimport org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker\nimport org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess\nimport org.jetbrains.kotlin.fir.declarations.FirClass\nimport org.jetbrains.kotlin.fir.declarations.FirConstructor\nimport org.jetbrains.kotlin.fir.declarations.toAnnotationClassId\nimport org.jetbrains.kotlin.fir.declarations.toAnnotationClassIdSafe\nimport org.jetbrains.kotlin.fir.resolve.fullyExpandedType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.types.coneType\nimport org.jetbrains.kotlin.name.ClassId\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.fir.AppPlatformMetroExtensionsDiagnostics\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeClassId\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\nimport software.amazon.app.platform.metro.compiler.fir.hasTransitiveSupertype\n\ninternal object ContributesRobotChecker : FirClassChecker(MppCheckerKind.Common) {\n\n  context(context: CheckerContext, reporter: DiagnosticReporter)\n  override fun check(declaration: FirClass) {\n    declaration.source ?: return\n    val session = context.session\n\n    val annotation =\n      declaration.annotations.firstOrNull { candidate ->\n        candidate.toAnnotationClassId(session) == ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID\n      } ?: return\n\n    val classSymbol = declaration.symbol as? FirRegularClassSymbol ?: return\n\n    if (declaration.classKind != ClassKind.CLASS) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"@ContributesRobot can only be applied to classes, not \" +\n          \"${declaration.classKind.name.lowercase().replace('_', ' ')}s.\",\n      )\n      return\n    }\n\n    if (!implementsRobot(declaration, session)) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"In order to use @ContributesRobot, ${classSymbol.name.asString()} must implement \" +\n          \"${ClassIds.ROBOT.asSingleFqName()}.\",\n      )\n    }\n\n    if (\n      requiresInjectAnnotation(declaration) && !hasAnnotation(classSymbol, ClassIds.INJECT, session)\n    ) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"${classSymbol.name.asString()} must be annotated with @Inject when injecting arguments \" +\n          \"into a robot.\",\n      )\n    }\n\n    val singletonAnnotation =\n      declaration.annotations.firstOrNull { candidate ->\n        isMetroScopeAnnotation(candidate.toAnnotationClassIdSafe(session), session)\n      }\n    if (singletonAnnotation != null) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"It's not allowed for a robot to be a singleton, because the lifetime of the \" +\n          \"robot is scoped to the robot() factory function. Remove the @\" +\n          singletonAnnotation.toAnnotationClassIdSafe(session)?.shortClassName?.asString() +\n          \" annotation.\",\n      )\n    }\n\n    val scopeClassId =\n      extractScopeClassId(classSymbol, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)\n    if (scopeClassId != null && scopeClassId != ClassIds.APP_SCOPE) {\n      reporter.reportOn(\n        annotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_ROBOT_ERROR,\n        \"Robots can only be contributed to the AppScope for now. Scope \" +\n          \"${scopeClassId.asSingleFqName()} is unsupported.\",\n      )\n    }\n  }\n\n  private fun implementsRobot(declaration: FirClass, session: FirSession): Boolean {\n    return declaration.superTypeRefs.any { superTypeRef ->\n      val coneType = superTypeRef.coneType.fullyExpandedType(session)\n      hasTransitiveSupertype(coneType, session, ClassIds.ROBOT_FQ_NAMES)\n    }\n  }\n\n  @OptIn(DirectDeclarationsAccess::class)\n  private fun requiresInjectAnnotation(declaration: FirClass): Boolean {\n    val constructor =\n      declaration.declarations.filterIsInstance<FirConstructor>().firstOrNull { it.isPrimary }\n        ?: declaration.declarations.filterIsInstance<FirConstructor>().firstOrNull()\n    return constructor?.valueParameters?.isNotEmpty() == true\n  }\n\n  private fun isMetroScopeAnnotation(annotationClassId: ClassId?, session: FirSession): Boolean {\n    val resolvedClassId = annotationClassId ?: return false\n    val annotationSymbol =\n      session.symbolProvider.getClassLikeSymbolByClassId(resolvedClassId) as? FirClassSymbol<*>\n        ?: return false\n    return annotationSymbol.resolvedCompilerAnnotationsWithClassIds.any {\n      it.toAnnotationClassIdSafe(session) == ClassIds.SCOPE\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/robot/ContributesRobotFir.kt",
    "content": "package software.amazon.app.platform.metro.compiler.robot\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroFirDeclarationGenerationExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.descriptors.Modality\nimport org.jetbrains.kotlin.descriptors.Visibilities\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.FirFunction\nimport org.jetbrains.kotlin.fir.declarations.FirResolvePhase\nimport org.jetbrains.kotlin.fir.declarations.builder.buildNamedFunction\nimport org.jetbrains.kotlin.fir.declarations.builder.buildRegularClass\nimport org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter\nimport org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl\nimport org.jetbrains.kotlin.fir.declarations.origin\nimport org.jetbrains.kotlin.fir.expressions.builder.buildAnnotation\nimport org.jetbrains.kotlin.fir.expressions.builder.buildAnnotationArgumentMapping\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider\nimport org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol\nimport org.jetbrains.kotlin.fir.toEffectiveVisibility\nimport org.jetbrains.kotlin.fir.toFirResolvedTypeRef\nimport org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl\nimport org.jetbrains.kotlin.name.CallableId\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.Keys\nimport software.amazon.app.platform.metro.compiler.fir.buildAnnotationCallWithArgument\nimport software.amazon.app.platform.metro.compiler.fir.buildClassExpression\nimport software.amazon.app.platform.metro.compiler.fir.buildSimpleAnnotationCall\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeArgument\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeClassId\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\n\n/**\n * Generates the declaration shape for `@ContributesRobot` classes.\n *\n * Pseudo Kotlin:\n * ```kotlin\n * @ContributesRobot(AppScope::class)\n * class TestRobot : Robot {\n *\n *   @ContributesTo(AppScope::class)\n *   interface RobotContribution {\n *     @Provides\n *     fun provideTestRobot(): TestRobot\n *\n *     @Binds\n *     @IntoMap\n *     @RobotKey(TestRobot::class)\n *     fun provideTestRobotIntoMap(robot: TestRobot): Robot\n *   }\n * }\n * ```\n *\n * No top-level graph interface is generated. If the robot class is already `@Inject`-constructible,\n * the nested declaration omits `provideTestRobot()` and only synthesizes the map-binding method.\n */\npublic class ContributesRobotFir(session: FirSession) :\n  MetroFirDeclarationGenerationExtension(session) {\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(ContributesRobotIds.PREDICATE)\n  }\n\n  override fun getContributionHints(): List<ContributionHint> {\n    return annotatedRobotClasses().mapNotNull { classSymbol ->\n      val scopeClassId =\n        extractScopeClassId(classSymbol, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)\n          ?: return@mapNotNull null\n      ContributionHint(\n        contributingClassId =\n          classSymbol.classId.createNestedClassId(ContributesRobotIds.NESTED_INTERFACE_NAME),\n        scope = scopeClassId,\n      )\n    }\n  }\n\n  override fun getNestedClassifiersNames(\n    classSymbol: FirClassSymbol<*>,\n    context: NestedClassGenerationContext,\n  ): Set<Name> {\n    return if (\n      hasAnnotation(classSymbol, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)\n    ) {\n      setOf(ContributesRobotIds.NESTED_INTERFACE_NAME)\n    } else {\n      emptySet()\n    }\n  }\n\n  override fun generateNestedClassLikeDeclaration(\n    owner: FirClassSymbol<*>,\n    name: Name,\n    context: NestedClassGenerationContext,\n  ): FirClassLikeSymbol<*>? {\n    if (name != ContributesRobotIds.NESTED_INTERFACE_NAME) return null\n    if (!hasAnnotation(owner, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)) return null\n\n    val scopeArg =\n      extractScopeArgument(owner, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)\n        ?: return null\n    val nestedClassId = owner.classId.createNestedClassId(name)\n    val classSymbol = FirRegularClassSymbol(nestedClassId)\n\n    buildRegularClass {\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRobotGeneratorKey.origin\n      source = owner.source\n      classKind = ClassKind.INTERFACE\n      scopeProvider = session.kotlinScopeProvider\n      this.name = nestedClassId.shortClassName\n      symbol = classSymbol\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.ABSTRACT,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      superTypeRefs += session.builtinTypes.anyType\n      annotations +=\n        buildAnnotationCallWithArgument(\n          ClassIds.CONTRIBUTES_TO,\n          Name.identifier(\"scope\"),\n          scopeArg,\n          classSymbol,\n          session,\n        )\n      annotations += buildOriginAnnotation(owner)\n      for (function in buildProvidesFunctions(nestedClassId, owner)) {\n        declarations += function\n      }\n    }\n\n    return classSymbol\n  }\n\n  private fun annotatedRobotClasses(): List<FirRegularClassSymbol> {\n    return session.predicateBasedProvider\n      .getSymbolsByPredicate(ContributesRobotIds.PREDICATE)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  private fun buildProvidesFunctions(\n    graphClassId: ClassId,\n    owner: FirClassSymbol<*>,\n  ): List<FirFunction> {\n    val functions = mutableListOf<FirFunction>()\n    if (!hasAnnotation(owner, ClassIds.INJECT, session)) {\n      functions += buildProvideRobotFunction(graphClassId, owner)\n    }\n    functions += buildProvideRobotIntoMapFunction(graphClassId, owner)\n    return functions\n  }\n\n  private fun buildProvideRobotFunction(\n    graphClassId: ClassId,\n    owner: FirClassSymbol<*>,\n  ): FirFunction {\n    val functionName = \"provide${ContributesRobotIds.generatedClassNamePrefix(owner.classId)}\"\n    val callableId = CallableId(graphClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRobotGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = owner.defaultType().toFirResolvedTypeRef()\n      dispatchReceiverType = generatedGraphType(graphClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      annotations += buildSimpleAnnotationCall(ClassIds.PROVIDES, functionSymbol, session)\n    }\n  }\n\n  private fun buildProvideRobotIntoMapFunction(\n    graphClassId: ClassId,\n    owner: FirClassSymbol<*>,\n  ): FirFunction {\n    val functionName =\n      \"provide${ContributesRobotIds.generatedClassNamePrefix(owner.classId)}IntoMap\"\n    val callableId = CallableId(graphClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n    val ownerType = owner.defaultType()\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesRobotGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = robotType().toFirResolvedTypeRef()\n      dispatchReceiverType = generatedGraphType(graphClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      valueParameters += buildValueParameter {\n        resolvePhase = FirResolvePhase.BODY_RESOLVE\n        moduleData = session.moduleData\n        origin = Keys.ContributesRobotGeneratorKey.origin\n        source = owner.source\n        returnTypeRef = ownerType.toFirResolvedTypeRef()\n        name = Name.identifier(\"robot\")\n        symbol = FirValueParameterSymbol()\n        containingDeclarationSymbol = functionSymbol\n      }\n      annotations += buildSimpleAnnotationCall(ClassIds.BINDS, functionSymbol, session)\n      annotations += buildSimpleAnnotationCall(ClassIds.INTO_MAP, functionSymbol, session)\n      annotations += buildRobotKeyAnnotation(owner.classId)\n    }\n  }\n\n  private fun generatedGraphType(graphClassId: ClassId) =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(graphClassId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n\n  private fun robotType() =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(ClassIds.ROBOT),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n\n  private fun buildOriginAnnotation(owner: FirClassSymbol<*>) = buildAnnotation {\n    val originSymbol =\n      session.symbolProvider.getClassLikeSymbolByClassId(ClassIds.ORIGIN) as? FirRegularClassSymbol\n        ?: error(\"Annotation class ${ClassIds.ORIGIN} not found on the classpath\")\n    annotationTypeRef = originSymbol.defaultType().toFirResolvedTypeRef()\n    argumentMapping = buildAnnotationArgumentMapping {\n      mapping[Name.identifier(\"value\")] = buildClassExpression(owner, session)\n    }\n  }\n\n  private fun buildRobotKeyAnnotation(robotClassId: ClassId) = buildAnnotation {\n    val robotKeySymbol =\n      session.symbolProvider.getClassLikeSymbolByClassId(ClassIds.ROBOT_KEY)\n        as? FirRegularClassSymbol\n        ?: error(\"Annotation class ${ClassIds.ROBOT_KEY} not found on the classpath\")\n    annotationTypeRef = robotKeySymbol.defaultType().toFirResolvedTypeRef()\n    argumentMapping = buildAnnotationArgumentMapping {\n      mapping[Name.identifier(\"value\")] = buildClassExpression(robotClassId, session)\n    }\n  }\n\n  @AutoService(MetroFirDeclarationGenerationExtension.Factory::class)\n  public class Factory : MetroFirDeclarationGenerationExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroFirDeclarationGenerationExtension = ContributesRobotFir(session)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/robot/ContributesRobotIds.kt",
    "content": "package software.amazon.app.platform.metro.compiler.robot\n\nimport org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.FqName\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\n\ninternal object ContributesRobotIds {\n  val CONTRIBUTES_ROBOT_CLASS_ID = ClassIds.CONTRIBUTES_ROBOT\n  val CONTRIBUTES_ROBOT_FQ_NAME =\n    FqName(\"software.amazon.app.platform.inject.robot.ContributesRobot\")\n  val NESTED_INTERFACE_NAME: Name = Name.identifier(\"RobotContribution\")\n\n  val PREDICATE = LookupPredicate.create { annotated(CONTRIBUTES_ROBOT_FQ_NAME) }\n\n  fun generatedClassNamePrefix(contributingClassId: ClassId): String {\n    return contributingClassId.relativeClassName.pathSegments().joinToString(separator = \"\") {\n      it.asString()\n    }\n  }\n\n  fun generatedRobotPropertyName(contributingClassId: ClassId): Name {\n    return Name.identifier(\n      generatedClassNamePrefix(contributingClassId).replaceFirstChar { char -> char.lowercase() }\n    )\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/robot/ContributesRobotIrExtension.kt",
    "content": "package software.amazon.app.platform.metro.compiler.robot\n\nimport org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension\nimport org.jetbrains.kotlin.backend.common.extensions.IrPluginContext\nimport org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder\nimport org.jetbrains.kotlin.ir.IrStatement\nimport org.jetbrains.kotlin.ir.UNDEFINED_OFFSET\nimport org.jetbrains.kotlin.ir.builders.irBlockBody\nimport org.jetbrains.kotlin.ir.builders.irCallConstructor\nimport org.jetbrains.kotlin.ir.builders.irReturn\nimport org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin\nimport org.jetbrains.kotlin.ir.declarations.IrModuleFragment\nimport org.jetbrains.kotlin.ir.declarations.IrSimpleFunction\nimport org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI\nimport org.jetbrains.kotlin.ir.types.IrSimpleType\nimport org.jetbrains.kotlin.ir.types.classOrNull\nimport org.jetbrains.kotlin.ir.util.constructors\nimport org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid\nimport org.jetbrains.kotlin.ir.visitors.transformChildrenVoid\nimport software.amazon.app.platform.metro.compiler.Keys\n\n/**\n * Fills in the bodies for FIR-generated nested `@ContributesRobot` provider functions.\n *\n * Pseudo Kotlin for `TestRobot.RobotContribution`:\n * ```kotlin\n * @ContributesRobot(AppScope::class)\n * class TestRobot : Robot {\n *\n *   @ContributesTo(AppScope::class)\n *   interface RobotContribution {\n *     @Provides\n *     fun provideTestRobot(): TestRobot = TestRobot()\n *\n *     @Binds\n *     @IntoMap\n *     @RobotKey(TestRobot::class)\n *     fun provideTestRobotIntoMap(robot: TestRobot): Robot\n *   }\n * }\n * ```\n *\n * The direct constructor call is only generated for zero-arg robots. The `@IntoMap` binding stays\n * abstract and is handled by Metro's normal `@Binds` support.\n */\n@Suppress(\"DEPRECATION\")\ninternal class ContributesRobotIrExtension : IrGenerationExtension {\n  override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {\n    moduleFragment.transformChildrenVoid(ContributesRobotIrTransformer(pluginContext))\n  }\n}\n\n@Suppress(\"DEPRECATION\")\n@OptIn(UnsafeDuringIrConstructionAPI::class)\nprivate class ContributesRobotIrTransformer(private val pluginContext: IrPluginContext) :\n  IrElementTransformerVoid() {\n\n  override fun visitSimpleFunction(declaration: IrSimpleFunction): IrStatement {\n    val origin = declaration.origin\n    if (\n      origin !is IrDeclarationOrigin.GeneratedByPlugin ||\n        origin.pluginKey != Keys.ContributesRobotGeneratorKey\n    ) {\n      return super.visitSimpleFunction(declaration)\n    }\n    if (declaration.body != null) return super.visitSimpleFunction(declaration)\n    if (!declaration.name.asString().startsWith(\"provide\"))\n      return super.visitSimpleFunction(declaration)\n\n    if (!declaration.name.asString().endsWith(\"IntoMap\")) {\n      generateProvideRobotBody(declaration)\n    }\n\n    return super.visitSimpleFunction(declaration)\n  }\n\n  private fun generateProvideRobotBody(declaration: IrSimpleFunction) {\n    val classSymbol = (declaration.returnType as? IrSimpleType)?.classOrNull ?: return\n    val constructor =\n      classSymbol.constructors.singleOrNull { it.owner.parameters.isEmpty() } ?: return\n    val irBuilder = irBuilderFor(declaration)\n\n    declaration.body = irBuilder.irBlockBody {\n      val constructorCall = irCallConstructor(constructor, emptyList())\n      constructorCall.startOffset = UNDEFINED_OFFSET\n      constructorCall.endOffset = UNDEFINED_OFFSET\n      +irReturn(constructorCall)\n    }\n  }\n\n  private fun irBuilderFor(declaration: IrSimpleFunction) =\n    DeclarationIrBuilder(pluginContext, declaration.symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/robot/ContributesRobotMetroExtension.kt",
    "content": "package software.amazon.app.platform.metro.compiler.robot\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroContributionExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport dev.zacsweers.metro.compiler.fir.MetroFirTypeResolver\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.scopes.getSingleClassifier\nimport org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.name.ClassId\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeClassId\n\npublic class ContributesRobotMetroExtension(private val session: FirSession) :\n  MetroContributionExtension {\n\n  private val predicate = ContributesRobotIds.PREDICATE\n\n  private val annotatedClasses by lazy {\n    session.predicateBasedProvider\n      .getSymbolsByPredicate(predicate)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(predicate)\n  }\n\n  override fun getContributions(\n    scopeClassId: ClassId,\n    typeResolverFactory: MetroFirTypeResolver.Factory,\n  ): List<MetroContributionExtension.Contribution> {\n    return annotatedClasses.mapNotNull { parentSymbol ->\n      val annotationScopeClassId =\n        extractScopeClassId(parentSymbol, ContributesRobotIds.CONTRIBUTES_ROBOT_CLASS_ID, session)\n          ?: return@mapNotNull null\n      if (annotationScopeClassId != scopeClassId) return@mapNotNull null\n\n      val contributionInterfaceClassId =\n        parentSymbol.classId.createNestedClassId(ContributesRobotIds.NESTED_INTERFACE_NAME)\n      val contributionSymbol =\n        session.symbolProvider.getClassLikeSymbolByClassId(contributionInterfaceClassId)\n          as? FirRegularClassSymbol ?: return@mapNotNull null\n      val scope = contributionSymbol.declaredMemberScope(session, memberRequiredPhase = null)\n      val metroContributionName =\n        scope.getClassifierNames().firstOrNull { it.identifier.startsWith(\"MetroContributionTo\") }\n          ?: return@mapNotNull null\n      val metroContributionSymbol =\n        scope.getSingleClassifier(metroContributionName) as? FirRegularClassSymbol\n          ?: return@mapNotNull null\n\n      MetroContributionExtension.Contribution(\n        supertype = metroContributionSymbol.defaultType(),\n        replaces = emptyList(),\n        originClassId = parentSymbol.classId,\n      )\n    }\n  }\n\n  @AutoService(MetroContributionExtension.Factory::class)\n  public class Factory : MetroContributionExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroContributionExtension {\n      return ContributesRobotMetroExtension(session)\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/scoped/ContributesScopedChecker.kt",
    "content": "package software.amazon.app.platform.metro.compiler.scoped\n\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.diagnostics.DiagnosticReporter\nimport org.jetbrains.kotlin.diagnostics.reportOn\nimport org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind\nimport org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext\nimport org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker\nimport org.jetbrains.kotlin.fir.declarations.FirClass\nimport org.jetbrains.kotlin.fir.declarations.toAnnotationClassId\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.fir.AppPlatformMetroExtensionsDiagnostics\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\n\ninternal object ContributesScopedChecker : FirClassChecker(MppCheckerKind.Common) {\n\n  context(context: CheckerContext, reporter: DiagnosticReporter)\n  override fun check(declaration: FirClass) {\n    declaration.source ?: return\n    val session = context.session\n    val classSymbol = declaration.symbol as? FirRegularClassSymbol ?: return\n\n    val contributesScopedAnnotation =\n      declaration.annotations.firstOrNull { candidate ->\n        candidate.toAnnotationClassId(session) == ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID\n      }\n    if (contributesScopedAnnotation != null) {\n      if (declaration.classKind != ClassKind.CLASS) {\n        reporter.reportOn(\n          contributesScopedAnnotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n          \"@ContributesScoped can only be applied to classes, not \" +\n            \"${declaration.classKind.name.lowercase().replace('_', ' ')}s.\",\n        )\n        return\n      }\n\n      if (!hasAnnotation(classSymbol, ClassIds.INJECT, session)) {\n        reporter.reportOn(\n          contributesScopedAnnotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n          \"${classSymbol.name.asString()} must be annotated with @Inject when using \" +\n            \"@ContributesScoped.\",\n        )\n        return\n      }\n\n      if (!implementsScoped(classSymbol, session)) {\n        reporter.reportOn(\n          contributesScopedAnnotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n          \"In order to use @ContributesScoped, ${classSymbol.name.asString()} must implement \" +\n            \"${ClassIds.SCOPED.asSingleFqName()}.\",\n        )\n        return\n      }\n\n      if (directOtherSupertypes(classSymbol, session).size > 1) {\n        reporter.reportOn(\n          contributesScopedAnnotation.source ?: declaration.source,\n          AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n          \"In order to use @ContributesScoped, ${classSymbol.name.asString()} is allowed to \" +\n            \"have only one other super type besides Scoped.\",\n        )\n        return\n      }\n    }\n\n    val contributesBindingAnnotation =\n      declaration.annotations.firstOrNull { candidate ->\n        candidate.toAnnotationClassId(session) == ClassIds.CONTRIBUTES_BINDING\n      } ?: return\n\n    if (implementsScoped(classSymbol, session)) {\n      reporter.reportOn(\n        contributesBindingAnnotation.source ?: declaration.source,\n        AppPlatformMetroExtensionsDiagnostics.CONTRIBUTES_SCOPED_ERROR,\n        \"${classSymbol.name.asString()} implements Scoped, but uses @ContributesBinding \" +\n          \"instead of @ContributesScoped. When implementing Scoped the annotation \" +\n          \"@ContributesScoped must be used instead of @ContributesBinding to bind both super \" +\n          \"types correctly. It's not necessary to use @ContributesBinding.\",\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/scoped/ContributesScopedFir.kt",
    "content": "package software.amazon.app.platform.metro.compiler.scoped\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroFirDeclarationGenerationExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport org.jetbrains.kotlin.descriptors.ClassKind\nimport org.jetbrains.kotlin.descriptors.Modality\nimport org.jetbrains.kotlin.descriptors.Visibilities\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.declarations.FirFunction\nimport org.jetbrains.kotlin.fir.declarations.FirResolvePhase\nimport org.jetbrains.kotlin.fir.declarations.builder.buildNamedFunction\nimport org.jetbrains.kotlin.fir.declarations.builder.buildRegularClass\nimport org.jetbrains.kotlin.fir.declarations.builder.buildValueParameter\nimport org.jetbrains.kotlin.fir.declarations.impl.FirResolvedDeclarationStatusImpl\nimport org.jetbrains.kotlin.fir.declarations.origin\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.moduleData\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.scopes.kotlinScopeProvider\nimport org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol\nimport org.jetbrains.kotlin.fir.toEffectiveVisibility\nimport org.jetbrains.kotlin.fir.toFirResolvedTypeRef\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl\nimport org.jetbrains.kotlin.name.CallableId\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.Keys\nimport software.amazon.app.platform.metro.compiler.fir.buildAnnotationCallWithArgument\nimport software.amazon.app.platform.metro.compiler.fir.buildClassExpression\nimport software.amazon.app.platform.metro.compiler.fir.buildSimpleAnnotationCall\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeArgument\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeClassId\nimport software.amazon.app.platform.metro.compiler.fir.hasAnnotation\n\n/**\n * Generates the declaration shape for `@ContributesScoped` classes.\n *\n * Pseudo Kotlin:\n * ```kotlin\n * @Inject\n * @ContributesScoped(AppScope::class)\n * class TestClass : SuperType, Scoped {\n *\n *   @ContributesTo(AppScope::class)\n *   @Origin(TestClass::class)\n *   interface ScopedContribution {\n *     @Binds\n *     fun bindSuperType(instance: TestClass): SuperType\n *\n *     @Binds\n *     @IntoSet\n *     @ForScope(AppScope::class)\n *     fun bindTestClassScoped(instance: TestClass): Scoped\n *   }\n * }\n * ```\n *\n * No top-level graph interface is generated. If the contributed class only implements `Scoped`,\n * then `bindSuperType()` is omitted and only the scoped multibinding is generated.\n */\npublic class ContributesScopedFir(session: FirSession) :\n  MetroFirDeclarationGenerationExtension(session) {\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(ContributesScopedIds.PREDICATE)\n  }\n\n  override fun getContributionHints(): List<ContributionHint> {\n    return annotatedScopedClasses().mapNotNull { classSymbol ->\n      if (contributesScopedMetadata(classSymbol, session) == null) return@mapNotNull null\n\n      val scopeClassId =\n        extractScopeClassId(classSymbol, ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID, session)\n          ?: return@mapNotNull null\n      ContributionHint(\n        contributingClassId =\n          classSymbol.classId.createNestedClassId(ContributesScopedIds.NESTED_INTERFACE_NAME),\n        scope = scopeClassId,\n      )\n    }\n  }\n\n  override fun getNestedClassifiersNames(\n    classSymbol: FirClassSymbol<*>,\n    context: NestedClassGenerationContext,\n  ): Set<Name> {\n    val regularClass = classSymbol as? FirRegularClassSymbol ?: return emptySet()\n    return if (\n      hasAnnotation(classSymbol, ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID, session) &&\n        contributesScopedMetadata(regularClass, session) != null\n    ) {\n      setOf(ContributesScopedIds.NESTED_INTERFACE_NAME)\n    } else {\n      emptySet()\n    }\n  }\n\n  override fun generateNestedClassLikeDeclaration(\n    owner: FirClassSymbol<*>,\n    name: Name,\n    context: NestedClassGenerationContext,\n  ): FirClassLikeSymbol<*>? {\n    if (name != ContributesScopedIds.NESTED_INTERFACE_NAME) return null\n    if (!hasAnnotation(owner, ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID, session)) {\n      return null\n    }\n\n    val scopedOwner = owner as? FirRegularClassSymbol ?: return null\n    val metadata = contributesScopedMetadata(scopedOwner, session) ?: return null\n    val scopeArg =\n      extractScopeArgument(scopedOwner, ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID, session)\n        ?: return null\n    val nestedClassId = scopedOwner.classId.createNestedClassId(name)\n    val contributionSymbol = FirRegularClassSymbol(nestedClassId)\n\n    buildRegularClass {\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesScopedGeneratorKey.origin\n      source = scopedOwner.source\n      classKind = ClassKind.INTERFACE\n      scopeProvider = session.kotlinScopeProvider\n      this.name = nestedClassId.shortClassName\n      symbol = contributionSymbol\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.ABSTRACT,\n          Visibilities.Public.toEffectiveVisibility(scopedOwner, forClass = true),\n        )\n      superTypeRefs += session.builtinTypes.anyType\n      annotations +=\n        buildAnnotationCallWithArgument(\n          classId = ClassIds.CONTRIBUTES_TO,\n          argName = Name.identifier(\"scope\"),\n          argument = scopeArg,\n          containingSymbol = contributionSymbol,\n          session = session,\n        )\n      annotations +=\n        buildAnnotationCallWithArgument(\n          classId = ClassIds.ORIGIN,\n          argName = Name.identifier(\"value\"),\n          argument = buildClassExpression(scopedOwner, session),\n          containingSymbol = contributionSymbol,\n          session = session,\n        )\n      buildBindingFunctions(nestedClassId, scopedOwner, metadata, scopeArg).forEach { function ->\n        declarations += function\n      }\n    }\n\n    return contributionSymbol\n  }\n\n  private fun annotatedScopedClasses(): List<FirRegularClassSymbol> {\n    return session.predicateBasedProvider\n      .getSymbolsByPredicate(ContributesScopedIds.PREDICATE)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  private fun buildBindingFunctions(\n    contributionClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    metadata: ScopedContributionMetadata,\n    scopeArg: org.jetbrains.kotlin.fir.expressions.FirExpression,\n  ): List<FirFunction> {\n    return buildList {\n      metadata.otherSuperType?.let { otherSuperType ->\n        add(buildBindFunction(contributionClassId, owner, otherSuperType))\n      }\n      add(buildBindScopedFunction(contributionClassId, owner, scopeArg))\n    }\n  }\n\n  private fun buildBindFunction(\n    contributionClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    otherSuperType: ResolvedScopedSuperType,\n  ): FirFunction {\n    val functionName = \"bind${ContributesScopedIds.generatedTypeName(otherSuperType.classId)}\"\n    return buildBindFunction(\n      contributionClassId = contributionClassId,\n      owner = owner,\n      functionName = functionName,\n      returnType = otherSuperType.coneType,\n    )\n  }\n\n  private fun buildBindScopedFunction(\n    contributionClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    scopeArg: org.jetbrains.kotlin.fir.expressions.FirExpression,\n  ): FirFunction {\n    val functionName = \"bind${ContributesScopedIds.generatedOwnerName(owner.classId)}Scoped\"\n    return buildBindFunction(\n      contributionClassId = contributionClassId,\n      owner = owner,\n      functionName = functionName,\n      returnType = scopedType(),\n      additionalAnnotations = { functionSymbol ->\n        add(buildSimpleAnnotationCall(ClassIds.INTO_SET, functionSymbol, session))\n        add(\n          buildAnnotationCallWithArgument(\n            classId = ClassIds.FOR_SCOPE,\n            argName = Name.identifier(\"scope\"),\n            argument = scopeArg,\n            containingSymbol = functionSymbol,\n            session = session,\n          )\n        )\n      },\n    )\n  }\n\n  private fun buildBindFunction(\n    contributionClassId: ClassId,\n    owner: FirRegularClassSymbol,\n    functionName: String,\n    returnType: ConeKotlinType,\n    additionalAnnotations:\n      MutableList<org.jetbrains.kotlin.fir.expressions.FirAnnotation>.(\n        FirNamedFunctionSymbol\n      ) -> Unit =\n      {},\n  ): FirFunction {\n    val callableId = CallableId(contributionClassId, Name.identifier(functionName))\n    val functionSymbol = FirNamedFunctionSymbol(callableId)\n\n    return buildNamedFunction {\n      isLocal = false\n      resolvePhase = FirResolvePhase.BODY_RESOLVE\n      moduleData = session.moduleData\n      origin = Keys.ContributesScopedGeneratorKey.origin\n      source = owner.source\n      symbol = functionSymbol\n      name = callableId.callableName\n      returnTypeRef = returnType.toFirResolvedTypeRef()\n      dispatchReceiverType = contributionType(contributionClassId)\n      status =\n        FirResolvedDeclarationStatusImpl(\n          Visibilities.Public,\n          Modality.OPEN,\n          Visibilities.Public.toEffectiveVisibility(owner, forClass = true),\n        )\n      valueParameters += buildValueParameter {\n        resolvePhase = FirResolvePhase.BODY_RESOLVE\n        moduleData = session.moduleData\n        origin = Keys.ContributesScopedGeneratorKey.origin\n        source = owner.source\n        returnTypeRef = owner.defaultType().toFirResolvedTypeRef()\n        name = Name.identifier(\"instance\")\n        symbol = FirValueParameterSymbol()\n        containingDeclarationSymbol = functionSymbol\n      }\n      annotations += buildSimpleAnnotationCall(ClassIds.BINDS, functionSymbol, session)\n      annotations.additionalAnnotations(functionSymbol)\n    }\n  }\n\n  private fun contributionType(classId: ClassId) =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(classId),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n\n  private fun scopedType() =\n    ConeClassLikeTypeImpl(\n      ConeClassLikeLookupTagImpl(ClassIds.SCOPED),\n      emptyArray(),\n      isMarkedNullable = false,\n    )\n\n  @AutoService(MetroFirDeclarationGenerationExtension.Factory::class)\n  public class Factory : MetroFirDeclarationGenerationExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroFirDeclarationGenerationExtension = ContributesScopedFir(session)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/scoped/ContributesScopedIds.kt",
    "content": "package software.amazon.app.platform.metro.compiler.scoped\n\nimport org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.Name\nimport software.amazon.app.platform.metro.compiler.ClassIds\n\ninternal object ContributesScopedIds {\n  val CONTRIBUTES_SCOPED_CLASS_ID = ClassIds.CONTRIBUTES_SCOPED\n  val NESTED_INTERFACE_NAME: Name = Name.identifier(\"ScopedContribution\")\n  val PREDICATE = LookupPredicate.create { annotated(CONTRIBUTES_SCOPED_CLASS_ID.asSingleFqName()) }\n\n  fun generatedOwnerName(contributingClassId: ClassId): String {\n    return contributingClassId.relativeClassName.pathSegments().joinToString(separator = \"\") {\n      it.asString()\n    }\n  }\n\n  fun generatedTypeName(boundClassId: ClassId): String {\n    return boundClassId.relativeClassName.pathSegments().joinToString(separator = \"\") {\n      it.asString()\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/scoped/ContributesScopedMetroExtension.kt",
    "content": "package software.amazon.app.platform.metro.compiler.scoped\n\nimport com.google.auto.service.AutoService\nimport dev.zacsweers.metro.compiler.MetroOptions\nimport dev.zacsweers.metro.compiler.api.fir.MetroContributionExtension\nimport dev.zacsweers.metro.compiler.compat.CompatContext\nimport dev.zacsweers.metro.compiler.fir.MetroFirTypeResolver\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar\nimport org.jetbrains.kotlin.fir.extensions.predicateBasedProvider\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.providers.symbolProvider\nimport org.jetbrains.kotlin.fir.scopes.getSingleClassifier\nimport org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.name.ClassId\nimport software.amazon.app.platform.metro.compiler.fir.extractScopeClassId\n\npublic class ContributesScopedMetroExtension(private val session: FirSession) :\n  MetroContributionExtension {\n\n  private val predicate = ContributesScopedIds.PREDICATE\n\n  private val annotatedClasses by lazy {\n    session.predicateBasedProvider\n      .getSymbolsByPredicate(predicate)\n      .filterIsInstance<FirRegularClassSymbol>()\n      .toList()\n  }\n\n  override fun FirDeclarationPredicateRegistrar.registerPredicates() {\n    register(predicate)\n  }\n\n  override fun getContributions(\n    scopeClassId: ClassId,\n    typeResolverFactory: MetroFirTypeResolver.Factory,\n  ): List<MetroContributionExtension.Contribution> {\n    return annotatedClasses.mapNotNull { parentSymbol ->\n      if (contributesScopedMetadata(parentSymbol, session) == null) return@mapNotNull null\n\n      val annotationScopeClassId =\n        extractScopeClassId(parentSymbol, ContributesScopedIds.CONTRIBUTES_SCOPED_CLASS_ID, session)\n          ?: return@mapNotNull null\n      if (annotationScopeClassId != scopeClassId) return@mapNotNull null\n\n      val contributionInterfaceClassId =\n        parentSymbol.classId.createNestedClassId(ContributesScopedIds.NESTED_INTERFACE_NAME)\n      val contributionSymbol =\n        session.symbolProvider.getClassLikeSymbolByClassId(contributionInterfaceClassId)\n          as? FirRegularClassSymbol ?: return@mapNotNull null\n      val scope = contributionSymbol.declaredMemberScope(session, memberRequiredPhase = null)\n      val metroContributionName =\n        scope.getClassifierNames().firstOrNull { it.identifier.startsWith(\"MetroContributionTo\") }\n          ?: return@mapNotNull null\n      val metroContributionSymbol =\n        scope.getSingleClassifier(metroContributionName) as? FirRegularClassSymbol\n          ?: return@mapNotNull null\n\n      MetroContributionExtension.Contribution(\n        supertype = metroContributionSymbol.defaultType(),\n        replaces = emptyList(),\n        originClassId = parentSymbol.classId,\n      )\n    }\n  }\n\n  @AutoService(MetroContributionExtension.Factory::class)\n  public class Factory : MetroContributionExtension.Factory {\n    override fun create(\n      session: FirSession,\n      options: MetroOptions,\n      compatContext: CompatContext,\n    ): MetroContributionExtension {\n      return ContributesScopedMetroExtension(session)\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/main/kotlin/software/amazon/app/platform/metro/compiler/scoped/ContributesScopedSupport.kt",
    "content": "package software.amazon.app.platform.metro.compiler.scoped\n\nimport org.jetbrains.kotlin.fir.FirSession\nimport org.jetbrains.kotlin.fir.resolve.defaultType\nimport org.jetbrains.kotlin.fir.resolve.fullyExpandedType\nimport org.jetbrains.kotlin.fir.resolve.toRegularClassSymbol\nimport org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol\nimport org.jetbrains.kotlin.fir.types.ConeKotlinType\nimport org.jetbrains.kotlin.fir.types.classId\nimport org.jetbrains.kotlin.name.ClassId\nimport org.jetbrains.kotlin.name.StandardClassIds\nimport software.amazon.app.platform.metro.compiler.ClassIds\nimport software.amazon.app.platform.metro.compiler.fir.resolveDeclaredSuperTypes\n\ninternal data class ResolvedScopedSuperType(val classId: ClassId, val coneType: ConeKotlinType)\n\ninternal data class ScopedContributionMetadata(val otherSuperType: ResolvedScopedSuperType?)\n\ninternal fun contributesScopedMetadata(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): ScopedContributionMetadata? {\n  if (!implementsScoped(classSymbol, session)) return null\n\n  val otherSuperTypes = directOtherSupertypes(classSymbol, session)\n  if (otherSuperTypes.size > 1) return null\n\n  return ScopedContributionMetadata(otherSuperType = otherSuperTypes.singleOrNull())\n}\n\ninternal fun directOtherSupertypes(\n  classSymbol: FirRegularClassSymbol,\n  session: FirSession,\n): List<ResolvedScopedSuperType> {\n  return resolveDeclaredSuperTypes(classSymbol, session).mapNotNull { superType ->\n    val classId = superType.classId ?: return@mapNotNull null\n    if (classId == ClassIds.SCOPED || classId == StandardClassIds.Any) return@mapNotNull null\n    ResolvedScopedSuperType(classId = classId, coneType = superType)\n  }\n}\n\ninternal fun implementsScoped(classSymbol: FirRegularClassSymbol, session: FirSession): Boolean {\n  return isScopedType(classSymbol.defaultType(), session)\n}\n\nprivate fun isScopedType(\n  type: ConeKotlinType,\n  session: FirSession,\n  visited: MutableSet<ConeKotlinType> = mutableSetOf(),\n): Boolean {\n  val expandedType = type.fullyExpandedType(session)\n  if (!visited.add(expandedType)) return false\n  if (expandedType.classId == ClassIds.SCOPED) return true\n\n  val classSymbol = expandedType.toRegularClassSymbol(session) ?: return false\n  return resolveDeclaredSuperTypes(classSymbol, session, actualType = expandedType).any {\n    isScopedType(it, session, visited)\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/java/software/amazon/app/platform/metro/compiler/runners/BoxTestGenerated.java",
    "content": "\n\npackage software.amazon.app.platform.metro.compiler.runners;\n\nimport com.intellij.testFramework.TestDataPath;\nimport org.jetbrains.kotlin.test.util.KtTestUtil;\nimport org.jetbrains.kotlin.test.TestMetadata;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.util.regex.Pattern;\n\n/** This class is generated by {@link software.amazon.app.platform.metro.compiler.GenerateTestsKt}. DO NOT MODIFY MANUALLY */\n@SuppressWarnings(\"all\")\n@TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box\")\n@TestDataPath(\"$PROJECT_ROOT\")\npublic class BoxTestGenerated extends AbstractBoxTest {\n  @Test\n  public void testAllFilesPresentInBox() {\n    KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrenderer {\n    @Test\n    public void testAllFilesPresentInContributesrenderer() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRenderer.kt\")\n    public void testDefaultConstructorRenderer() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/defaultConstructorRenderer.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"explicitModelType.kt\")\n    public void testExplicitModelType() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/explicitModelType.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"inferredFromHierarchy.kt\")\n    public void testInferredFromHierarchy() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/inferredFromHierarchy.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"inferredFromHierarchyMultipleLevels.kt\")\n    public void testInferredFromHierarchyMultipleLevels() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/inferredFromHierarchyMultipleLevels.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"injectConstructorRenderer.kt\")\n    public void testInjectConstructorRenderer() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/injectConstructorRenderer.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"innerModel.kt\")\n    public void testInnerModel() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/innerModel.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"innerRenderer.kt\")\n    public void testInnerRenderer() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/innerRenderer.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"sealedHierarchy.kt\")\n    public void testSealedHierarchy() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchy.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"sealedHierarchyDisabled.kt\")\n    public void testSealedHierarchyDisabled() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchyDisabled.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"sealedHierarchyInDependencyModule.kt\")\n    public void testSealedHierarchyInDependencyModule() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchyInDependencyModule.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrobot {\n    @Test\n    public void testAllFilesPresentInContributesrobot() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRobot.kt\")\n    public void testDefaultConstructorRobot() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/defaultConstructorRobot.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"indirectSupertype.kt\")\n    public void testIndirectSupertype() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/indirectSupertype.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"injectConstructorRobot.kt\")\n    public void testInjectConstructorRobot() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/injectConstructorRobot.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesscoped {\n    @Test\n    public void testAllFilesPresentInContributesscoped() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultScoped.kt\")\n    public void testDefaultScoped() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/defaultScoped.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"innerClass.kt\")\n    public void testInnerClass() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/innerClass.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"onlyScoped.kt\")\n    public void testOnlyScoped() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/onlyScoped.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"transitiveScoped.kt\")\n    public void testTransitiveScoped() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/transitiveScoped.kt\");\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/java/software/amazon/app/platform/metro/compiler/runners/FirDiagnosticTestGenerated.java",
    "content": "\n\npackage software.amazon.app.platform.metro.compiler.runners;\n\nimport com.intellij.testFramework.TestDataPath;\nimport org.jetbrains.kotlin.test.util.KtTestUtil;\nimport org.jetbrains.kotlin.test.TestMetadata;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.util.regex.Pattern;\n\n/** This class is generated by {@link software.amazon.app.platform.metro.compiler.GenerateTestsKt}. DO NOT MODIFY MANUALLY */\n@SuppressWarnings(\"all\")\n@TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics\")\n@TestDataPath(\"$PROJECT_ROOT\")\npublic class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {\n  @Test\n  public void testAllFilesPresentInDiagnostics() {\n    KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrenderer {\n    @Test\n    public void testAllFilesPresentInContributesrenderer() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"missingInjectOnNonZeroArgConstructor.kt\")\n    public void testMissingInjectOnNonZeroArgConstructor() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/missingInjectOnNonZeroArgConstructor.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"modelTypeMustBeExplicitWhenNotInferable.kt\")\n    public void testModelTypeMustBeExplicitWhenNotInferable() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/modelTypeMustBeExplicitWhenNotInferable.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"redundantInjectOnZeroArgConstructor.kt\")\n    public void testRedundantInjectOnZeroArgConstructor() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/redundantInjectOnZeroArgConstructor.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"rendererMustNotBeSingleton.kt\")\n    public void testRendererMustNotBeSingleton() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/rendererMustNotBeSingleton.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrobot {\n    @Test\n    public void testAllFilesPresentInContributesrobot() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"classMustImplementRobot.kt\")\n    public void testClassMustImplementRobot() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classMustImplementRobot.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"classWithConstructorParametersMustUseInject.kt\")\n    public void testClassWithConstructorParametersMustUseInject() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classWithConstructorParametersMustUseInject.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"onlyAppScopeSupported.kt\")\n    public void testOnlyAppScopeSupported() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/onlyAppScopeSupported.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"robotMustNotBeSingleton.kt\")\n    public void testRobotMustNotBeSingleton() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/robotMustNotBeSingleton.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesscoped {\n    @Test\n    public void testAllFilesPresentInContributesscoped() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"multipleOtherSupertypes.kt\")\n    public void testMultipleOtherSupertypes() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/multipleOtherSupertypes.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"mustBeInject.kt\")\n    public void testMustBeInject() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustBeInject.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"mustImplementScoped.kt\")\n    public void testMustImplementScoped() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustImplementScoped.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"noSupertypes.kt\")\n    public void testNoSupertypes() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/noSupertypes.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"useContributesScopedInsteadOfContributesBinding.kt\")\n    public void testUseContributesScopedInsteadOfContributesBinding() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/useContributesScopedInsteadOfContributesBinding.kt\");\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/java/software/amazon/app/platform/metro/compiler/runners/FirDumpTestGenerated.java",
    "content": "\n\npackage software.amazon.app.platform.metro.compiler.runners;\n\nimport com.intellij.testFramework.TestDataPath;\nimport org.jetbrains.kotlin.test.util.KtTestUtil;\nimport org.jetbrains.kotlin.test.TestMetadata;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.util.regex.Pattern;\n\n/** This class is generated by {@link software.amazon.app.platform.metro.compiler.GenerateTestsKt}. DO NOT MODIFY MANUALLY */\n@SuppressWarnings(\"all\")\n@TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump\")\n@TestDataPath(\"$PROJECT_ROOT\")\npublic class FirDumpTestGenerated extends AbstractFirDumpTest {\n  @Test\n  public void testAllFilesPresentInDump() {\n    KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrenderer {\n    @Test\n    public void testAllFilesPresentInContributesrenderer() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRenderer.kt\")\n    public void testDefaultConstructorRenderer() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRenderer.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRendererIr.kt\")\n    public void testDefaultConstructorRendererIr() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRendererIr.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesrobot {\n    @Test\n    public void testAllFilesPresentInContributesrobot() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRobot.kt\")\n    public void testDefaultConstructorRobot() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobot.kt\");\n    }\n\n    @Test\n    @TestMetadata(\"defaultConstructorRobotIr.kt\")\n    public void testDefaultConstructorRobotIr() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobotIr.kt\");\n    }\n  }\n\n  @Nested\n  @TestMetadata(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesscoped\")\n  @TestDataPath(\"$PROJECT_ROOT\")\n  public class Contributesscoped {\n    @Test\n    public void testAllFilesPresentInContributesscoped() {\n      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesscoped\"), Pattern.compile(\"^(.+)\\\\.kt$\"), null, true);\n    }\n\n    @Test\n    @TestMetadata(\"defaultScoped.kt\")\n    public void testDefaultScoped() {\n      runTest(\"metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesscoped/defaultScoped.kt\");\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/GenerateTests.kt",
    "content": "package software.amazon.app.platform.metro.compiler\n\nimport org.jetbrains.kotlin.generators.dsl.junit5.generateTestGroupSuiteWithJUnit5\nimport software.amazon.app.platform.metro.compiler.runners.AbstractBoxTest\nimport software.amazon.app.platform.metro.compiler.runners.AbstractFirDiagnosticTest\nimport software.amazon.app.platform.metro.compiler.runners.AbstractFirDumpTest\n\nfun main() {\n  generateTestGroupSuiteWithJUnit5 {\n    testGroup(\n      testDataRoot = \"metro-extensions/contribute/impl-compiler-plugin/src/test/resources\",\n      testsRoot = \"metro-extensions/contribute/impl-compiler-plugin/src/test/java\",\n    ) {\n      testClass<AbstractBoxTest> { model(\"box\") }\n      testClass<AbstractFirDiagnosticTest> { model(\"diagnostics\") }\n      testClass<AbstractFirDumpTest> { model(\"dump\") }\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/runners/AbstractBoxTest.kt",
    "content": "package software.amazon.app.platform.metro.compiler.runners\n\nimport org.jetbrains.kotlin.config.JvmTarget\nimport org.jetbrains.kotlin.test.FirParser\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.directives.CodegenTestDirectives\nimport org.jetbrains.kotlin.test.directives.ConfigurationDirectives\nimport org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives\nimport org.jetbrains.kotlin.test.runners.codegen.AbstractFirBlackBoxCodegenTestBase\nimport org.jetbrains.kotlin.test.services.EnvironmentBasedStandardLibrariesPathProvider\nimport org.jetbrains.kotlin.test.services.KotlinStandardLibrariesPathProvider\nimport software.amazon.app.platform.metro.compiler.services.configureKotlinTestImports\nimport software.amazon.app.platform.metro.compiler.services.configureMetroImports\nimport software.amazon.app.platform.metro.compiler.services.configurePlugin\nimport software.amazon.app.platform.metro.compiler.services.configureTestSupportClasspath\n\nopen class AbstractBoxTest : AbstractFirBlackBoxCodegenTestBase(FirParser.LightTree) {\n  override fun createKotlinStandardLibrariesPathProvider(): KotlinStandardLibrariesPathProvider {\n    return EnvironmentBasedStandardLibrariesPathProvider\n  }\n\n  override fun configure(builder: TestConfigurationBuilder) =\n    with(builder) {\n      super.configure(this)\n\n      defaultDirectives {\n        JvmEnvironmentConfigurationDirectives.JVM_TARGET.with(JvmTarget.JVM_11)\n        +ConfigurationDirectives.WITH_STDLIB\n        +JvmEnvironmentConfigurationDirectives.FULL_JDK\n        +CodegenTestDirectives.IGNORE_DEXING\n      }\n\n      configurePlugin()\n      configureTestSupportClasspath()\n      configureMetroImports()\n      configureKotlinTestImports()\n    }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/runners/AbstractFirDiagnosticTest.kt",
    "content": "package software.amazon.app.platform.metro.compiler.runners\n\nimport org.jetbrains.kotlin.test.FirParser\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.directives.CodegenTestDirectives\nimport org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives\nimport org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives\nimport org.jetbrains.kotlin.test.directives.TestPhaseDirectives\nimport org.jetbrains.kotlin.test.runners.AbstractFirPhasedDiagnosticTest\nimport org.jetbrains.kotlin.test.services.EnvironmentBasedStandardLibrariesPathProvider\nimport org.jetbrains.kotlin.test.services.KotlinStandardLibrariesPathProvider\nimport org.jetbrains.kotlin.test.services.TestPhase\nimport software.amazon.app.platform.metro.compiler.services.configureMetroImports\nimport software.amazon.app.platform.metro.compiler.services.configurePlugin\n\nopen class AbstractFirDiagnosticTest : AbstractFirPhasedDiagnosticTest(FirParser.LightTree) {\n  override fun createKotlinStandardLibrariesPathProvider(): KotlinStandardLibrariesPathProvider {\n    return EnvironmentBasedStandardLibrariesPathProvider\n  }\n\n  override fun configure(builder: TestConfigurationBuilder) =\n    with(builder) {\n      super.configure(builder)\n\n      defaultDirectives {\n        +FirDiagnosticsDirectives.DISABLE_GENERATED_FIR_TAGS\n        +JvmEnvironmentConfigurationDirectives.FULL_JDK\n        +CodegenTestDirectives.IGNORE_DEXING\n        TestPhaseDirectives.RUN_PIPELINE_TILL.with(TestPhase.FRONTEND)\n      }\n\n      configurePlugin()\n      configureMetroImports()\n    }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/runners/AbstractFirDumpTest.kt",
    "content": "package software.amazon.app.platform.metro.compiler.runners\n\nimport org.jetbrains.kotlin.config.JvmTarget\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.directives.CodegenTestDirectives\nimport org.jetbrains.kotlin.test.directives.ConfigurationDirectives\nimport org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives\nimport org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives\nimport org.jetbrains.kotlin.test.runners.ir.AbstractFirLightTreeJvmIrTextTest\nimport org.jetbrains.kotlin.test.services.EnvironmentBasedStandardLibrariesPathProvider\nimport org.jetbrains.kotlin.test.services.KotlinStandardLibrariesPathProvider\nimport software.amazon.app.platform.metro.compiler.services.configureMetroImports\nimport software.amazon.app.platform.metro.compiler.services.configurePlugin\n\nopen class AbstractFirDumpTest : AbstractFirLightTreeJvmIrTextTest() {\n  override fun createKotlinStandardLibrariesPathProvider(): KotlinStandardLibrariesPathProvider {\n    return EnvironmentBasedStandardLibrariesPathProvider\n  }\n\n  override fun configure(builder: TestConfigurationBuilder) {\n    super.configure(builder)\n\n    with(builder) {\n      configurePlugin()\n      configureMetroImports()\n\n      defaultDirectives {\n        JvmEnvironmentConfigurationDirectives.JVM_TARGET.with(JvmTarget.JVM_11)\n        +ConfigurationDirectives.WITH_STDLIB\n        +JvmEnvironmentConfigurationDirectives.FULL_JDK\n        +FirDiagnosticsDirectives.FIR_DUMP\n        +FirDiagnosticsDirectives.DISABLE_GENERATED_FIR_TAGS\n        +CodegenTestDirectives.IGNORE_DEXING\n        -CodegenTestDirectives.DUMP_IR\n        -CodegenTestDirectives.DUMP_KT_IR\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/CompilerPluginTestSupport.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport dev.zacsweers.metro.compiler.MetroCompilerPluginRegistrar\nimport org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar\nimport org.jetbrains.kotlin.config.CompilerConfiguration\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.model.TestModule\nimport org.jetbrains.kotlin.test.services.EnvironmentConfigurator\nimport org.jetbrains.kotlin.test.services.TestServices\nimport software.amazon.app.platform.metro.compiler.AppPlatformMetroExtensionsPluginComponentRegistrar\n\nfun TestConfigurationBuilder.configurePlugin() {\n  useConfigurators(::ExtensionRegistrarConfigurator)\n  configureAnnotations()\n  configureMetroRuntime()\n}\n\nfun TestConfigurationBuilder.configureMetroImports() {\n  useSourcePreprocessor(::MetroImportsPreprocessor)\n}\n\nfun TestConfigurationBuilder.configureKotlinTestImports() {\n  useSourcePreprocessor(::KotlinTestImportsPreprocessor)\n}\n\nprivate class ExtensionRegistrarConfigurator(testServices: TestServices) :\n  EnvironmentConfigurator(testServices) {\n  private val metroRegistrar = MetroCompilerPluginRegistrar()\n  private val extensionsRegistrar = AppPlatformMetroExtensionsPluginComponentRegistrar()\n\n  override fun CompilerPluginRegistrar.ExtensionStorage.registerCompilerExtensions(\n    module: TestModule,\n    configuration: CompilerConfiguration,\n  ) {\n    with(metroRegistrar) { registerExtensions(configuration) }\n    with(extensionsRegistrar) { registerExtensions(configuration) }\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/KotlinTestImportsPreprocessor.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport org.jetbrains.kotlin.test.model.TestFile\nimport org.jetbrains.kotlin.test.services.ReversibleSourceFilePreprocessor\nimport org.jetbrains.kotlin.test.services.TestServices\nimport org.jetbrains.kotlin.test.services.isJavaFile\n\nclass KotlinTestImportsPreprocessor(testServices: TestServices) :\n  ReversibleSourceFilePreprocessor(testServices) {\n\n  private val additionalImports: Set<String> = setOf(\"kotlin.test.*\")\n\n  private val additionalImportsString: String by lazy {\n    additionalImports.sorted().joinToString(separator = \"\\n\") { \"import $it\" }\n  }\n\n  override fun process(file: TestFile, content: String): String {\n    if (file.isAdditional) return content\n    if (file.isJavaFile) return content\n\n    val lines = content.lines().toMutableList()\n    when (val packageIndex = lines.indexOfFirst { it.startsWith(\"package \") }) {\n      -1 ->\n        when (val nonBlankIndex = lines.indexOfFirst { it.isNotBlank() }) {\n          -1 -> lines.add(0, additionalImportsString)\n          else -> lines.add(nonBlankIndex, additionalImportsString)\n        }\n      else -> lines.add(packageIndex + 1, additionalImportsString)\n    }\n    return lines.joinToString(separator = \"\\n\")\n  }\n\n  override fun revert(file: TestFile, actualContent: String): String {\n    if (file.isAdditional) return actualContent\n    if (file.isJavaFile) return actualContent\n    return actualContent.replace(additionalImportsString + \"\\n\", \"\")\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/MetroImportsPreprocessor.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport org.jetbrains.kotlin.test.model.TestFile\nimport org.jetbrains.kotlin.test.services.ReversibleSourceFilePreprocessor\nimport org.jetbrains.kotlin.test.services.TestServices\nimport org.jetbrains.kotlin.test.services.isJavaFile\n\nclass MetroImportsPreprocessor(testServices: TestServices) :\n  ReversibleSourceFilePreprocessor(testServices) {\n\n  private val additionalImports: Set<String> = setOf(\"dev.zacsweers.metro.*\")\n\n  private val additionalImportsString: String by lazy {\n    additionalImports.sorted().joinToString(separator = \"\\n\") { \"import $it\" }\n  }\n\n  override fun process(file: TestFile, content: String): String {\n    if (file.isAdditional) return content\n    if (file.isJavaFile) return content\n\n    val lines = content.lines().toMutableList()\n    when (val packageIndex = lines.indexOfFirst { it.startsWith(\"package \") }) {\n      -1 ->\n        when (val nonBlankIndex = lines.indexOfFirst { it.isNotBlank() }) {\n          -1 -> lines.add(0, additionalImportsString)\n          else -> lines.add(nonBlankIndex, additionalImportsString)\n        }\n      else -> lines.add(packageIndex + 1, additionalImportsString)\n    }\n    return lines.joinToString(separator = \"\\n\")\n  }\n\n  override fun revert(file: TestFile, actualContent: String): String {\n    if (file.isAdditional) return actualContent\n    if (file.isJavaFile) return actualContent\n    return actualContent.replace(additionalImportsString + \"\\n\", \"\")\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/MetroRuntimeProvider.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport java.io.File\nimport org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots\nimport org.jetbrains.kotlin.config.CompilerConfiguration\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.model.TestModule\nimport org.jetbrains.kotlin.test.services.EnvironmentConfigurator\nimport org.jetbrains.kotlin.test.services.RuntimeClasspathProvider\nimport org.jetbrains.kotlin.test.services.TestServices\n\nprivate val metroRuntimeClasspath: List<File> =\n  System.getProperty(\"metroRuntime.classpath\")\n    ?.split(File.pathSeparator)\n    ?.filter { it.isNotBlank() }\n    ?.map(::File) ?: error(\"Unable to get a valid classpath from 'metroRuntime.classpath' property\")\n\nfun TestConfigurationBuilder.configureMetroRuntime() {\n  useConfigurators(::MetroRuntimeEnvironmentConfigurator)\n  useCustomRuntimeClasspathProviders(::MetroRuntimeClasspathProvider)\n}\n\nprivate class MetroRuntimeEnvironmentConfigurator(testServices: TestServices) :\n  EnvironmentConfigurator(testServices) {\n  override fun configureCompilerConfiguration(\n    configuration: CompilerConfiguration,\n    module: TestModule,\n  ) {\n    configuration.addJvmClasspathRoots(metroRuntimeClasspath)\n  }\n}\n\nprivate class MetroRuntimeClasspathProvider(testServices: TestServices) :\n  RuntimeClasspathProvider(testServices) {\n  override fun runtimeClassPaths(module: TestModule): List<File> = metroRuntimeClasspath\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/PluginAnnotationsProvider.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport java.io.File\nimport org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots\nimport org.jetbrains.kotlin.config.CompilerConfiguration\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.model.TestModule\nimport org.jetbrains.kotlin.test.services.EnvironmentConfigurator\nimport org.jetbrains.kotlin.test.services.RuntimeClasspathProvider\nimport org.jetbrains.kotlin.test.services.TestServices\n\nprivate val annotationsRuntimeClasspath: List<File> =\n  System.getProperty(\"annotationsRuntime.classpath\")\n    ?.split(File.pathSeparator)\n    ?.filter { it.isNotBlank() }\n    ?.map(::File)\n    ?: error(\"Unable to get a valid classpath from 'annotationsRuntime.classpath' property\")\n\nfun TestConfigurationBuilder.configureAnnotations() {\n  useConfigurators(::PluginAnnotationsProvider)\n  useCustomRuntimeClasspathProviders(::PluginAnnotationsClasspathProvider)\n}\n\nprivate class PluginAnnotationsProvider(testServices: TestServices) :\n  EnvironmentConfigurator(testServices) {\n  override fun configureCompilerConfiguration(\n    configuration: CompilerConfiguration,\n    module: TestModule,\n  ) {\n    configuration.addJvmClasspathRoots(annotationsRuntimeClasspath)\n  }\n}\n\nprivate class PluginAnnotationsClasspathProvider(testServices: TestServices) :\n  RuntimeClasspathProvider(testServices) {\n  override fun runtimeClassPaths(module: TestModule): List<File> = annotationsRuntimeClasspath\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/services/TestSupportClasspathProvider.kt",
    "content": "package software.amazon.app.platform.metro.compiler.services\n\nimport java.io.File\nimport org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots\nimport org.jetbrains.kotlin.config.CompilerConfiguration\nimport org.jetbrains.kotlin.test.builders.TestConfigurationBuilder\nimport org.jetbrains.kotlin.test.model.TestModule\nimport org.jetbrains.kotlin.test.services.EnvironmentConfigurator\nimport org.jetbrains.kotlin.test.services.RuntimeClasspathProvider\nimport org.jetbrains.kotlin.test.services.TestServices\n\nprivate val testSupportClasspath: List<File> =\n  System.getProperty(\"testSupport.classpath\")\n    ?.split(File.pathSeparator)\n    ?.filter { it.isNotBlank() }\n    ?.map(::File) ?: error(\"Unable to get a valid classpath from 'testSupport.classpath' property\")\n\nfun TestConfigurationBuilder.configureTestSupportClasspath() {\n  useConfigurators(::TestSupportClasspathConfigurator)\n  useCustomRuntimeClasspathProviders(::TestSupportRuntimeClasspathProvider)\n}\n\nprivate class TestSupportClasspathConfigurator(testServices: TestServices) :\n  EnvironmentConfigurator(testServices) {\n  override fun configureCompilerConfiguration(\n    configuration: CompilerConfiguration,\n    module: TestModule,\n  ) {\n    configuration.addJvmClasspathRoots(testSupportClasspath)\n  }\n}\n\nprivate class TestSupportRuntimeClasspathProvider(testServices: TestServices) :\n  RuntimeClasspathProvider(testServices) {\n  override fun runtimeClassPaths(module: TestModule): List<File> = testSupportClasspath\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/kotlin/software/amazon/app/platform/metro/compiler/support/UnusedRendererFactory.kt",
    "content": "package software.amazon.app.platform.metro.compiler.support\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererFactory\n\nobject UnusedRendererFactory : RendererFactory {\n  override fun <T : BaseModel> createRenderer(\n    modelType: kotlin.reflect.KClass<out T>\n  ): Renderer<T> {\n    error(\"unused\")\n  }\n\n  override fun <T : BaseModel> getRenderer(\n    modelType: kotlin.reflect.KClass<out T>,\n    rendererId: Int,\n  ): Renderer<T> {\n    error(\"unused\")\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/defaultConstructorRenderer.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val rendererProvider = graph.renderers.getValue(Model::class)\n  val renderer = rendererProvider()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/explicitModelType.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\nclass Model2 : BaseModel\n\n@ContributesRenderer(Model::class)\nclass TestRenderer : Renderer<Model2> {\n  override fun render(model: Model2) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: explicit model type should win, but got keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/inferredFromHierarchy.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\ninterface OtherRenderer : Renderer<Model>\n\n@ContributesRenderer\nclass TestRenderer : OtherRenderer {\n  override fun render(model: Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val renderer = graph.renderers.getValue(Model::class).invoke()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/inferredFromHierarchyMultipleLevels.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\ninterface OtherRenderer<S : Any, T : BaseModel, U : CharSequence> : Renderer<T>\n\ninterface OtherRenderer2<S : BaseModel, T : Any> : OtherRenderer<T, S, String>\n\ninterface OtherRenderer3 : OtherRenderer2<Model, Any>\n\ninterface OtherRenderer4 : OtherRenderer3\n\n@ContributesRenderer\nclass TestRenderer : OtherRenderer4 {\n  override fun render(model: Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val renderer = graph.renderers.getValue(Model::class).invoke()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/injectConstructorRenderer.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\n@ContributesRenderer\n@Inject\nclass TestRenderer(val string: String) : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph {\n  @Provides fun provideString(): String = \"abc\"\n}\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val rendererProvider = graph.renderers.getValue(Model::class)\n  val renderer = rendererProvider()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n  if (renderer.string != \"abc\") {\n    return \"FAIL: expected injected string to be abc but got ${renderer.string}\"\n  }\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/innerModel.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Presenter {\n  class Model : BaseModel\n}\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Presenter.Model> {\n  override fun render(model: Presenter.Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val renderer = graph.renderers.getValue(Presenter.Model::class).invoke()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n  if (graph.renderers.keys != setOf(Presenter.Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Presenter.Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/innerRenderer.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\nclass Model : BaseModel\n\nclass TestRenderer {\n  @ContributesRenderer\n  class Inner : Renderer<Model> {\n    override fun render(model: Model) = Unit\n  }\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val renderer = graph.renderers.getValue(Model::class).invoke()\n  if (renderer !is TestRenderer.Inner) {\n    return \"FAIL: expected TestRenderer.Inner but got $renderer\"\n  }\n  if (graph.renderers.keys != setOf(Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Model::class to TestRenderer.Inner::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchy.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\ninterface Presenter {\n  sealed interface Model : BaseModel {\n    sealed interface Inner : Model {\n      data object Model1 : Inner\n\n      data object Model2 : Inner\n    }\n\n    data object Model2 : Model\n\n    class OtherSubclass\n  }\n}\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Presenter.Model> {\n  override fun render(model: Presenter.Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val expectedKeys =\n    setOf(\n      Presenter.Model::class,\n      Presenter.Model.Inner::class,\n      Presenter.Model.Inner.Model1::class,\n      Presenter.Model.Inner.Model2::class,\n      Presenter.Model.Model2::class,\n    )\n  if (graph.renderers.keys != expectedKeys) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping.keys != expectedKeys) {\n    return \"FAIL: unexpected modelToRendererMapping keys ${graph.modelToRendererMapping.keys}\"\n  }\n  if (graph.modelToRendererMapping.values.toSet() != setOf(TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping values ${graph.modelToRendererMapping.values}\"\n  }\n  for (key in expectedKeys) {\n    val renderer = graph.renderers.getValue(key).invoke()\n    if (renderer !is TestRenderer) {\n      return \"FAIL: expected TestRenderer for $key but got $renderer\"\n    }\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchyDisabled.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererGraph\n\ninterface Presenter {\n  sealed interface Model : BaseModel {\n    data object Model1 : Model\n\n    data object Model2 : Model\n  }\n}\n\n@ContributesRenderer(includeSealedSubtypes = false)\nclass TestRenderer : Renderer<Presenter.Model> {\n  override fun render(model: Presenter.Model) = Unit\n}\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val renderer = graph.renderers.getValue(Presenter.Model::class).invoke()\n  if (renderer !is TestRenderer) {\n    return \"FAIL: expected TestRenderer but got $renderer\"\n  }\n  if (graph.renderers.keys != setOf(Presenter.Model::class)) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping != mapOf(Presenter.Model::class to TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping ${graph.modelToRendererMapping}\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrenderer/sealedHierarchyInDependencyModule.kt",
    "content": "// MODULE: public\n// FILE: Template.kt\npackage com.test\n\nimport software.amazon.app.platform.presenter.BaseModel\n\nsealed interface Template : BaseModel {\n  data object FullScreen : Template\n\n  data object ListDetail : Template\n}\n\n// MODULE: impl(public)\n// FILE: TestRenderer.kt\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.Renderer\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Template> {\n  override fun render(model: Template) = Unit\n}\n\n// MODULE: app(impl public)\n// FILE: app.kt\npackage com.test\n\nimport software.amazon.app.platform.metro.compiler.support.UnusedRendererFactory\nimport software.amazon.app.platform.renderer.RendererGraph\n\n@DependencyGraph(AppScope::class)\ninterface AppGraph\n\nfun box(): String {\n  val factory = createGraph<AppGraph>() as RendererGraph.Factory\n  val graph = factory.createRendererGraph(UnusedRendererFactory)\n  val expectedKeys =\n    setOf(\n      Template::class,\n      Template.FullScreen::class,\n      Template.ListDetail::class,\n    )\n  if (graph.renderers.keys != expectedKeys) {\n    return \"FAIL: unexpected renderer keys ${graph.renderers.keys}\"\n  }\n  if (graph.modelToRendererMapping.keys != expectedKeys) {\n    return \"FAIL: unexpected modelToRendererMapping keys ${graph.modelToRendererMapping.keys}\"\n  }\n  if (graph.modelToRendererMapping.values.toSet() != setOf(TestRenderer::class)) {\n    return \"FAIL: unexpected modelToRendererMapping values ${graph.modelToRendererMapping.values}\"\n  }\n  for (key in expectedKeys) {\n    val renderer = graph.renderers.getValue(key).invoke()\n    if (renderer !is TestRenderer) {\n      return \"FAIL: expected TestRenderer for $key but got $renderer\"\n    }\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/defaultConstructorRobot.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\nimport software.amazon.app.platform.robot.RobotGraph\n\n@ContributesRobot(AppScope::class)\nclass TestRobot : Robot\n\n@DependencyGraph(AppScope::class)\ninterface MyGraph : RobotGraph\n\nfun box(): String {\n  val graph = createGraph<MyGraph>()\n  val robotFactory = graph.robots.getValue(TestRobot::class)\n  val robot = robotFactory()\n\n  return if (robot is TestRobot) \"OK\" else \"FAIL: expected TestRobot but got $robot\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/indirectSupertype.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\nimport software.amazon.app.platform.robot.RobotGraph\n\ninterface BaseRobot1 : Robot\n\nabstract class BaseRobot2 : BaseRobot1\n\n@Inject\n@ContributesRobot(AppScope::class)\nclass TestRobot : BaseRobot2()\n\n@DependencyGraph(AppScope::class)\ninterface MyGraph : RobotGraph\n\nfun box(): String {\n  val graph = createGraph<MyGraph>()\n  val robotFactory = graph.robots.getValue(TestRobot::class)\n  val robot = robotFactory()\n\n  return if (robot is TestRobot) \"OK\" else \"FAIL: expected TestRobot but got $robot\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesrobot/injectConstructorRobot.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\nimport software.amazon.app.platform.robot.RobotGraph\n\n@Inject\nclass RobotDependency {\n  fun value(): String = \"dependency\"\n}\n\n@Inject\n@ContributesRobot(AppScope::class)\nclass TestRobot(\n  val dependency: RobotDependency,\n) : Robot\n\n@DependencyGraph(AppScope::class)\ninterface MyGraph : RobotGraph\n\nfun box(): String {\n  val graph = createGraph<MyGraph>()\n  val robotFactory = graph.robots.getValue(TestRobot::class)\n  val robot = robotFactory()\n\n  if (robot !is TestRobot) {\n    return \"FAIL: expected TestRobot but got $robot\"\n  }\n\n  return if (robot.dependency.value() == \"dependency\") {\n    \"OK\"\n  } else {\n    \"FAIL: dependency was not injected\"\n  }\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/defaultScoped.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass TestClass : SuperType, Scoped\n\n@DependencyGraph(AppScope::class)\n@SingleIn(AppScope::class)\ninterface GraphInterface {\n  val superTypeInstance: SuperType\n\n  @ForScope(AppScope::class)\n  val allScoped: Set<Scoped>\n}\n\nfun box(): String {\n  val graph = createGraph<GraphInterface>()\n  val scoped = graph.allScoped.single()\n  if (graph.superTypeInstance !is TestClass) {\n    return \"FAIL: expected TestClass super type binding but got ${graph.superTypeInstance}\"\n  }\n  if (scoped !is TestClass) {\n    return \"FAIL: expected TestClass scoped binding but got $scoped\"\n  }\n  if (graph.superTypeInstance !== scoped) {\n    return \"FAIL: expected shared singleton instance\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/innerClass.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\ninterface TestClass {\n  @Inject\n  @SingleIn(AppScope::class)\n  @ContributesScoped(AppScope::class)\n  class Inner : SuperType, Scoped\n}\n\n@DependencyGraph(AppScope::class)\n@SingleIn(AppScope::class)\ninterface GraphInterface {\n  val superTypeInstance: SuperType\n\n  @ForScope(AppScope::class)\n  val allScoped: Set<Scoped>\n}\n\nfun box(): String {\n  val graph = createGraph<GraphInterface>()\n  val scoped = graph.allScoped.single()\n  if (graph.superTypeInstance !is TestClass.Inner) {\n    return \"FAIL: expected TestClass.Inner super type binding but got ${graph.superTypeInstance}\"\n  }\n  if (scoped !is TestClass.Inner) {\n    return \"FAIL: expected TestClass.Inner scoped binding but got $scoped\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/onlyScoped.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass TestClass : Scoped\n\n@DependencyGraph(AppScope::class)\n@SingleIn(AppScope::class)\ninterface GraphInterface {\n  @ForScope(AppScope::class)\n  val allScoped: Set<Scoped>\n}\n\nfun box(): String {\n  val graph = createGraph<GraphInterface>()\n  val scoped = graph.allScoped.single()\n  return if (scoped is TestClass) \"OK\" else \"FAIL: expected TestClass but got $scoped\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/box/contributesscoped/transitiveScoped.kt",
    "content": "package com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType : Scoped\n\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass TestClass : SuperType\n\n@DependencyGraph(AppScope::class)\n@SingleIn(AppScope::class)\ninterface GraphInterface {\n  val superTypeInstance: SuperType\n\n  @ForScope(AppScope::class)\n  val allScoped: Set<Scoped>\n}\n\nfun box(): String {\n  val graph = createGraph<GraphInterface>()\n  val scoped = graph.allScoped.single()\n  if (graph.superTypeInstance !is TestClass) {\n    return \"FAIL: expected TestClass super type binding but got ${graph.superTypeInstance}\"\n  }\n  if (scoped !is TestClass) {\n    return \"FAIL: expected TestClass scoped binding but got $scoped\"\n  }\n\n  return \"OK\"\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/missingInjectOnNonZeroArgConstructor.fir.diag.txt",
    "content": "/missingInjectOnNonZeroArgConstructor.kt:(278,298): error: When using @ContributesRenderer and you need to inject types in the constructor, then it's necessary to add the @Inject annotation.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/missingInjectOnNonZeroArgConstructor.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\n\nclass Model : BaseModel\n\n<!CONTRIBUTES_RENDERER_ERROR!>@ContributesRenderer<!>\nclass TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/modelTypeMustBeExplicitWhenNotInferable.fir.diag.txt",
    "content": "/modelTypeMustBeExplicitWhenNotInferable.kt:(374,394): error: Couldn't find BaseModel type for TestRenderer. Consider adding an explicit parameter.Found: com.test.Model1, com.test.Model2\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/modelTypeMustBeExplicitWhenNotInferable.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\n\nclass Model1 : BaseModel\n\nclass Model2 : BaseModel\n\ninterface OtherRenderer<S : BaseModel, T : BaseModel> : Renderer<S>\n\n<!CONTRIBUTES_RENDERER_ERROR!>@ContributesRenderer<!>\nclass TestRenderer : OtherRenderer<Model1, Model2> {\n  override fun render(model: Model1) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/redundantInjectOnZeroArgConstructor.fir.diag.txt",
    "content": "/redundantInjectOnZeroArgConstructor.kt:(278,298): error: It's redundant to use @Inject when using @ContributesRenderer for a Renderer with a zero-arg constructor.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/redundantInjectOnZeroArgConstructor.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\n\nclass Model : BaseModel\n\n<!CONTRIBUTES_RENDERER_ERROR!>@ContributesRenderer<!>\n@Inject\nclass TestRenderer : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/rendererMustNotBeSingleton.fir.diag.txt",
    "content": "/rendererMustNotBeSingleton.kt:(337,357): error: Renderers should not be singletons in the RendererScope. The RendererFactory will cache the Renderer when necessary. Remove the @SingleIn(RendererScope::class) annotation.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrenderer/rendererMustNotBeSingleton.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererScope\n\nclass Model : BaseModel\n\n<!CONTRIBUTES_RENDERER_ERROR!>@ContributesRenderer<!>\n@SingleIn(RendererScope::class)\n@Inject\nclass TestRenderer(@Suppress(\"unused\") val string: String) : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classMustImplementRobot.fir.diag.txt",
    "content": "/classMustImplementRobot.kt:(146,176): error: In order to use @ContributesRobot, MissingRobot must implement software.amazon.app.platform.robot.Robot.\n\n/classMustImplementRobot.kt:(146,176): error: Robots can only be contributed to the AppScope for now. Scope kotlin.Unit is unsupported.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classMustImplementRobot.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\n\n<!CONTRIBUTES_ROBOT_ERROR, CONTRIBUTES_ROBOT_ERROR!>@ContributesRobot(Unit::class)<!>\nclass MissingRobot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classWithConstructorParametersMustUseInject.fir.diag.txt",
    "content": "/classWithConstructorParametersMustUseInject.kt:(217,251): error: TestRobot must be annotated with @Inject when injecting arguments into a robot.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/classWithConstructorParametersMustUseInject.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\n\nclass RobotDependency\n\n<!CONTRIBUTES_ROBOT_ERROR!>@ContributesRobot(AppScope::class)<!>\nclass TestRobot(\n  val dependency: RobotDependency,\n) : Robot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/onlyAppScopeSupported.fir.diag.txt",
    "content": "/onlyAppScopeSupported.kt:(194,226): error: Robots can only be contributed to the AppScope for now. Scope kotlin.String is unsupported.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/onlyAppScopeSupported.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\n\n<!CONTRIBUTES_ROBOT_ERROR!>@ContributesRobot(String::class)<!>\nclass TestRobot : Robot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/robotMustNotBeSingleton.fir.diag.txt",
    "content": "/robotMustNotBeSingleton.kt:(221,255): error: It's not allowed for a robot to be a singleton, because the lifetime of the robot is scoped to the robot() factory function. Remove the @SingleIn annotation.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesrobot/robotMustNotBeSingleton.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\n\n@SingleIn(AppScope::class)\n<!CONTRIBUTES_ROBOT_ERROR!>@ContributesRobot(AppScope::class)<!>\nclass TestRobot : Robot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/multipleOtherSupertypes.fir.diag.txt",
    "content": "/multipleOtherSupertypes.kt:(274,309): error: In order to use @ContributesScoped, TestClass is allowed to have only one other super type besides Scoped.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/multipleOtherSupertypes.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\ninterface SuperType2\n\n@Inject\n@SingleIn(AppScope::class)\n<!CONTRIBUTES_SCOPED_ERROR!>@ContributesScoped(AppScope::class)<!>\nclass TestClass : SuperType, SuperType2, Scoped\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustBeInject.fir.diag.txt",
    "content": "/mustBeInject.kt:(244,279): error: TestClass must be annotated with @Inject when using @ContributesScoped.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustBeInject.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\n@SingleIn(AppScope::class)\n<!CONTRIBUTES_SCOPED_ERROR!>@ContributesScoped(AppScope::class)<!>\nclass TestClass : SuperType, Scoped\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustImplementScoped.fir.diag.txt",
    "content": "/mustImplementScoped.kt:(203,238): error: In order to use @ContributesScoped, TestClass must implement software.amazon.app.platform.scope.Scoped.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/mustImplementScoped.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\n\ninterface SuperType\n\n@Inject\n@SingleIn(AppScope::class)\n<!CONTRIBUTES_SCOPED_ERROR!>@ContributesScoped(AppScope::class)<!>\nclass TestClass : SuperType\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/noSupertypes.fir.diag.txt",
    "content": "/noSupertypes.kt:(182,217): error: In order to use @ContributesScoped, TestClass must implement software.amazon.app.platform.scope.Scoped.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/noSupertypes.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\n\n@Inject\n@SingleIn(AppScope::class)\n<!CONTRIBUTES_SCOPED_ERROR!>@ContributesScoped(AppScope::class)<!>\nclass TestClass\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/useContributesScopedInsteadOfContributesBinding.fir.diag.txt",
    "content": "/useContributesScopedInsteadOfContributesBinding.kt:(185,221): error: TestClass implements Scoped, but uses @ContributesBinding instead of @ContributesScoped. When implementing Scoped the annotation @ContributesScoped must be used instead of @ContributesBinding to bind both super types correctly. It's not necessary to use @ContributesBinding.\n\n/useContributesScopedInsteadOfContributesBinding.kt:(185,221): error: `@ContributesBinding`-annotated class @dev.zacsweers.metro.ContributesBinding doesn't declare an explicit `binding` type but has multiple supertypes. You must define an explicit bound type in this scenario.\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/diagnostics/contributesscoped/useContributesScopedInsteadOfContributesBinding.kt",
    "content": "// RENDER_DIAGNOSTICS_FULL_TEXT\npackage com.test\n\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\n@Inject\n@SingleIn(AppScope::class)\n<!AGGREGATION_ERROR, CONTRIBUTES_SCOPED_ERROR!>@ContributesBinding(AppScope::class)<!>\nclass TestClass : SuperType, Scoped\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRenderer.fir.txt",
    "content": "FILE: defaultConstructorRenderer.kt\n    package com.test\n\n    public final class Model : R|software/amazon/app/platform/presenter/BaseModel| {\n        public constructor(): R|com/test/Model| {\n            super<R|kotlin/Any|>()\n        }\n\n    }\n    @R|software/amazon/app/platform/inject/ContributesRenderer|() public final class TestRenderer : R|software/amazon/app/platform/renderer/Renderer<com/test/Model>| {\n        public constructor(): R|com/test/TestRenderer| {\n            super<R|kotlin/Any|>()\n        }\n\n        public open override fun render(model: R|com/test/Model|): R|kotlin/Unit| {\n            ^render Q|kotlin/Unit|\n        }\n\n        @R|dev/zacsweers/metro/ContributesTo|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) @R|dev/zacsweers/metro/Origin|(value = <getClass>(Q|com/test/TestRenderer|)) public abstract interface RendererContribution : R|kotlin/Any| {\n            @R|dev/zacsweers/metro/Provides|() public open fun provideComTestTestRenderer(): R|com/test/TestRenderer|\n\n            @R|dev/zacsweers/metro/Binds|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RendererKey|(value = <getClass>(Q|com/test/Model|)) public open fun provideComTestTestRendererModel(renderer: R|com/test/TestRenderer|): R|software/amazon/app/platform/renderer/Renderer<*>|\n\n            @R|dev/zacsweers/metro/Provides|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RendererKey|(value = <getClass>(Q|com/test/Model|)) @R|dev/zacsweers/metro/ForScope|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) public open fun provideComTestTestRendererModelKey(): R|kotlin/reflect/KClass<out software/amazon/app/platform/renderer/Renderer<*>>|\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/MetroContribution|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) public abstract interface MetroContributionToSoftwareamazonappplatformrendererrendererScope2FKSELE : R|com/test/TestRenderer.RendererContribution| {\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideComTestTestRenderer), propertyName = String(), startOffset = Int(276), endOffset = Int(380), newInstanceName = String(provideComTestTestRenderer)) public final class ProvideComTestTestRendererMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRenderer.RendererContribution.ProvideComTestTestRendererMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideComTestTestRendererModelKey), propertyName = String(), startOffset = Int(276), endOffset = Int(380), newInstanceName = String(provideComTestTestRendererModelKey)) public final class ProvideComTestTestRendererModelKeyMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRenderer.RendererContribution.ProvideComTestTestRendererModelKeyMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public abstract class BindsMirror : R|kotlin/Any| {\n                private constructor(): R|com/test/TestRenderer.RendererContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n                private constructor(): R|com/test/TestRenderer.RendererContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n            }\n\n        }\n\n    }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRenderer.kt",
    "content": "// RUN_PIPELINE_TILL: BACKEND\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\n\nclass Model : BaseModel\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRendererIr.fir.kt.txt",
    "content": "// FILE: defaultConstructorRendererIr.kt\npackage com.test\n\nclass Model : BaseModel {\n  constructor() /* primary */ {\n    super/*Any*/()\n    /* <init>() */\n\n  }\n\n}\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Model> {\n  @ContributesTo(scope = RendererScope::class)\n  @Origin(value = TestRenderer::class)\n  interface RendererContribution {\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @ComptimeOnly\n    abstract class BindsMirror {\n      private constructor() /* primary */ {\n        super/*Any*/()\n        /* <init>() */\n\n      }\n\n      @Binds\n      @IntoMap\n      @RendererKey(value = Model::class)\n      @CallableMetadata(callableName = \"provideComTestTestRendererModel\", propertyName = \"\", startOffset = 290, endOffset = 394)\n      abstract fun provideComTestTestRendererModel1186685849_intomap(renderer: TestRenderer): Renderer<*>\n\n    }\n\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @MetroContribution(scope = RendererScope::class)\n    interface MetroContributionToSoftwareamazonappplatformrendererrendererScope2FKSELE : RendererContribution {\n    }\n\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @CallableMetadata(callableName = \"provideComTestTestRenderer\", propertyName = \"\", startOffset = 290, endOffset = 394, newInstanceName = \"provideComTestTestRenderer\")\n    class ProvideComTestTestRendererMetroFactory : Factory<TestRenderer> {\n      private /* final field */ val instance: RendererContribution = instance\n      companion object Companion {\n        private constructor() /* primary */ {\n          super/*Any*/()\n          /* <init>() */\n\n        }\n\n        @HiddenFromObjC\n        fun create(instance: RendererContribution): Factory<TestRenderer> {\n          return ProvideComTestTestRendererMetroFactory(instance = instance)\n        }\n\n        @HiddenFromObjC\n        fun provideComTestTestRenderer(instance: RendererContribution): TestRenderer {\n          return instance.provideComTestTestRenderer()\n        }\n\n      }\n\n      @HiddenFromObjC\n      private constructor(instance: RendererContribution) /* primary */ {\n        super/*Any*/()\n        /* <init>() */\n\n      }\n\n      @HiddenFromObjC\n      override operator fun invoke(): TestRenderer {\n        return Companion.provideComTestTestRenderer(instance = <this>.#instance)\n      }\n\n      @HiddenFromObjC\n      fun mirrorFunction(): TestRenderer {\n        return error(message = \"Never called\")\n      }\n\n    }\n\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @CallableMetadata(callableName = \"provideComTestTestRendererModelKey\", propertyName = \"\", startOffset = 290, endOffset = 394, newInstanceName = \"provideComTestTestRendererModelKey\")\n    class ProvideComTestTestRendererModelKeyMetroFactory : Factory<KClass<out Renderer<*>>> {\n      private /* final field */ val instance: RendererContribution = instance\n      companion object Companion {\n        private constructor() /* primary */ {\n          super/*Any*/()\n          /* <init>() */\n\n        }\n\n        @HiddenFromObjC\n        fun create(instance: RendererContribution): Factory<KClass<out Renderer<*>>> {\n          return ProvideComTestTestRendererModelKeyMetroFactory(instance = instance)\n        }\n\n        @HiddenFromObjC\n        fun provideComTestTestRendererModelKey(instance: RendererContribution): KClass<out Renderer<*>> {\n          return instance.provideComTestTestRendererModelKey()\n        }\n\n      }\n\n      @HiddenFromObjC\n      private constructor(instance: RendererContribution) /* primary */ {\n        super/*Any*/()\n        /* <init>() */\n\n      }\n\n      @HiddenFromObjC\n      override operator fun invoke(): KClass<out Renderer<*>> {\n        return Companion.provideComTestTestRendererModelKey(instance = <this>.#instance)\n      }\n\n      @IntoMap\n      @ForScope(scope = RendererScope::class)\n      @RendererKey(value = Model::class)\n      @HiddenFromObjC\n      fun mirrorFunction(): KClass<out Renderer<*>> {\n        return error(message = \"Never called\")\n      }\n\n    }\n\n    @Provides\n    fun provideComTestTestRenderer(): TestRenderer {\n      return TestRenderer()\n    }\n\n    @Binds\n    @IntoMap\n    @RendererKey(value = Model::class)\n    @ComptimeOnly\n    fun provideComTestTestRendererModel(renderer: TestRenderer): Renderer<*> {\n      return error(message = \"Never called\")\n    }\n\n    @Provides\n    @IntoMap\n    @RendererKey(value = Model::class)\n    @ForScope(scope = RendererScope::class)\n    fun provideComTestTestRendererModelKey(): KClass<out Renderer<*>> {\n      return TestRenderer::class\n    }\n\n  }\n\n  constructor() /* primary */ {\n    super/*Any*/()\n    /* <init>() */\n\n  }\n\n  override fun render(model: Model) {\n    return Unit\n  }\n\n}\n\n// FILE: comTestTestRendererRendererContributionRendererScope.kt\npackage metro.hints\n\nfun RendererScope(contributed: RendererContribution) {\n  return error(message = \"Never called\")\n}\n\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRendererIr.fir.txt",
    "content": "FILE: defaultConstructorRendererIr.kt\n    package com.test\n\n    public final class Model : R|software/amazon/app/platform/presenter/BaseModel| {\n        public constructor(): R|com/test/Model| {\n            super<R|kotlin/Any|>()\n        }\n\n    }\n    @R|software/amazon/app/platform/inject/ContributesRenderer|() public final class TestRenderer : R|software/amazon/app/platform/renderer/Renderer<com/test/Model>| {\n        public constructor(): R|com/test/TestRenderer| {\n            super<R|kotlin/Any|>()\n        }\n\n        public open override fun render(model: R|com/test/Model|): R|kotlin/Unit| {\n            ^render Q|kotlin/Unit|\n        }\n\n        @R|dev/zacsweers/metro/ContributesTo|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) @R|dev/zacsweers/metro/Origin|(value = <getClass>(Q|com/test/TestRenderer|)) public abstract interface RendererContribution : R|kotlin/Any| {\n            @R|dev/zacsweers/metro/Provides|() public open fun provideComTestTestRenderer(): R|com/test/TestRenderer|\n\n            @R|dev/zacsweers/metro/Binds|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RendererKey|(value = <getClass>(Q|com/test/Model|)) public open fun provideComTestTestRendererModel(renderer: R|com/test/TestRenderer|): R|software/amazon/app/platform/renderer/Renderer<*>|\n\n            @R|dev/zacsweers/metro/Provides|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RendererKey|(value = <getClass>(Q|com/test/Model|)) @R|dev/zacsweers/metro/ForScope|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) public open fun provideComTestTestRendererModelKey(): R|kotlin/reflect/KClass<out software/amazon/app/platform/renderer/Renderer<*>>|\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/MetroContribution|(scope = <getClass>(Q|software/amazon/app/platform/renderer/RendererScope|)) public abstract interface MetroContributionToSoftwareamazonappplatformrendererrendererScope2FKSELE : R|com/test/TestRenderer.RendererContribution| {\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideComTestTestRenderer), propertyName = String(), startOffset = Int(290), endOffset = Int(394), newInstanceName = String(provideComTestTestRenderer)) public final class ProvideComTestTestRendererMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRenderer.RendererContribution.ProvideComTestTestRendererMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideComTestTestRendererModelKey), propertyName = String(), startOffset = Int(290), endOffset = Int(394), newInstanceName = String(provideComTestTestRendererModelKey)) public final class ProvideComTestTestRendererModelKeyMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRenderer.RendererContribution.ProvideComTestTestRendererModelKeyMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public abstract class BindsMirror : R|kotlin/Any| {\n                private constructor(): R|com/test/TestRenderer.RendererContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n                private constructor(): R|com/test/TestRenderer.RendererContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n            }\n\n        }\n\n    }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrenderer/defaultConstructorRendererIr.kt",
    "content": "// RUN_PIPELINE_TILL: BACKEND\n// DUMP_KT_IR\npackage com.test\n\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.Renderer\n\nclass Model : BaseModel\n\n@ContributesRenderer\nclass TestRenderer : Renderer<Model> {\n  override fun render(model: Model) = Unit\n}\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobot.fir.txt",
    "content": "FILE: defaultConstructorRobot.kt\n    package com.test\n\n    @R|software/amazon/app/platform/inject/robot/ContributesRobot|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public final class TestRobot : R|software/amazon/app/platform/robot/Robot| {\n        public constructor(): R|com/test/TestRobot| {\n            super<R|kotlin/Any|>()\n        }\n\n        @R|dev/zacsweers/metro/ContributesTo|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) @R|dev/zacsweers/metro/Origin|(value = <getClass>(Q|com/test/TestRobot|)) public abstract interface RobotContribution : R|kotlin/Any| {\n            @R|dev/zacsweers/metro/Provides|() public open fun provideTestRobot(): R|com/test/TestRobot|\n\n            @R|dev/zacsweers/metro/Binds|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RobotKey|(value = <getClass>(Q|com/test/TestRobot|)) public open fun provideTestRobotIntoMap(robot: R|com/test/TestRobot|): R|software/amazon/app/platform/robot/Robot|\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/MetroContribution|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public abstract interface MetroContributionToDevzacsweersmetroappScopeoxd1AHI : R|com/test/TestRobot.RobotContribution| {\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideTestRobot), propertyName = String(), startOffset = Int(192), endOffset = Int(250), newInstanceName = String(provideTestRobot)) public final class ProvideTestRobotMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRobot.RobotContribution.ProvideTestRobotMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public abstract class BindsMirror : R|kotlin/Any| {\n                private constructor(): R|com/test/TestRobot.RobotContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n                private constructor(): R|com/test/TestRobot.RobotContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n            }\n\n        }\n\n    }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobot.kt",
    "content": "// RUN_PIPELINE_TILL: BACKEND\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\n\n@ContributesRobot(AppScope::class)\nclass TestRobot : Robot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobotIr.fir.kt.txt",
    "content": "// FILE: defaultConstructorRobotIr.kt\npackage com.test\n\n@ContributesRobot(scope = AppScope::class)\nclass TestRobot : Robot {\n  @ContributesTo(scope = AppScope::class)\n  @Origin(value = TestRobot::class)\n  interface RobotContribution {\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @ComptimeOnly\n    abstract class BindsMirror {\n      private constructor() /* primary */ {\n        super/*Any*/()\n        /* <init>() */\n\n      }\n\n      @Binds\n      @IntoMap\n      @RobotKey(value = TestRobot::class)\n      @CallableMetadata(callableName = \"provideTestRobotIntoMap\", propertyName = \"\", startOffset = 206, endOffset = 264)\n      abstract fun provideTestRobotIntoMap1666857635_intomap(robot: TestRobot): Robot\n\n    }\n\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @MetroContribution(scope = AppScope::class)\n    interface MetroContributionToDevzacsweersmetroappScopeoxd1AHI : RobotContribution {\n    }\n\n    @Deprecated(message = \"This synthesized declaration should not be used directly\", level = DeprecationLevel.HIDDEN)\n    @CallableMetadata(callableName = \"provideTestRobot\", propertyName = \"\", startOffset = 206, endOffset = 264, newInstanceName = \"provideTestRobot\")\n    class ProvideTestRobotMetroFactory : Factory<TestRobot> {\n      private /* final field */ val instance: RobotContribution = instance\n      companion object Companion {\n        private constructor() /* primary */ {\n          super/*Any*/()\n          /* <init>() */\n\n        }\n\n        @HiddenFromObjC\n        fun create(instance: RobotContribution): Factory<TestRobot> {\n          return ProvideTestRobotMetroFactory(instance = instance)\n        }\n\n        @HiddenFromObjC\n        fun provideTestRobot(instance: RobotContribution): TestRobot {\n          return instance.provideTestRobot()\n        }\n\n      }\n\n      @HiddenFromObjC\n      private constructor(instance: RobotContribution) /* primary */ {\n        super/*Any*/()\n        /* <init>() */\n\n      }\n\n      @HiddenFromObjC\n      override operator fun invoke(): TestRobot {\n        return Companion.provideTestRobot(instance = <this>.#instance)\n      }\n\n      @HiddenFromObjC\n      fun mirrorFunction(): TestRobot {\n        return error(message = \"Never called\")\n      }\n\n    }\n\n    @Provides\n    fun provideTestRobot(): TestRobot {\n      return TestRobot()\n    }\n\n    @Binds\n    @IntoMap\n    @RobotKey(value = TestRobot::class)\n    @ComptimeOnly\n    fun provideTestRobotIntoMap(robot: TestRobot): Robot {\n      return error(message = \"Never called\")\n    }\n\n  }\n\n  constructor() /* primary */ {\n    super/*Any*/()\n    /* <init>() */\n\n  }\n\n}\n\n// FILE: comTestTestRobotRobotContributionAppScope.kt\npackage metro.hints\n\nfun AppScope(contributed: RobotContribution) {\n  return error(message = \"Never called\")\n}\n\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobotIr.fir.txt",
    "content": "FILE: defaultConstructorRobotIr.kt\n    package com.test\n\n    @R|software/amazon/app/platform/inject/robot/ContributesRobot|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public final class TestRobot : R|software/amazon/app/platform/robot/Robot| {\n        public constructor(): R|com/test/TestRobot| {\n            super<R|kotlin/Any|>()\n        }\n\n        @R|dev/zacsweers/metro/ContributesTo|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) @R|dev/zacsweers/metro/Origin|(value = <getClass>(Q|com/test/TestRobot|)) public abstract interface RobotContribution : R|kotlin/Any| {\n            @R|dev/zacsweers/metro/Provides|() public open fun provideTestRobot(): R|com/test/TestRobot|\n\n            @R|dev/zacsweers/metro/Binds|() @R|dev/zacsweers/metro/IntoMap|() @R|software/amazon/app/platform/renderer/metro/RobotKey|(value = <getClass>(Q|com/test/TestRobot|)) public open fun provideTestRobotIntoMap(robot: R|com/test/TestRobot|): R|software/amazon/app/platform/robot/Robot|\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/MetroContribution|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public abstract interface MetroContributionToDevzacsweersmetroappScopeoxd1AHI : R|com/test/TestRobot.RobotContribution| {\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/CallableMetadata|(callableName = String(provideTestRobot), propertyName = String(), startOffset = Int(206), endOffset = Int(264), newInstanceName = String(provideTestRobot)) public final class ProvideTestRobotMetroFactory : R|kotlin/Any| {\n                public final companion object Companion : R|kotlin/Any| {\n                    private constructor(): R|com/test/TestRobot.RobotContribution.ProvideTestRobotMetroFactory.Companion| {\n                        super<R|kotlin/Any|>()\n                    }\n\n                }\n\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public abstract class BindsMirror : R|kotlin/Any| {\n                private constructor(): R|com/test/TestRobot.RobotContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n                private constructor(): R|com/test/TestRobot.RobotContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n            }\n\n        }\n\n    }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesrobot/defaultConstructorRobotIr.kt",
    "content": "// RUN_PIPELINE_TILL: BACKEND\n// DUMP_KT_IR\npackage com.test\n\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.Robot\n\n@ContributesRobot(AppScope::class)\nclass TestRobot : Robot\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesscoped/defaultScoped.fir.txt",
    "content": "FILE: defaultScoped.kt\n    package com.test\n\n    public abstract interface SuperType : R|kotlin/Any| {\n    }\n    @R|dev/zacsweers/metro/Inject|() @R|dev/zacsweers/metro/SingleIn|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) @R|software/amazon/app/platform/inject/metro/ContributesScoped|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public final class TestClass : R|com/test/SuperType|, R|software/amazon/app/platform/scope/Scoped| {\n        public constructor(): R|com/test/TestClass| {\n            super<R|kotlin/Any|>()\n        }\n\n        @R|dev/zacsweers/metro/ContributesTo|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) @R|dev/zacsweers/metro/Origin|(value = <getClass>(Q|com/test/TestClass|)) public abstract interface ScopedContribution : R|kotlin/Any| {\n            @R|dev/zacsweers/metro/Binds|() public open fun bindSuperType(instance: R|com/test/TestClass|): R|com/test/SuperType|\n\n            @R|dev/zacsweers/metro/Binds|() @R|dev/zacsweers/metro/IntoSet|() @R|dev/zacsweers/metro/ForScope|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public open fun bindTestClassScoped(instance: R|com/test/TestClass|): R|software/amazon/app/platform/scope/Scoped|\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) @R|dev/zacsweers/metro/internal/MetroContribution|(scope = <getClass>(Q|dev/zacsweers/metro/AppScope|)) public abstract interface MetroContributionToDevzacsweersmetroappScopeoxd1AHI : R|com/test/TestClass.ScopedContribution| {\n            }\n\n            @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public abstract class BindsMirror : R|kotlin/Any| {\n                private constructor(): R|com/test/TestClass.ScopedContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n                private constructor(): R|com/test/TestClass.ScopedContribution.BindsMirror| {\n                    super<R|kotlin/Any|>()\n                }\n\n            }\n\n        }\n\n        @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object MetroFactory : R|kotlin/Any| {\n            private constructor(): R|com/test/TestClass.MetroFactory| {\n                super<R|kotlin/Any|>()\n            }\n\n        }\n\n    }\n"
  },
  {
    "path": "metro-extensions/contribute/impl-compiler-plugin/src/test/resources/dump/contributesscoped/defaultScoped.kt",
    "content": "// RUN_PIPELINE_TILL: BACKEND\npackage com.test\n\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scoped\n\ninterface SuperType\n\n@Inject\n@SingleIn(AppScope::class)\n@ContributesScoped(AppScope::class)\nclass TestClass : SuperType, Scoped\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: App Platform\nsite_url: https://amzn.github.io/app-platform/\nrepo_name: app-platform\nrepo_url: https://github.com/amzn/app-platform\nedit_uri: edit/main/docs/\nsite_description: \"A lightweight application framework for state and memory management suitable for Kotlin Multiplatform projects.\"\nremote_branch: gh-pages\ncopyright: 'Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.'\n\n# https://squidfunk.github.io/mkdocs-material/\ntheme:\n  name: 'material'\n  favicon: images/favicon.png\n  logo: images/logo.svg\n  palette:\n    - media: '(prefers-color-scheme: light)'\n      scheme: default\n      primary: blue grey\n      accent: blue\n      toggle:\n        icon: material/brightness-7\n        name: Switch to dark mode\n    - media: '(prefers-color-scheme: dark)'\n      scheme: slate\n      primary: blue grey\n      accent: blue\n      toggle:\n        icon: material/brightness-4\n        name: Switch to light mode\n  font:\n    text: 'Inter'\n    code: 'Fira Code'\n\n  features:\n    - content.code.annotate\n    - content.code.copy\n    - content.code.select\n    - content.tooltips\n    - navigation.tabs\n    - navigation.tabs.sticky\n    - navigation.top\n    - toc.follow\n    - toc.integrate\n    - content.tabs.link\n    - content.action.edit\n\n  icon:\n    edit: material/pencil\n\nmarkdown_extensions:\n  - smarty\n  - codehilite:\n      guess_lang: false\n  - footnotes\n  - def_list\n  - meta\n  - toc:\n      permalink: true\n  - pymdownx.betterem:\n      smart_enable: all\n  - pymdownx.caret\n  - pymdownx.details\n  - pymdownx.emoji\n  - pymdownx.inlinehilite\n  - pymdownx.magiclink\n  - pymdownx.smartsymbols\n  - pymdownx.snippets\n  - pymdownx.superfences:\n      custom_fences:\n        - name: mermaid\n          class: mermaid\n          format: !!python/name:pymdownx.superfences.fence_code_format\n  - pymdownx.tabbed:\n      alternate_style: true\n      slugify: !!python/object/apply:pymdownx.slugs.slugify\n        kwds:\n          case: lower\n  - tables\n  - admonition\n  - attr_list\n  - md_in_html\n\nnav:\n  - 'Introduction': index.md\n  - 'Setup': setup.md\n  - 'Module Structure': module-structure.md\n  - 'Scope': scope.md\n  - 'Presenter': presenter.md\n  - 'Renderer': renderer.md\n  - 'Template': template.md\n  - 'DI Framework': di.md\n  - 'Testing': testing.md\n  - 'FAQ': faq.md\n  - 'Changelog': changelog.md\n\nplugins:\n  - search\n  - social:\n      cards_layout_options:\n        logo: docs/images/app-platform-logo.png\n"
  },
  {
    "path": "presenter/public/api/android/public.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/BaseModel {\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/Presenter {\n\tpublic abstract fun getModel ()Lkotlinx/coroutines/flow/StateFlow;\n}\n\npublic final class software/amazon/app/platform/presenter/StateInKt {\n\tpublic static final fun stateInPresenter (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/StateFlow;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/template/ModelDelegate {\n\tpublic abstract fun delegate ()Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/template/Template : software/amazon/app/platform/presenter/BaseModel {\n}\n\n"
  },
  {
    "path": "presenter/public/api/desktop/public.api",
    "content": "public abstract interface class software/amazon/app/platform/presenter/BaseModel {\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/Presenter {\n\tpublic abstract fun getModel ()Lkotlinx/coroutines/flow/StateFlow;\n}\n\npublic final class software/amazon/app/platform/presenter/StateInKt {\n\tpublic static final fun stateInPresenter (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/StateFlow;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/template/ModelDelegate {\n\tpublic abstract fun delegate ()Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/template/Template : software/amazon/app/platform/presenter/BaseModel {\n}\n\n"
  },
  {
    "path": "presenter/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n"
  },
  {
    "path": "presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/BaseModel.kt",
    "content": "package software.amazon.app.platform.presenter\n\n/**\n * `Presenters` produce a stream of models that represents the state of this presenter. Concrete\n * model types are usually implemented as inner classes of the presenter, e.g.\n *\n * ```\n * class LoginPresenter : Presenter<Model> {\n *     data class Model(..) : BaseModel\n * }\n * ```\n *\n * Models must be immutable. Making models mutable and changing their state is an error and leads to\n * undesired results or crashes. While technically not required, common practice is to use a `data\n * class` for models.\n *\n * Using sealed hierarchies for models is common and allows to differentiate between states better:\n * ```\n * class LoginPresenter : Presenter<Model> {\n *     sealed interface Model : BaseModel {\n *         data object LoggedOut : Model\n *\n *         data class LoggedIn(\n *             val user: User,\n *         ) : Model\n *     }\n * }\n * ```\n *\n * State observers such as the UI layer communicate with the `Presenter` through events. Events are\n * returned through an `onEvent` callback in the model class and the `Presenter` handles the event:\n * ```\n * class LoginPresenter : Presenter<Model> {\n *     sealed interface Event {\n *         data object Logout : Event\n *\n *         data class ChangeName(\n *             val newName: String,\n *         ) : Event\n *     }\n *\n *     sealed interface Model : BaseModel {\n *         data object LoggedOut : Model\n *\n *         data class LoggedIn(\n *             val user: User,\n *             val onEvent: (Event) -> Unit\n *         ) : Model\n *     }\n * }\n * ```\n *\n * [BaseModel] is a marker interface for all models that can be used for extensions.\n */\npublic interface BaseModel\n"
  },
  {
    "path": "presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/Presenter.kt",
    "content": "package software.amazon.app.platform.presenter\n\nimport kotlinx.coroutines.flow.StateFlow\n\n/**\n * A presenter is the glue between our business logic and UI. A presenter injects service objects,\n * data repositories and other presenters to compute a model to represent what should be shown to\n * the user. Presenters are reactive. If its internal state or state of injected dependencies\n * change, then a new model is emitted.\n *\n * Presenters are composable, meaning that one presenter can inject other presenters and combine\n * their emitted models to a single model. This enables to implement model-driven navigation.\n *\n * By decoupling presenters from UI and Android components like Activities, Fragments and ViewModels\n * we make them easier to test. Business logic and UI integration can evolve independently.\n *\n * Events from the UI layer flow back to the presenter inform of callbacks provided by the model:\n * ```\n * class LoginPresenter : Presenter<Model> {\n *     data class Model(\n *         val name: String,\n *         val onEvent: (Event) -> Unit\n *     ) : BaseModel\n *\n *     sealed interface Event {\n *         object OnNameClick : Event\n *     }\n * }\n * ```\n *\n * Presenters can be implemented with any framework or by hand. Most commonly we use\n * `MoleculePresenter`, which can be transformed into a [Presenter] with `launchMoleculePresenter`.\n * A direct implementation of this interface could look like this:\n * ```\n * @Inject\n * class LoginPresenter(\n *     repository: Repository,\n *     presenterScope: PresenterCoroutineScope,\n * ) : Presenter<Model> {\n *\n *     override val model = repository.dataFlow\n *         .stateInPresenter(presenterScope) {\n *             Model(repository.dataFlow.value)\n *         }\n *\n *     data class Model(\n *         val data: Repository.Data\n *     ) : BaseModel\n * }\n * ```\n */\npublic interface Presenter<ModelT : BaseModel> {\n  /** The StateFlow of [ModelT] that the Presenter outputs. */\n  public val model: StateFlow<ModelT>\n}\n"
  },
  {
    "path": "presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/StateIn.kt",
    "content": "package software.amazon.app.platform.presenter\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.stateIn\n\n/**\n * Used within a [Presenter] to convert a [Flow] to a [StateFlow], e.g.\n *\n * ```\n * @Inject\n * class LoginPresenter(\n *     repository: Repository,\n *     presenterScope: PresenterCoroutineScope,\n * ) : Presenter<Model> {\n *\n *     override val model = repository.dataFlow\n *         .stateInPresenter(presenterScope) {\n *             Model(repository.dataFlow.value)\n *         }\n *\n *     data class Model(\n *         val data: Repository.Data\n *     ) : BaseModel\n * }\n * ```\n *\n * This is a convenience function and could be replaced by following line instead:\n * ```\n * .stateIn(presenterScope, SharingStarted.WhileSubscribed(), Model(repository.dataFlow.value))\n * ```\n *\n * [SharingStarted.WhileSubscribed] is chosen as default to cancel any upstream subscriptions as\n * soon as nobody collects the returned [StateFlow] anymore.\n */\npublic inline fun <T : BaseModel> Flow<T>.stateInPresenter(\n  coroutineScope: CoroutineScope,\n  crossinline default: () -> T,\n): StateFlow<T> {\n  return stateIn(coroutineScope, SharingStarted.WhileSubscribed(), default())\n}\n"
  },
  {
    "path": "presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/template/ModelDelegate.kt",
    "content": "package software.amazon.app.platform.presenter.template\n\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * Can be implemented by a [BaseModel] class to delegate to another [BaseModel]. This is helpful for\n * presenters whose only concern is navigation to say: render this model or render that model\n * instead, e.g.\n *\n * ```\n * data class NavigationModel(\n *     private val model1: BaseModel,\n *     private val model2: BaseModel,\n *     private val condition: Boolean,\n * ) : BaseModel, ModelDelegate {\n *     override fun delegate(): BaseModel = if (condition) model1 else model2\n * }\n */\npublic interface ModelDelegate {\n  /** Returns the Model to which this Model should delegate. */\n  public fun delegate(): BaseModel\n}\n"
  },
  {
    "path": "presenter/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/template/Template.kt",
    "content": "package software.amazon.app.platform.presenter.template\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.Presenter\n\n/**\n * Templates connect the presenter with the GUI side of the application and it’s what the UI\n * rendering understands. Practically, a template is one particular type of [BaseModel] that hosts\n * other models (a container of models). However, instead of using a weak type like\n * `List<BaseModel>`, a template carries some semantics around what content should be rendered, how\n * many layers there are and where each individual model should be displayed.\n *\n * The UI rendering understands what templates are, extracts the models, lays them out as needed for\n * a particular screen configuration and renders the content within the respective UI containers.\n *\n * For every single state change in the application a new template containing new, immutable models\n * is emitted. The UI renderer receives the update and renders the new template with the new content\n * on screen.\n *\n * For a [Presenter] to change the used template its model should implement [ModelDelegate] and wrap\n * its model in a [Template], e.g.\n *\n * ```\n * data class Model(\n *     val name: String\n * ) : BaseModel, ModelDelegate {\n *     override fun delegate(): BaseModel = AppTemplate.SomeTemplate(this)\n * }\n * ```\n *\n * This is only a marker interface, because concrete templates are app specific. The recommendation\n * is to introduce a sealed type with a predefined set of app specific templates, e.g.\n *\n * ```\n * sealed interface AmazonTemplate : Template {\n *   data class FullScreenTemplate(\n *     val model: BaseModel\n *   ) : AmazonTemplate\n *\n *   data class ListDetailTemplate(\n *     val listModel: BaseModel,\n *     val detailModel: BaseModel,\n *   ) : AmazonTemplate\n * }\n * ```\n */\npublic interface Template : BaseModel\n\n/**\n * Converts any [BaseModel] to a [Template]. If the given model implements [ModelDelegate] and\n * returns a [Template], then this template is used. Otherwise the result of [defaultTemplate] is\n * used.\n */\npublic inline fun <reified T : Template> BaseModel.toTemplate(\n  defaultTemplate: (BaseModel) -> T\n): T {\n  var model = this\n  while (model is ModelDelegate) {\n    val delegatedModel = model.delegate()\n    if (delegatedModel === model) {\n      break\n    }\n    model = delegatedModel\n  }\n\n  return if (model is T) {\n    model\n  } else {\n    defaultTemplate(model)\n  }\n}\n"
  },
  {
    "path": "presenter/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/StateInTest.kt",
    "content": "package software.amazon.app.platform.presenter\n\nimport app.cash.turbine.test\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.StateInTest.TestPresenter.Model\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass StateInTest {\n\n  @Test\n  fun `upstream collections are canceled 'model' is no longer collected`() =\n    runTest(UnconfinedTestDispatcher()) {\n      val flow = MutableSharedFlow<Int>()\n      val presenter = TestPresenter(flow, backgroundScope)\n\n      presenter.model.test {\n        assertThat(presenter.startCalled).isEqualTo(1)\n        assertThat(presenter.completeCalled).isEqualTo(0)\n\n        assertThat(awaitItem().number).isEqualTo(0)\n        flow.emit(1)\n        assertThat(awaitItem().number).isEqualTo(1)\n        flow.emit(2)\n        assertThat(awaitItem().number).isEqualTo(2)\n\n        assertThat(presenter.completeCalled).isEqualTo(0)\n      }\n\n      assertThat(presenter.startCalled).isEqualTo(1)\n      assertThat(presenter.completeCalled).isEqualTo(1)\n    }\n\n  @Test\n  fun `collecting multiple times returns the cached value from the StateFlow`() =\n    runTest(UnconfinedTestDispatcher()) {\n      val flow = MutableSharedFlow<Int>()\n      val presenter = TestPresenter(flow, backgroundScope)\n\n      repeat(10) { index ->\n        assertThat(presenter.startCalled).isEqualTo(index)\n        assertThat(presenter.completeCalled).isEqualTo(index)\n\n        presenter.model.test {\n          assertThat(presenter.startCalled).isEqualTo(index + 1)\n          assertThat(presenter.completeCalled).isEqualTo(index)\n\n          if (index == 0) {\n            // That's the default value.\n            assertThat(awaitItem().number).isEqualTo(0)\n\n            flow.emit(1)\n          }\n\n          // This value comes from the passed in Flow in the first iteration and\n          // then is cached in the StateFlow.\n          assertThat(awaitItem().number).isEqualTo(1)\n        }\n\n        assertThat(presenter.startCalled).isEqualTo(index + 1)\n        assertThat(presenter.completeCalled).isEqualTo(index + 1)\n      }\n    }\n\n  private class TestPresenter(flow: Flow<Int>, coroutineScope: CoroutineScope) : Presenter<Model> {\n    var startCalled = 0\n      private set\n\n    var completeCalled = 0\n      private set\n\n    override val model: StateFlow<Model> =\n      flow\n        .map { Model(it) }\n        .onStart { startCalled++ }\n        .onCompletion { completeCalled++ }\n        .stateInPresenter(coroutineScope) { Model(0) }\n\n    data class Model(val number: Int) : BaseModel\n  }\n}\n"
  },
  {
    "path": "presenter/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/template/TemplateTest.kt",
    "content": "package software.amazon.app.platform.presenter.template\n\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport assertk.assertions.isSameInstanceAs\nimport kotlin.test.Test\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass TemplateTest {\n\n  sealed interface TestTemplate : Template {\n    data class FullScreen(val model: BaseModel) : TestTemplate\n  }\n\n  @Test\n  fun `toTemplate returns the inner most template with delegated models`() {\n    val delegatedModel = object : BaseModel {}\n    val innerTemplate = TestTemplate.FullScreen(delegatedModel)\n\n    class InnerModel : BaseModel, ModelDelegate {\n      override fun delegate(): BaseModel = innerTemplate\n    }\n\n    class OuterModel : BaseModel, ModelDelegate {\n      override fun delegate(): BaseModel = InnerModel()\n    }\n\n    val result = OuterModel().toTemplate { throw NotImplementedError() }\n    assertThat(result).isSameInstanceAs(innerTemplate)\n  }\n\n  @Test\n  fun `toTemplate returns the default template`() {\n    val model = object : BaseModel {}\n    val result = model.toTemplate { TestTemplate.FullScreen(it) }\n    assertThat(result).isInstanceOf<TestTemplate.FullScreen>()\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/impl/api/android/impl.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactory : software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlin/jvm/functions/Function0;)V\n\tpublic fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryComponent {\n\tpublic fun provideAndroidMoleculeScopeFactory (Lkotlin/jvm/functions/Function0;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryComponent$DefaultImpls {\n\tpublic static fun provideAndroidMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryComponent;Lkotlin/jvm/functions/Function0;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph {\n\tpublic fun provideAndroidMoleculeScopeFactory (Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$DefaultImpls {\n\tpublic static fun provideAndroidMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope : software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope$DefaultImpls {\n\tpublic static fun provideAndroidMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$ProvideAndroidMoleculeScopeFactoryMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$ProvideAndroidMoleculeScopeFactoryMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n\tpublic final fun mirrorFunction (Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph$ProvideAndroidMoleculeScopeFactoryMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideAndroidMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent {\n\tpublic fun provideDefaultBackGestureDispatcherPresenter ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph {\n\tpublic fun provideDefaultBackGestureDispatcherPresenter ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope : software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n\tpublic final fun mirrorFunction ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\n"
  },
  {
    "path": "presenter-molecule/impl/api/desktop/impl.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactory : software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlin/jvm/functions/Function0;)V\n\tpublic fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryComponent {\n\tpublic fun provideDesktopMoleculeScopeFactory (Lkotlin/jvm/functions/Function0;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryComponent$DefaultImpls {\n\tpublic static fun provideDesktopMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryComponent;Lkotlin/jvm/functions/Function0;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph {\n\tpublic fun provideDesktopMoleculeScopeFactory (Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$DefaultImpls {\n\tpublic static fun provideDesktopMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope : software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope$DefaultImpls {\n\tpublic static fun provideDesktopMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$MetroContributionToMetroAppScope;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$ProvideDesktopMoleculeScopeFactoryMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$ProvideDesktopMoleculeScopeFactoryMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n\tpublic final fun mirrorFunction (Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph$ProvideDesktopMoleculeScopeFactoryMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideDesktopMoleculeScopeFactory (Lsoftware/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactoryGraph;Ldev/zacsweers/metro/Provider;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent {\n\tpublic fun provideDefaultBackGestureDispatcherPresenter ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterComponent;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph {\n\tpublic fun provideDefaultBackGestureDispatcherPresenter ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope : software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph {\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope$DefaultImpls {\n\tpublic static fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$MetroContributionToMetroAppScope;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory : dev/zacsweers/metro/internal/Factory {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory$Companion;\n\tpublic synthetic fun <init> (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;Lkotlin/jvm/internal/DefaultConstructorMarker;)V\n\tpublic synthetic fun invoke ()Ljava/lang/Object;\n\tpublic final fun invoke ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n\tpublic final fun mirrorFunction ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph$ProvideDefaultBackGestureDispatcherPresenterMetroFactory$Companion {\n\tpublic final fun create (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Ldev/zacsweers/metro/internal/Factory;\n\tpublic final fun provideDefaultBackGestureDispatcherPresenter (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenterGraph;)Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\n"
  },
  {
    "path": "presenter-molecule/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enableMetro true\n    enableMolecule true\n    enablePublishing true\n\n    // Had to be disabled because KSP2 is disabled, which produces warnings for the Kotlin compilation.\n    kotlinWarningsAsErrors false\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n\n    commonTestImplementation project(':kotlin-inject:impl')\n    commonTestImplementation libs.kotlin.inject.runtime.kmp\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/androidMain/kotlin/software/amazon/app/platform/presenter/molecule/AndroidMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.AndroidUiDispatcher\nimport app.cash.molecule.RecompositionMode\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport kotlinx.coroutines.CoroutineScope\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Runs `MoleculePresenters` on the main thread provided by [PresenterCoroutineScope] and recomposes\n * only once per screen refresh when needed.\n */\npublic class AndroidMoleculeScopeFactory(coroutineScopeFactory: () -> CoroutineScope) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    coroutineContext = AndroidUiDispatcher.Main,\n    recompositionMode = RecompositionMode.ContextClock,\n  )\n\n/** Provides the [AndroidMoleculeScopeFactory] in the kotlin-inject graph. */\n@KiContributesTo(KiAppScope::class)\npublic interface AndroidMoleculeScopeFactoryComponent {\n  /** Provides the [AndroidMoleculeScopeFactory] in the kotlin-inject graph as a singleton. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideAndroidMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n  ): MoleculeScopeFactory = AndroidMoleculeScopeFactory(coroutineScopeFactory)\n}\n\n/** Provides the [AndroidMoleculeScopeFactory] in the Metro graph. */\n@MetroContributesTo(MetroAppScope::class)\npublic interface AndroidMoleculeScopeFactoryGraph {\n  /** Provides the [AndroidMoleculeScopeFactory] in the Metro graph as a singleton. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideAndroidMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n  ): MoleculeScopeFactory = AndroidMoleculeScopeFactory { coroutineScopeFactory() }\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/DefaultMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.plus\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\n\n/**\n * Creates new [MoleculeScope]s with the given defaults. When calling [createMoleculeScope], then\n * [coroutineScopeFactory] is used as default scope. [coroutineContext] allows you to add additional\n * elements to created scopes. [recompositionMode] is used for launching [MoleculePresenter]s.\n */\ninternal class DefaultMoleculeScopeFactory(\n  @PresenterCoroutineScope private val coroutineScopeFactory: () -> CoroutineScope,\n  private val coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  private val recompositionMode: RecompositionMode,\n) : MoleculeScopeFactory {\n\n  override fun createMoleculeScope(): MoleculeScope =\n    createMoleculeScopeFromCoroutineScope(coroutineScopeFactory())\n\n  override fun createMoleculeScopeFromCoroutineScope(\n    coroutineScope: CoroutineScope,\n    coroutineContext: CoroutineContext,\n  ): MoleculeScope =\n    MoleculeScope(\n      coroutineScope = coroutineScope + this.coroutineContext + coroutineContext,\n      recompositionMode = recompositionMode,\n    )\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/DefaultBackGestureDispatcherPresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Provides a [BackGestureDispatcherPresenter] that maintains a list of registered back gesture\n * listeners and forwards events to the last registered callback. See\n * [BackGestureDispatcherPresenter] for more details.\n */\n@KiContributesTo(KiAppScope::class)\npublic interface DefaultBackGestureDispatcherPresenterComponent {\n  /** Provides a [BackGestureDispatcherPresenter] as singleton in the kotlin-inject graph. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideDefaultBackGestureDispatcherPresenter(): BackGestureDispatcherPresenter =\n    BackGestureDispatcherPresenter.createNewInstance()\n}\n\n/**\n * Provides a [BackGestureDispatcherPresenter] that maintains a list of registered back gesture\n * listeners and forwards events to the last registered callback. See\n * [BackGestureDispatcherPresenter] for more details.\n */\n@MetroContributesTo(MetroAppScope::class)\npublic interface DefaultBackGestureDispatcherPresenterGraph {\n  /** Provides a [BackGestureDispatcherPresenter] as singleton in the Metro graph. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideDefaultBackGestureDispatcherPresenter(): BackGestureDispatcherPresenter =\n    BackGestureDispatcherPresenter.createNewInstance()\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/DefaultMoleculeScopeFactoryTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\n\nclass DefaultMoleculeScopeFactoryTest {\n  @Test\n  fun `the default provided coroutine scope is used when creating a new MoleculeScope`() {\n    val factory = factory(scope = CoroutineScope(CoroutineName(\"abc\")))\n    val moleculeScope = factory.createMoleculeScope()\n\n    assertThat(moleculeScope.name).isEqualTo(\"abc\")\n  }\n\n  @Test\n  fun `the given coroutine scope is used when creating a new MoleculeScope`() {\n    val factory = factory(scope = CoroutineScope(CoroutineName(\"abc\")))\n    val moleculeScope =\n      factory.createMoleculeScopeFromCoroutineScope(CoroutineScope(CoroutineName(\"def\")))\n\n    assertThat(moleculeScope.name).isEqualTo(\"def\")\n  }\n\n  @Test\n  fun `default coroutine context elements are applied when creating a new MoleculeScope`() {\n    val factory = factory(coroutineContext = CoroutineName(\"abc\"))\n    val moleculeScope = factory.createMoleculeScope()\n\n    assertThat(moleculeScope.name).isEqualTo(\"abc\")\n  }\n\n  @Test\n  fun `the given coroutine context elements override default elements when creating a new MoleculeScope`() {\n    val factory = factory(coroutineContext = CoroutineName(\"abc\"))\n    val moleculeScope =\n      factory.createMoleculeScopeFromCoroutineScope(\n        coroutineScope = CoroutineScope(EmptyCoroutineContext),\n        coroutineContext = CoroutineName(\"def\"),\n      )\n\n    assertThat(moleculeScope.name).isEqualTo(\"def\")\n  }\n\n  private fun factory(\n    scope: CoroutineScope = CoroutineScope(EmptyCoroutineContext),\n    coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  ) =\n    DefaultMoleculeScopeFactory(\n      coroutineScopeFactory = { scope },\n      coroutineContext = coroutineContext,\n      recompositionMode = RecompositionMode.Immediate,\n    )\n\n  private val MoleculeScope.name: String\n    get() = requireNotNull(coroutineScope.coroutineContext[CoroutineName.Key]?.name)\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/KotlinInjectInjectionTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport me.tatarka.inject.annotations.Component\nimport me.tatarka.inject.annotations.Inject\nimport me.tatarka.inject.annotations.KmpComponentCreate\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.app.platform.presenter.PresenterCoroutineScopeComponent\nimport software.amazon.app.platform.scope.coroutine.MainCoroutineDispatcher\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\nclass KotlinInjectInjectionTest {\n\n  @Test\n  fun `the PresenterCoroutineScope can be injected lazily`() {\n    val testScope = CoroutineScope(CoroutineName(\"TestName\"))\n    val testDispatcher = Dispatchers.Default\n\n    val component = createTestComponent(testScope, testDispatcher)\n\n    val moleculeScope = component.moleculeScopeFactory.createMoleculeScope()\n\n    assertThat(moleculeScope.coroutineScope.coroutineContext[CoroutineName.Key]?.name)\n      .isEqualTo(\"TestName\")\n\n    moleculeScope.cancel()\n  }\n}\n\n@Component\n@SingleIn(AppScope::class)\nabstract class KotlinInjectTestComponent(\n  private val coroutineScope: CoroutineScope,\n  private val coroutineDispatcher: CoroutineDispatcher,\n) : PresenterCoroutineScopeComponent {\n  abstract val moleculeScopeFactory: MoleculeScopeFactory\n\n  @Provides\n  @ForScope(AppScope::class)\n  fun provideAppScopeCoroutineScope(): CoroutineScope = coroutineScope\n\n  @Provides\n  @MainCoroutineDispatcher\n  fun provideMainCoroutineDispatcher(): CoroutineDispatcher = coroutineDispatcher\n\n  @Provides\n  fun provideMoleculeScopeFactory(\n    factory: KotlinInjectTestMoleculeScopeFactory\n  ): MoleculeScopeFactory = factory\n}\n\n@Inject\n@SingleIn(AppScope::class)\nclass KotlinInjectTestMoleculeScopeFactory(\n  @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    coroutineContext = EmptyCoroutineContext,\n    recompositionMode = RecompositionMode.Immediate,\n  )\n\n@KmpComponentCreate\nexpect fun createTestComponent(\n  coroutineScope: CoroutineScope,\n  coroutineDispatcher: CoroutineDispatcher,\n): KotlinInjectTestComponent\n"
  },
  {
    "path": "presenter-molecule/impl/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/MetroInjectionTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.Binds\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport dev.zacsweers.metro.createGraphFactory\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\n\nclass MetroInjectionTest {\n\n  @Test\n  fun `the PresenterCoroutineScope can be injected lazily`() {\n    val testScope = CoroutineScope(CoroutineName(\"TestName\"))\n\n    val component = createGraphFactory<MetroTestComponent.Factory>().create(testScope)\n\n    val moleculeScope = component.moleculeScopeFactory.createMoleculeScope()\n\n    assertThat(moleculeScope.coroutineScope.coroutineContext[CoroutineName.Key]?.name)\n      .isEqualTo(\"TestName\")\n\n    moleculeScope.cancel()\n  }\n}\n\n@Suppress(\"unused\")\n@DependencyGraph\n@SingleIn(AppScope::class)\ninterface MetroTestComponent {\n\n  @DependencyGraph.Factory\n  fun interface Factory {\n    fun create(\n      @Provides @PresenterCoroutineScope coroutineScope: CoroutineScope\n    ): MetroTestComponent\n  }\n\n  val moleculeScopeFactory: MoleculeScopeFactory\n\n  @Binds val MetroTestMoleculeScopeFactory.bind: MoleculeScopeFactory\n}\n\n@Inject\n@SingleIn(AppScope::class)\nclass MetroTestMoleculeScopeFactory(\n  @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = { coroutineScopeFactory() },\n    coroutineContext = EmptyCoroutineContext,\n    recompositionMode = RecompositionMode.Immediate,\n  )\n"
  },
  {
    "path": "presenter-molecule/impl/src/desktopMain/kotlin/software/amazon/app/platform/presenter/molecule/DesktopMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport kotlinx.coroutines.CoroutineScope\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Runs `MoleculePresenters` on the main thread provided by [PresenterCoroutineScope] and recomposes\n * as fast as possible.\n */\npublic class DesktopMoleculeScopeFactory(coroutineScopeFactory: () -> CoroutineScope) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    recompositionMode = RecompositionMode.Immediate,\n  )\n\n/** Provides the [DesktopMoleculeScopeFactory] in the kotlin-inject graph. */\n@KiContributesTo(KiAppScope::class)\npublic interface DesktopMoleculeScopeFactoryComponent {\n  /** Provides the [DesktopMoleculeScopeFactory] in the kotlin-inject graph as a singleton. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideDesktopMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n  ): MoleculeScopeFactory = DesktopMoleculeScopeFactory(coroutineScopeFactory)\n}\n\n/** Provides the [DesktopMoleculeScopeFactory] in the Metro graph. */\n@MetroContributesTo(MetroAppScope::class)\npublic interface DesktopMoleculeScopeFactoryGraph {\n  /** Provides the [DesktopMoleculeScopeFactory] in the Metro graph as a singleton. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideDesktopMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n  ): MoleculeScopeFactory = DesktopMoleculeScopeFactory { coroutineScopeFactory() }\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/iosMain/kotlin/software/amazon/app/platform/presenter/molecule/IosMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.DisplayLinkClock\nimport app.cash.molecule.RecompositionMode\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport kotlinx.coroutines.CoroutineScope\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Runs `MoleculePresenters` on the main thread provided by [PresenterCoroutineScope] and recomposes\n * only once per screen refresh when needed.\n */\npublic class IosMoleculeScopeFactory(coroutineScopeFactory: () -> CoroutineScope) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    coroutineContext = DisplayLinkClock,\n    recompositionMode = RecompositionMode.ContextClock,\n  )\n\n/** Provides the [IosMoleculeScopeFactory] in the kotlin-inject graph. */\n@KiContributesTo(KiAppScope::class)\npublic interface IosMoleculeScopeFactoryComponent {\n  /** Provides the [IosMoleculeScopeFactory] in the kotlin-inject graph as a singleton. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideIosMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n  ): MoleculeScopeFactory = IosMoleculeScopeFactory(coroutineScopeFactory)\n}\n\n/** Provides the [IosMoleculeScopeFactory] in the Metro graph. */\n@MetroContributesTo(MetroAppScope::class)\npublic interface IosMoleculeScopeFactoryGraph {\n  /** Provides the [IosMoleculeScopeFactory] in the Metro graph as a singleton. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideIosMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n  ): MoleculeScopeFactory = IosMoleculeScopeFactory { coroutineScopeFactory() }\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/linuxMain/kotlin/software/amazon/app/platform/presenter/molecule/LinuxMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport kotlinx.coroutines.CoroutineScope\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Runs `MoleculePresenters` on the main thread provided by [PresenterCoroutineScope] and recomposes\n * as fast as possible.\n */\npublic class LinuxMoleculeScopeFactory(coroutineScopeFactory: () -> CoroutineScope) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    recompositionMode = RecompositionMode.Immediate,\n  )\n\n/** Provides the [LinuxMoleculeScopeFactory] in the kotlin-inject graph. */\n@KiContributesTo(KiAppScope::class)\npublic interface LinuxMoleculeScopeFactoryComponent {\n  /** Provides the [LinuxMoleculeScopeFactory] in the kotlin-inject graph as a singleton. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideLinuxMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n  ): MoleculeScopeFactory = LinuxMoleculeScopeFactory(coroutineScopeFactory)\n}\n\n/** Provides the [LinuxMoleculeScopeFactory] in the Metro graph. */\n@MetroContributesTo(MetroAppScope::class)\npublic interface LinuxMoleculeScopeFactoryGraph {\n  /** Provides the [LinuxMoleculeScopeFactory] in the Metro graph as a singleton. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideLinuxMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n  ): MoleculeScopeFactory = LinuxMoleculeScopeFactory { coroutineScopeFactory() }\n}\n"
  },
  {
    "path": "presenter-molecule/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/presenter/molecule/WasmJsMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport dev.zacsweers.metro.AppScope as MetroAppScope\nimport dev.zacsweers.metro.ContributesTo as MetroContributesTo\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides as MetroProvides\nimport dev.zacsweers.metro.SingleIn as MetroSingleIn\nimport kotlinx.coroutines.CoroutineScope\nimport me.tatarka.inject.annotations.Provides as KiProvides\nimport software.amazon.app.platform.presenter.PresenterCoroutineScope\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope as KiAppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo as KiContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn as KiSingleIn\n\n/**\n * Runs `MoleculePresenters` on the main thread provided by [PresenterCoroutineScope] and recomposes\n * as fast as possible.\n */\npublic class WasmJsMoleculeScopeFactory(coroutineScopeFactory: () -> CoroutineScope) :\n  MoleculeScopeFactory by DefaultMoleculeScopeFactory(\n    coroutineScopeFactory = coroutineScopeFactory,\n    recompositionMode = RecompositionMode.Immediate,\n  )\n\n/** Provides the [WasmJsMoleculeScopeFactory] in the kotlin-inject graph. */\n@KiContributesTo(KiAppScope::class)\npublic interface WasmJsMoleculeScopeFactoryComponent {\n  /** Provides the [WasmJsMoleculeScopeFactory] in the kotlin-inject graph as a singleton. */\n  @KiProvides\n  @KiSingleIn(KiAppScope::class)\n  public fun provideWasmJsMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: () -> CoroutineScope\n  ): MoleculeScopeFactory = WasmJsMoleculeScopeFactory(coroutineScopeFactory)\n}\n\n/** Provides the [WasmJsMoleculeScopeFactory] in the Metro graph. */\n@MetroContributesTo(MetroAppScope::class)\npublic interface WasmJsMoleculeScopeFactoryGraph {\n  /** Provides the [WasmJsMoleculeScopeFactory] in the Metro graph as a singleton. */\n  @MetroProvides\n  @MetroSingleIn(MetroAppScope::class)\n  public fun provideWasmJsMoleculeScopeFactory(\n    @PresenterCoroutineScope coroutineScopeFactory: Provider<CoroutineScope>\n  ): MoleculeScopeFactory = WasmJsMoleculeScopeFactory { coroutineScopeFactory() }\n}\n"
  },
  {
    "path": "presenter-molecule/public/api/android/public.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/LaunchMoleculePresenterKt {\n\tpublic static final fun launchMoleculePresenter (Lkotlinx/coroutines/CoroutineScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/StateFlow;Lapp/cash/molecule/RecompositionMode;)Lsoftware/amazon/app/platform/presenter/Presenter;\n\tpublic static final fun launchMoleculePresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Ljava/lang/Object;)Lsoftware/amazon/app/platform/presenter/Presenter;\n\tpublic static final fun launchMoleculePresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/StateFlow;)Lsoftware/amazon/app/platform/presenter/Presenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/MoleculePresenter {\n\tpublic abstract fun present (Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/MoleculeScope {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/molecule/RecompositionMode;)V\n\tpublic final fun cancel ()V\n\tpublic final fun getCoroutineScope ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun getRecompositionMode ()Lapp/cash/molecule/RecompositionMode;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic abstract fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic abstract fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic static synthetic fun createMoleculeScopeFromCoroutineScope$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory$DefaultImpls {\n\tpublic static synthetic fun createMoleculeScopeFromCoroutineScope$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/ReturningCompositionLocalProviderKt {\n\tpublic static final fun returningCompositionLocalProvider ([Landroidx/compose/runtime/ProvidedValue;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter$Companion;\n\tpublic static final field EDGE_LEFT I\n\tpublic static final field EDGE_RIGHT I\n\tpublic fun <init> (FFFI)V\n\tpublic final fun getProgress ()F\n\tpublic final fun getSwipeEdge ()I\n\tpublic final fun getTouchX ()F\n\tpublic final fun getTouchY ()F\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter$Companion {\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter$Companion;\n\tpublic abstract fun PredictiveBackHandlerPresenter (ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V\n\tpublic abstract fun getListenersCount ()Lkotlinx/coroutines/flow/StateFlow;\n\tpublic abstract fun onPredictiveBack (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter$Companion {\n\tpublic final fun createNewInstance ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterKt {\n\tpublic static final fun BackHandlerPresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;ZLkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V\n\tpublic static final fun PredictiveBackHandlerPresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V\n\tpublic static final fun getLocalBackGestureDispatcherPresenter ()Landroidx/compose/runtime/ProvidableCompositionLocal;\n}\n\n"
  },
  {
    "path": "presenter-molecule/public/api/desktop/public.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/LaunchMoleculePresenterKt {\n\tpublic static final fun launchMoleculePresenter (Lkotlinx/coroutines/CoroutineScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/StateFlow;Lapp/cash/molecule/RecompositionMode;)Lsoftware/amazon/app/platform/presenter/Presenter;\n\tpublic static final fun launchMoleculePresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Ljava/lang/Object;)Lsoftware/amazon/app/platform/presenter/Presenter;\n\tpublic static final fun launchMoleculePresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/StateFlow;)Lsoftware/amazon/app/platform/presenter/Presenter;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/MoleculePresenter {\n\tpublic abstract fun present (Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/MoleculeScope {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlinx/coroutines/CoroutineScope;Lapp/cash/molecule/RecompositionMode;)V\n\tpublic final fun cancel ()V\n\tpublic final fun getCoroutineScope ()Lkotlinx/coroutines/CoroutineScope;\n\tpublic final fun getRecompositionMode ()Lapp/cash/molecule/RecompositionMode;\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic abstract fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic abstract fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic static synthetic fun createMoleculeScopeFromCoroutineScope$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory$DefaultImpls {\n\tpublic static synthetic fun createMoleculeScopeFromCoroutineScope$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScopeFactory;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/ReturningCompositionLocalProviderKt {\n\tpublic static final fun returningCompositionLocalProvider ([Landroidx/compose/runtime/ProvidedValue;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)Lsoftware/amazon/app/platform/presenter/BaseModel;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter {\n\tpublic static final field $stable I\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter$Companion;\n\tpublic static final field EDGE_LEFT I\n\tpublic static final field EDGE_RIGHT I\n\tpublic fun <init> (FFFI)V\n\tpublic final fun getProgress ()F\n\tpublic final fun getSwipeEdge ()I\n\tpublic final fun getTouchX ()F\n\tpublic final fun getTouchY ()F\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter$Companion {\n}\n\npublic abstract interface class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter$Companion;\n\tpublic abstract fun PredictiveBackHandlerPresenter (ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V\n\tpublic abstract fun getListenersCount ()Lkotlinx/coroutines/flow/StateFlow;\n\tpublic abstract fun onPredictiveBack (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter$Companion {\n\tpublic final fun createNewInstance ()Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterKt {\n\tpublic static final fun BackHandlerPresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;ZLkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V\n\tpublic static final fun PredictiveBackHandlerPresenter (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V\n\tpublic static final fun getLocalBackGestureDispatcherPresenter ()Landroidx/compose/runtime/ProvidableCompositionLocal;\n}\n\n"
  },
  {
    "path": "presenter-molecule/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableMolecule true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonMainApi libs.androidx.annotations\n    commonTestImplementation project(':internal:testing')\n    commonTestImplementation project(':presenter-molecule:testing')\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/LaunchMoleculePresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport app.cash.molecule.RecompositionMode\nimport app.cash.molecule.launchMolecule\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.Presenter\n\n/**\n * Launch a coroutine into this [CoroutineScope] which will continually recompose\n * [MoleculePresenter.present] to produce a [StateFlow]. The [StateFlow] will be provided by the\n * returned [Presenter].\n */\npublic fun <InputT : Any, ModelT : BaseModel> CoroutineScope.launchMoleculePresenter(\n  presenter: MoleculePresenter<InputT, ModelT>,\n  input: StateFlow<InputT>,\n  recompositionMode: RecompositionMode,\n): Presenter<ModelT> {\n  return object : Presenter<ModelT> {\n    override val model: StateFlow<ModelT> =\n      launchMolecule(recompositionMode) {\n        val inputElement by input.collectAsState()\n        presenter.present(inputElement)\n      }\n  }\n}\n\n/**\n * Launch a coroutine into this [MoleculeScope] which will continually recompose\n * [MoleculePresenter.present] to produce a [StateFlow]. The [StateFlow] will be provided by the\n * returned [Presenter].\n */\npublic fun <InputT : Any, ModelT : BaseModel> MoleculeScope.launchMoleculePresenter(\n  presenter: MoleculePresenter<InputT, ModelT>,\n  input: StateFlow<InputT>,\n): Presenter<ModelT> =\n  coroutineScope.launchMoleculePresenter(\n    presenter = presenter,\n    input = input,\n    recompositionMode = recompositionMode,\n  )\n\n/**\n * Launch a coroutine into this [MoleculeScope] which will continually recompose\n * [MoleculePresenter.present] to produce a [StateFlow]. The [StateFlow] will be provided by the\n * returned [Presenter].\n */\npublic fun <InputT : Any, ModelT : BaseModel> MoleculeScope.launchMoleculePresenter(\n  presenter: MoleculePresenter<InputT, ModelT>,\n  input: InputT,\n): Presenter<ModelT> =\n  launchMoleculePresenter(presenter = presenter, input = MutableStateFlow(input))\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculePresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.Composable\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.Presenter\n\n/**\n * `MoleculePresenter` is a presenter that uses Compose core (don't confuse Compose core with\n * Compose UI, Compose UI is built on-top of Compose core) to create a [StateFlow] of models.\n * [Molecule](https://github.com/cashapp/molecule) is leveraged to turn the composable function\n * [present] into a `StateFlow<ModelT>`. By leveraging Compose we can turn reactive code built\n * on-top of Flow and its operators into imperative code using language statements like\n * `if-then-else`, `when` or `try-catch`.\n *\n * Note that [MoleculePresenter] itself doesn't extend the [Presenter] interface. Use\n * [launchMoleculePresenter] to transform a [MoleculePresenter] to a [Presenter]. To use another\n * [Presenter] within a [MoleculePresenter] you can inject the presenter directly and subscribe to\n * changes of [Presenter.model].\n *\n * `MoleculePresenters` typically are stateless, meaning they have no properties and are not marked\n * as singletons. If no input is used, then use [Unit] for [InputT]. A typical implementation may\n * look like:\n * ```\n * @Inject\n * class MyPresenter : MoleculePresenter<Unit, Model> {\n *\n *     @Composable\n *     override fun present(input: Unit): Model {\n *         ...\n *         return Model(...)\n *     }\n *\n *     data class Model(...) : BaseModel\n *\n * }\n * ```\n *\n * If a consumer like the UI layer should be able to send events back to the presenter, then the\n * [BaseModel] implementation typically has an `onEvent` callback lambda as last parameter:\n * ```\n * @Inject\n * class MyPresenter : MoleculePresenter<Unit, Model> {\n *\n *     @Composable\n *     override fun present(input: Unit): Model {\n *         var myData by remember { mutableStateOf(\"\") }\n *\n *         ...\n *         return Model(...) { event ->\n *             when (event) {\n *                 is MyEvent -> {\n *                     // This will trigger recomposition and a new Model will be produced.\n *                     myData = event.moreData\n *                 }\n *             }\n *         }\n *     }\n *\n *     data class Model(\n *         ...,\n *         onEvent: (Event) -> Unit,\n *     ) : BaseModel\n *\n *     sealed interface Event {\n *         data class MyEvent(\n *             val moreData: String,\n *         ) : Event\n *     }\n * }\n * ```\n *\n * `MoleculePresenters` can host and embed other child presenters. To invoke them call [present]\n * inline. This is also the chance to pass inputs from one presenter to another. To avoid\n * instantiating presenters eagerly and only when they're actually needed, it's recommended to\n * inject them lazily:\n * ```\n * @Inject\n * class MyPresenter(\n *     private val userPresenter: () -> UserPresenter,\n *     private val loginPresenter: () -> LoginPresenter,\n * ) : MoleculePresenter<Unit, Model> {\n *\n *     @Composable\n *     override fun present(input: Unit): Model {\n *         if (condition) {\n *             val userPresenter = remember { userPresenter() }\n *             val userPresenterModel = userPresenter.present(Unit)\n *             // Use the returned model for further evaluation.\n *         } else {\n *             val loginPresenter = remember { loginPresenter() }\n *             val loginPresenterModel = loginPresenter.present(\"String input\")\n *             // Use the returned model for further evaluation.\n *         }\n *\n *         return Model(...)\n *     }\n *\n *     data class Model(...) : BaseModel\n *\n * }\n * ```\n */\npublic interface MoleculePresenter<InputT : Any, ModelT : BaseModel> {\n  /** Called every time state of the composable changes to produce a new model. */\n  @Composable public fun present(input: InputT): ModelT\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculeScope.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.cancel\n\n/**\n * A pair of a [CoroutineScope] and a Compose [RecompositionMode] to make it easier to launch a\n * [MoleculePresenter]. Once a [MoleculeScope] is no longer used it must be canceled through\n * [cancel] otherwise [coroutineScope] will leak.\n */\npublic class MoleculeScope(\n  /** The CoroutineScope which this MoleculeScope should use to run @Composable functions. */\n  public val coroutineScope: CoroutineScope,\n\n  /**\n   * The [RecompositionMode] which this MoleculeScope should use to determine how frequently new\n   * models are computed.\n   */\n  public val recompositionMode: RecompositionMode,\n) {\n\n  /** Cancel the provided [coroutineScope]. */\n  public fun cancel(): Unit = coroutineScope.cancel()\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineScope\n\n/** Creates new [MoleculeScope] instances. */\npublic interface MoleculeScopeFactory {\n\n  /**\n   * Creates a new [MoleculeScope]. Once the returned scope is not needed anymore, you must call\n   * [MoleculeScope.cancel] to avoid memory leaks.\n   */\n  public fun createMoleculeScope(): MoleculeScope\n\n  /**\n   * Wraps the given [coroutineScope] in a [MoleculeScope] and applies platform specific defaults in\n   * order to run Molecule. [coroutineContext] allows you to add additional elements to the used\n   * [CoroutineScope] and override the platform defaults if necessary.\n   */\n  public fun createMoleculeScopeFromCoroutineScope(\n    coroutineScope: CoroutineScope,\n    coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  ): MoleculeScope\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/ReturningCompositionLocalProvider.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.InternalComposeApi\nimport androidx.compose.runtime.ProvidedValue\nimport androidx.compose.runtime.currentComposer\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * Similar to `CompositionLocalProvider` offered by the Compose runtime itself, but allows us to\n * return a result rather than returning `Unit`.\n */\n@Composable\n@OptIn(InternalComposeApi::class)\n@Suppress(\"FunctionNaming\")\npublic fun <ModelT : BaseModel> returningCompositionLocalProvider(\n  vararg values: ProvidedValue<*>,\n  content: @Composable () -> ModelT,\n): ModelT {\n  currentComposer.startProviders(values)\n  val model = content()\n  currentComposer.endProviders()\n  return model\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/BackEventPresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.annotation.FloatRange\nimport androidx.annotation.IntRange\n\n/**\n * Object used to report back gesture progress. Holds information about the touch event, swipe\n * direction and the animation progress that predictive back animations should seek to.\n */\n// Note, this is a copy from\n// https://github.com/JetBrains/compose-multiplatform-core/blob/244635e202f9aa734bd8c86bd1748a9065ecd818/compose/ui/ui-backhandler/src/commonMain/kotlin/androidx/compose/ui/backhandler/BackEventCompat.kt\n//\n// By copying the class we don't need to expose the Compose Multiplatform APIs from the presenter\n// artifact, which is independent of the UI layer implementation.\npublic class BackEventPresenter(\n  /**\n   * Absolute X location of the touch point of this event in the coordinate space of the view that\n   * * received this back event.\n   */\n  public val touchX: Float,\n  /**\n   * Absolute Y location of the touch point of this event in the coordinate space of the view that\n   * received this back event.\n   */\n  public val touchY: Float,\n  /** Value between 0 and 1 on how far along the back gesture is. */\n  @get:FloatRange(from = 0.0, to = 1.0) public val progress: Float,\n  /** Indicates which edge the swipe starts from. */\n  @get:IntRange(from = 0, to = 1) public val swipeEdge: Int,\n) {\n  public companion object {\n    /** Indicates that the edge swipe starts from the left edge of the screen. */\n    public const val EDGE_LEFT: Int = 0\n\n    /** Indicates that the edge swipe starts from the right edge of the screen. */\n    public const val EDGE_RIGHT: Int = 1\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.ProvidableCompositionLocal\nimport androidx.compose.runtime.compositionLocalOf\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/**\n * A dispatcher that forwards back press events from the UI layer to presenters. Internally it\n * manages a list of listeners and determines which listener is actively handling back gesture\n * events.\n *\n * This class facilitates managing multiple back gesture event listeners, allowing the most recently\n * added and enabled listener to handle events.\n *\n * An implementation of this interface is provided by App Platform and it's safe to inject this type\n * within the App scope.\n */\npublic interface BackGestureDispatcherPresenter {\n  /**\n   * The count of enabled listeners. The UI layer should observe this count and enable the back\n   * gesture callback if its greater than 0 and disable the callback for a count of 0.\n   */\n  public val listenersCount: StateFlow<Int>\n\n  /**\n   * This is the callback for the UI layer to forward back press events to presenters when they\n   * happen. The [progress] Flow will be consumed by presenters.\n   */\n  public suspend fun onPredictiveBack(progress: Flow<BackEventPresenter>)\n\n  /**\n   * Presenters call this function to register the [onBack] callback for back press gestures. See\n   * [software.amazon.app.platform.presenter.molecule.backgesture.PredictiveBackHandlerPresenter]\n   * for more details.\n   */\n  @Composable\n  public fun PredictiveBackHandlerPresenter(\n    enabled: Boolean,\n    onBack: suspend (progress: Flow<BackEventPresenter>) -> Unit,\n  )\n\n  public companion object {\n\n    /**\n     * Creates a new [BackGestureDispatcherPresenter] instance.\n     *\n     * Usually, calling this function is not necessary and a default instance of\n     * [BackGestureDispatcherPresenter] is provided by App Platform that can be injected, e.g.\n     *\n     * ```\n     * @Inject\n     * class RootPresenter(\n     *   private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n     *   ...\n     * )\n     * ```\n     *\n     * This provided instance is a singleton that can be shared between presenters and renderers.\n     *\n     * This function [createNewInstance] is helpful if you need multiple instances that you want to\n     * manage yourself.\n     */\n    public fun createNewInstance(): BackGestureDispatcherPresenter =\n      CommonBackGestureDispatcherPresenter()\n  }\n}\n\n/**\n * Provides the instance of [BackGestureDispatcherPresenter] to the presenter hierarchy. This value\n * is used by [PredictiveBackHandlerPresenter] and [BackHandlerPresenter]. If the value is not\n * provided, then these functions will throw an error when used.\n *\n * This composition local is usually registered in the root presenter of the presenter hierarchy,\n * e.g.\n *\n * ```\n * @Inject\n * class RootPresenter(\n *   private val backPressDispatcherPresenter: BackPressDispatcherPresenter,\n * ) : MoleculePresenter<Unit, Model> {\n *   @Composable\n *   override fun present(input: Unit): Model {\n *     return returningCompositionLocalProvider(\n *       LocalBackPressDispatcherPresenter provides backPressDispatcherPresenter\n *     ) {\n *       ... // Call other presenters.\n *     }\n *   }\n * }\n * ```\n */\npublic val LocalBackGestureDispatcherPresenter:\n  ProvidableCompositionLocal<BackGestureDispatcherPresenter?> =\n  compositionLocalOf {\n    null\n  }\n\n/**\n * An effect for handling predictive system back gestures.\n *\n * Calling this in your presenter adds the given [onBack] lambda to the\n * [BackGestureDispatcherPresenter]. The lambda passes in a `Flow<BackEventPresenter>` where each\n * [BackEventPresenter] reflects the progress of current gesture back. The lambda content should\n * follow this structure:\n * ```\n * PredictiveBackHandlerPresenter { progress: Flow<BackEventCompat> ->\n *   // code for gesture back started\n *   try {\n *     progress.collect { backevent ->\n *       // code for progress\n *     }\n *     // code for completion\n *   } catch (e: CancellationException) {\n *     // code for cancellation\n *   }\n * }\n * ```\n *\n * If this is called by nested composables, if enabled, the inner most composable will consume the\n * call to system back and invoke its lambda. The call will continue to propagate up until it finds\n * an enabled BackHandler.\n *\n * **Important:** Back gestures can only be handled if a [BackGestureDispatcherPresenter] is\n * provided as composition local in the presenter hierarchy. See\n * [LocalBackGestureDispatcherPresenter] for more details. Further, back gestures need to be\n * forwarded from the UI layer to the [BackGestureDispatcherPresenter], e.g. using\n * `BackGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()`.\n *\n * @param enabled if this BackHandler should be enabled, true by default.\n * @param onBack the action invoked by back gesture.\n */\n// Note that the receiver parameter is used to ensure that this function is only called within a\n// presenter. This will make sure that a renderer doesn't call this function accidentally.\n@Suppress(\"UnusedReceiverParameter\")\n@Composable\npublic fun MoleculePresenter<*, *>.PredictiveBackHandlerPresenter(\n  enabled: Boolean = true,\n  onBack: suspend (progress: Flow<BackEventPresenter>) -> Unit,\n) {\n  val dispatcher =\n    checkNotNull(LocalBackGestureDispatcherPresenter.current) {\n      \"Couldn't find the BackGestureDispatcherPresenter in the presenter hierarchy. \" +\n        \"Did you register the BackGestureDispatcherPresenter instance as composition \" +\n        \"local? See LocalBackGestureDispatcherPresenter for more details.\"\n    }\n  dispatcher.PredictiveBackHandlerPresenter(enabled, onBack)\n}\n\n/**\n * An effect for handling the back event.\n *\n * Calling this in your presenter adds the given lambda to the [BackGestureDispatcherPresenter].\n *\n * If this is called by nested composables, if enabled, the inner most composable will consume the\n * call to system back and invoke its lambda. The call will continue to propagate up until it finds\n * an enabled BackHandler.\n *\n * **Important:** Back gestures can only be handled if a [BackGestureDispatcherPresenter] is\n * provided as composition local in the presenter hierarchy. See\n * [LocalBackGestureDispatcherPresenter] for more details. Further, back gestures need to be\n * forwarded from the UI layer to the [BackGestureDispatcherPresenter], e.g. using\n * `BackGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()`.\n *\n * @param enabled if this BackHandler should be enabled\n * @param onBack the action invoked by system back event\n */\n// Note that the receiver parameter is used to ensure that this function is only called within a\n// presenter. This will make sure that a renderer doesn't call this function accidentally.\n@Composable\npublic fun MoleculePresenter<*, *>.BackHandlerPresenter(\n  enabled: Boolean = true,\n  onBack: () -> Unit,\n) {\n  PredictiveBackHandlerPresenter(enabled) { progress ->\n    try {\n      progress.collect { /*ignore*/ }\n      onBack()\n    } catch (_: CancellationException) {\n      // ignore\n    }\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/CommonBackGestureDispatcherPresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberUpdatedState\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\n\ninternal class CommonBackGestureDispatcherPresenter : BackGestureDispatcherPresenter {\n  private val listeners = mutableListOf<Listener>()\n\n  private val _listenersCount = MutableStateFlow(0)\n  override val listenersCount: StateFlow<Int> = _listenersCount\n\n  override suspend fun onPredictiveBack(progress: Flow<BackEventPresenter>) {\n    val listener =\n      checkNotNull(listeners.lastOrNull { it.enabled }) {\n        \"No back gesture listener was registered or they were all disabled. \" +\n          \"Check `listenerCount` before invoking this function.\"\n      }\n\n    listener.onBack(progress)\n  }\n\n  @Composable\n  override fun PredictiveBackHandlerPresenter(\n    enabled: Boolean,\n    onBack: suspend (Flow<BackEventPresenter>) -> Unit,\n  ) {\n    // This implementation is somewhat inspired by\n    // https://github.com/JetBrains/compose-multiplatform-core/blob/244635e202f9aa734bd8c86bd1748a9065ecd818/compose/ui/ui-backhandler/src/jbMain/kotlin/androidx/compose/ui/backhandler/BackHandler.jb.kt\n\n    // Ensure we don't re-register callbacks when onBack changes. It's always the same `listener`\n    // instance with its own callback that invokes the updated `onBack`.\n    val currentOnBack by rememberUpdatedState(onBack)\n    val listener = remember { Listener(enabled) { currentOnBack(it) } }\n\n    LaunchedEffect(enabled) {\n      listener.enabled = enabled\n      updateListenersCount()\n    }\n\n    DisposableEffect(Unit) {\n      listeners += listener\n      updateListenersCount()\n\n      onDispose {\n        listeners.remove(listener)\n        updateListenersCount()\n      }\n    }\n  }\n\n  private fun updateListenersCount() {\n    _listenersCount.value = listeners.count { it.enabled }\n  }\n\n  private class Listener(\n    var enabled: Boolean,\n    val onBack: suspend (Flow<BackEventPresenter>) -> Unit,\n  )\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/LaunchMoleculePresenterTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.doesNotContain\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.startsWith\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.internal.IgnoreNative\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.internal.currentThreadName\nimport software.amazon.app.platform.presenter.BaseModel\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass LaunchMoleculePresenterTest {\n\n  @Test\n  @IgnoreNative\n  @IgnoreWasm\n  fun `the first present call happens inline and the second present call happens on the background thread`() =\n    runTest {\n      val inputFlow = MutableStateFlow(\"1\")\n      TestPresenter().test(this, inputFlow, UnconfinedTestDispatcher()) {\n        val model1 = awaitItem()\n        inputFlow.value = \"2\"\n        val model2 = awaitItem()\n\n        val testRunnerPackage = \"kotlinx.coroutines.test\"\n        assertThat(model1.threadName).startsWith(\"Test worker\")\n        assertThat(model1.threadName).contains(testRunnerPackage)\n        assertThat(model2.threadName).startsWith(\"Test worker\")\n        assertThat(model2.threadName).doesNotContain(testRunnerPackage)\n      }\n    }\n\n  @Test\n  fun `the presenter is called and computes a new model whenever the input changes`() = runTest {\n    data class Model(val value: String) : BaseModel\n\n    val presenter =\n      object : MoleculePresenter<Int, Model> {\n        @Composable\n        override fun present(input: Int): Model {\n          return Model(input.toString())\n        }\n      }\n\n    val inputFlow = MutableStateFlow(1)\n\n    presenter.test(this, inputFlow) {\n      assertThat(awaitItem().value).isEqualTo(\"1\")\n\n      inputFlow.value = 2\n      assertThat(awaitItem().value).isEqualTo(\"2\")\n\n      inputFlow.value = 3\n      assertThat(awaitItem().value).isEqualTo(\"3\")\n    }\n  }\n\n  @Test\n  fun `launching a presenter on a canceled scope throws an error`() = runTest {\n    val coroutineScope = CoroutineScope(Dispatchers.Default)\n    coroutineScope.cancel()\n    assertThat(coroutineScope.isActive).isFalse()\n\n    val moleculeScope = MoleculeScope(coroutineScope, RecompositionMode.Immediate)\n\n    data class Model(val value: String) : BaseModel\n\n    val presenter =\n      object : MoleculePresenter<Int, Model> {\n        @Composable\n        override fun present(input: Int): Model {\n          return Model(input.toString())\n        }\n      }\n\n    assertFailsWith<IllegalStateException> { moleculeScope.launchMoleculePresenter(presenter, 1) }\n  }\n\n  private class TestPresenter : MoleculePresenter<StateFlow<String>, TestPresenter.Model> {\n    @Composable\n    override fun present(input: StateFlow<String>): Model {\n      val value by input.collectAsState()\n\n      return Model(threadName = currentThreadName + value)\n    }\n\n    data class Model(val threadName: String) : BaseModel\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/MoleculeScopeTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertThat\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.isActive\n\nclass MoleculeScopeTest {\n\n  @Test\n  fun `canceling a MoleculeScope cancels the CoroutineScope`() {\n    val coroutineScope = CoroutineScope(EmptyCoroutineContext)\n    val moleculeScope = MoleculeScope(coroutineScope, RecompositionMode.Immediate)\n\n    assertThat(coroutineScope.isActive).isTrue()\n\n    moleculeScope.cancel()\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/OnEventMemoizationTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DontMemoize\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlin.test.Test\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.test.runCurrent\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass OnEventMemoizationTest {\n\n  @Test\n  fun `onEvent lambda is memoized for different inputs`() = runTest {\n    var presentCalls = 0\n\n    val presenter =\n      object : MoleculePresenter<Int, Model> {\n        @Composable\n        override fun present(input: Int): Model {\n          presentCalls++\n\n          return Model(string = if (input == 0) \"A\" else \"B\") {}\n        }\n      }\n\n    val inputs = MutableStateFlow(0)\n\n    presenter.test(this, inputs) {\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(1)\n\n      inputs.value = 1\n      assertThat(awaitItem().string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(2)\n\n      inputs.value = 2\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(3)\n\n      inputs.value = 3\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(4)\n\n      // This verifies that the presenter recomposes and a new Model is returned. But due to the\n      // strong skipping mode and lambda memoization the new model is equal and no update emitted.\n      inputs.value = 0\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(5)\n    }\n  }\n\n  @Test\n  fun `onEvent lambda is memoized for state changes`() = runTest {\n    var presentCalls = 0\n\n    var state by mutableIntStateOf(0)\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          presentCalls++\n\n          return Model(string = if (state == 0) \"A\" else \"B\") {}\n        }\n      }\n\n    presenter.test(this) {\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(1)\n\n      state = 1\n      assertThat(awaitItem().string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(2)\n\n      state = 2\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(3)\n\n      state = 3\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(4)\n\n      // This verifies that the presenter recomposes and a new Model is returned. But due to the\n      // strong skipping mode and lambda memoization the new model is equal and no update emitted.\n      state = 0\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(5)\n    }\n  }\n\n  @Test\n  fun `onEvent lambda is memoized for onEvent callbacks`() = runTest {\n    var presentCalls = 0\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          presentCalls++\n\n          var state by remember { mutableIntStateOf(0) }\n\n          return Model(string = if (state == 0) \"A\" else \"B\") { state = it.value }\n        }\n      }\n\n    presenter.test(this) {\n      var model = awaitItem()\n      assertThat(model.string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(1)\n\n      model.onEvent(Event(1))\n\n      model = awaitItem()\n      assertThat(model.string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(2)\n\n      model.onEvent(Event(2))\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(3)\n\n      model.onEvent(Event(3))\n      runCurrent()\n      expectNoEvents()\n      assertThat(presentCalls).isEqualTo(4)\n\n      // This verifies that the presenter recomposes and a new Model is returned. But due to the\n      // strong skipping mode and lambda memoization the new model is equal and no update emitted.\n      model.onEvent(Event(0))\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(5)\n    }\n  }\n\n  @Test\n  fun `a new model is produced when memoization is disabled`() = runTest {\n    var presentCalls = 0\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          presentCalls++\n\n          var state by remember { mutableIntStateOf(0) }\n\n          return Model(\n            string = if (state == 0) \"A\" else \"B\",\n            onEvent = @DontMemoize { state = it.value },\n          )\n        }\n      }\n\n    presenter.test(this) {\n      var model = awaitItem()\n      assertThat(model.string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(1)\n\n      model.onEvent(Event(1))\n\n      model = awaitItem()\n      assertThat(model.string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(2)\n\n      model.onEvent(Event(2))\n\n      runCurrent()\n      model = awaitItem()\n      assertThat(model.string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(3)\n\n      model.onEvent(Event(3))\n\n      runCurrent()\n      model = awaitItem()\n      assertThat(model.string).isEqualTo(\"B\")\n      assertThat(presentCalls).isEqualTo(4)\n\n      // This verifies that the presenter recomposes and a new Model is returned. But due to the\n      // strong skipping mode and lambda memoization the new model is equal and no update emitted.\n      model.onEvent(Event(0))\n      assertThat(awaitItem().string).isEqualTo(\"A\")\n      assertThat(presentCalls).isEqualTo(5)\n    }\n  }\n\n  private data class Event(val value: Int)\n\n  private data class Model(val string: String, val onEvent: (Event) -> Unit) : BaseModel\n}\n"
  },
  {
    "path": "presenter-molecule/public/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/CommonBackGestureDispatcherPresenterTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNull\nimport assertk.assertions.messageContains\nimport kotlin.test.Test\nimport kotlinx.coroutines.flow.emptyFlow\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.presenter.molecule.test\n\nclass CommonBackGestureDispatcherPresenterTest {\n  @Test\n  fun `the back handler is invoked`() = runTest {\n    val dispatcher = CommonBackGestureDispatcherPresenter()\n\n    data class Model(val backPressCount: Int) : BaseModel\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          var backPressCount by remember { mutableIntStateOf(0) }\n          BackHandlerPresenter { backPressCount++ }\n\n          return Model(backPressCount = backPressCount)\n        }\n      }\n\n    RootPresenter(dispatcher, presenter).test(this) {\n      assertThat(awaitItem().backPressCount).isEqualTo(0)\n\n      dispatcher.onPredictiveBack(emptyFlow())\n      assertThat(awaitItem().backPressCount).isEqualTo(1)\n\n      dispatcher.onPredictiveBack(emptyFlow())\n      assertThat(awaitItem().backPressCount).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun `the predictive back handler is invoked`() = runTest {\n    val dispatcher = CommonBackGestureDispatcherPresenter()\n\n    data class Model(val lastEvent: BackEventPresenter?) : BaseModel\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          var lastEvent by remember { mutableStateOf<BackEventPresenter?>(null) }\n\n          PredictiveBackHandlerPresenter { progress -> progress.collect { lastEvent = it } }\n\n          return Model(lastEvent = lastEvent)\n        }\n      }\n\n    RootPresenter(dispatcher, presenter).test(this) {\n      assertThat(awaitItem().lastEvent).isNull()\n\n      dispatcher.onPredictiveBack(\n        flowOf(BackEventPresenter(1f, 1f, 1f, BackEventPresenter.EDGE_RIGHT))\n      )\n      assertThat(awaitItem().lastEvent?.touchX).isEqualTo(1f)\n    }\n  }\n\n  @Test\n  fun `the last enabled handler wins`() = runTest {\n    val dispatcher = CommonBackGestureDispatcherPresenter()\n\n    data class Model(val backPressCount1: Int, val backPressCount2: Int) : BaseModel\n\n    val presenter =\n      object : MoleculePresenter<Unit, Model> {\n        @Composable\n        override fun present(input: Unit): Model {\n          var backPressCount1 by remember { mutableIntStateOf(0) }\n          BackHandlerPresenter { backPressCount1++ }\n\n          var backPressCount2 by remember { mutableIntStateOf(0) }\n          BackHandlerPresenter(enabled = backPressCount2 == 0) { backPressCount2++ }\n\n          return Model(backPressCount1 = backPressCount1, backPressCount2 = backPressCount2)\n        }\n      }\n\n    RootPresenter(dispatcher, presenter).test(this) {\n      with(awaitItem()) {\n        assertThat(backPressCount1).isEqualTo(0)\n        assertThat(backPressCount2).isEqualTo(0)\n      }\n\n      dispatcher.onPredictiveBack(emptyFlow())\n      with(awaitItem()) {\n        assertThat(backPressCount1).isEqualTo(0)\n        assertThat(backPressCount2).isEqualTo(1)\n      }\n\n      dispatcher.onPredictiveBack(emptyFlow())\n      with(awaitItem()) {\n        assertThat(backPressCount1).isEqualTo(1)\n        assertThat(backPressCount2).isEqualTo(1)\n      }\n    }\n  }\n\n  @Test\n  fun `not registering BackGestureDispatcherPresenter as composition local throws an error`() =\n    runTest {\n      data class Model(val backPressCount: Int) : BaseModel\n\n      val presenter =\n        object : MoleculePresenter<Unit, Model> {\n          @Composable\n          override fun present(input: Unit): Model {\n            var backPressCount by remember { mutableIntStateOf(0) }\n            BackHandlerPresenter { backPressCount++ }\n\n            return Model(backPressCount = backPressCount)\n          }\n        }\n\n      assertFailure { presenter.test(this) {} }\n        .messageContains(\n          \"Couldn't find the BackGestureDispatcherPresenter in the presenter hierarchy. \" +\n            \"Did you register the BackGestureDispatcherPresenter instance as composition local? \" +\n            \"See LocalBackGestureDispatcherPresenter for more details.\"\n        )\n    }\n\n  @Test\n  fun `the listener count increases with the number of enabled handlers`() =\n    runTest(UnconfinedTestDispatcher()) {\n      val dispatcher = CommonBackGestureDispatcherPresenter()\n\n      var handlers by mutableIntStateOf(0)\n      var disabledHandlers by mutableIntStateOf(0)\n\n      class Model : BaseModel\n      val model = Model()\n\n      val presenter =\n        object : MoleculePresenter<Unit, Model> {\n          @Composable\n          override fun present(input: Unit): Model {\n            repeat(handlers) { index ->\n              BackHandlerPresenter(enabled = index >= disabledHandlers) {}\n            }\n\n            return model\n          }\n        }\n\n      RootPresenter(dispatcher, presenter).test(this) {\n        skipItems(1)\n\n        assertThat(dispatcher.listenersCount.value).isEqualTo(0)\n\n        handlers = 2\n        assertThat(dispatcher.listenersCount.value).isEqualTo(2)\n\n        disabledHandlers = 1\n        assertThat(dispatcher.listenersCount.value).isEqualTo(1)\n      }\n    }\n\n  @Test\n  fun `calling onPredictiveBack without a registered handler is an error`() =\n    runTest(UnconfinedTestDispatcher()) {\n      val dispatcher = CommonBackGestureDispatcherPresenter()\n\n      var handlerEnabled by mutableStateOf(true)\n\n      data class Model(val count: Int) : BaseModel\n\n      val presenter =\n        object : MoleculePresenter<Unit, Model> {\n          @Composable\n          override fun present(input: Unit): Model {\n            var count by remember { mutableIntStateOf(0) }\n            BackHandlerPresenter(enabled = handlerEnabled) { count++ }\n\n            return Model(count)\n          }\n        }\n\n      RootPresenter(dispatcher, presenter).test(this) {\n        assertThat(awaitItem().count).isEqualTo(0)\n        assertThat(dispatcher.listenersCount.value).isEqualTo(1)\n\n        dispatcher.onPredictiveBack(emptyFlow())\n        assertThat(awaitItem().count).isEqualTo(1)\n\n        handlerEnabled = false\n        assertThat(dispatcher.listenersCount.value).isEqualTo(0)\n\n        assertFailure { dispatcher.onPredictiveBack(emptyFlow()) }\n          .messageContains(\n            \"No back gesture listener was registered or they were all disabled. Check \" +\n              \"`listenerCount` before invoking this function.\"\n          )\n      }\n    }\n\n  private class RootPresenter<ModelT : BaseModel>(\n    private val dispatcher: BackGestureDispatcherPresenter,\n    private val presenter: MoleculePresenter<Unit, ModelT>,\n  ) : MoleculePresenter<Unit, ModelT> {\n    @Composable\n    override fun present(input: Unit): ModelT {\n      return returningCompositionLocalProvider(\n        LocalBackGestureDispatcherPresenter provides dispatcher\n      ) {\n        presenter.present(Unit)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/testing/api/android/testing.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/FakeMoleculeScopeFactory : software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlinx/coroutines/CoroutineScope;)V\n\tpublic fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/TestMoleculeScopeKt {\n\tpublic static final fun moleculeScope (Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic static synthetic fun moleculeScope$default (Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/TestPresenterKt {\n\tpublic static final fun test (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun test-FHKeTTw (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test-FHKeTTw$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun test-Zzr-CC0 (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Ljava/lang/Object;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test-Zzr-CC0$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Ljava/lang/Object;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/TestBackGestureDispatcherPresenterKt {\n\tpublic static final fun withBackGestureDispatcher (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n\tpublic static final fun withBackGestureDispatcherUnit (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n\tpublic static synthetic fun withBackGestureDispatcherUnit$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n}\n\n"
  },
  {
    "path": "presenter-molecule/testing/api/desktop/testing.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/FakeMoleculeScopeFactory : software/amazon/app/platform/presenter/molecule/MoleculeScopeFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lkotlinx/coroutines/CoroutineScope;)V\n\tpublic fun createMoleculeScope ()Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic fun createMoleculeScopeFromCoroutineScope (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/TestMoleculeScopeKt {\n\tpublic static final fun moleculeScope (Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n\tpublic static synthetic fun moleculeScope$default (Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculeScope;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/TestPresenterKt {\n\tpublic static final fun test (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun test-FHKeTTw (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test-FHKeTTw$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun test-Zzr-CC0 (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Ljava/lang/Object;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;\n\tpublic static synthetic fun test-Zzr-CC0$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/test/TestScope;Ljava/lang/Object;Lkotlin/coroutines/CoroutineContext;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;\n}\n\npublic final class software/amazon/app/platform/presenter/molecule/backgesture/TestBackGestureDispatcherPresenterKt {\n\tpublic static final fun withBackGestureDispatcher (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n\tpublic static final fun withBackGestureDispatcherUnit (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n\tpublic static synthetic fun withBackGestureDispatcherUnit$default (Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;Lkotlinx/coroutines/flow/SharedFlow;ILjava/lang/Object;)Lsoftware/amazon/app/platform/presenter/molecule/MoleculePresenter;\n}\n\n"
  },
  {
    "path": "presenter-molecule/testing/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableMolecule true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonTestImplementation project(':internal:testing')\n    commonTestImplementation project(':presenter-molecule:testing')\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/FakeMoleculeScopeFactory.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport kotlin.coroutines.CoroutineContext\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.test.TestDispatcher\nimport kotlinx.coroutines.test.TestScope\n\n/**\n * Uses the given [coroutineScope] to create new [MoleculeScope] instances. In testing environments\n * often [TestScope] is used as argument.\n */\npublic class FakeMoleculeScopeFactory(private val coroutineScope: CoroutineScope) :\n  MoleculeScopeFactory {\n  override fun createMoleculeScope(): MoleculeScope =\n    createMoleculeScopeFromCoroutineScope(coroutineScope)\n\n  override fun createMoleculeScopeFromCoroutineScope(\n    coroutineScope: CoroutineScope,\n    coroutineContext: CoroutineContext,\n  ): MoleculeScope {\n    return if (coroutineScope is TestScope) {\n      coroutineScope.moleculeScope(coroutineContext)\n    } else {\n      @OptIn(ExperimentalStdlibApi::class)\n      val coroutineDispatcher = coroutineScope.coroutineContext[CoroutineDispatcher.Key]\n      if (coroutineDispatcher is TestDispatcher) {\n        // If this is a TestDispatcher, then this scope was likely based on a TestScope\n        // and we can create a wrapper. E.g. this happens if you do\n        // `testScope + CoroutineName(..)`, which returns a CoroutineScope and not\n        // TestScope.\n        TestScope(coroutineScope.coroutineContext).moleculeScope(coroutineContext)\n      } else {\n        // Respect the choice not to use a TestScope, which also initializes the whole test\n        // machinery with skipping.\n        MoleculeScope(\n          coroutineScope = coroutineScope + coroutineContext,\n          recompositionMode = RecompositionMode.Immediate,\n        )\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/TestMoleculeScope.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.TestScope\n\n/**\n * Creates and returns a [MoleculeScope] with a recompositionMode of [RecompositionMode.Immediate]\n * and with a scope that defaults to using [StandardTestDispatcher].\n *\n * @param coroutineContext a [CoroutineContext] to override any element of coroutine scope.\n */\npublic fun TestScope.moleculeScope(\n  coroutineContext: CoroutineContext = EmptyCoroutineContext\n): MoleculeScope {\n  val scope = backgroundScope + CoroutineName(\"TestMoleculeScope\") + coroutineContext\n\n  return MoleculeScope(scope, RecompositionMode.Immediate)\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/TestPresenter.kt",
    "content": "@file:Suppress(\"RedundantSuppression\", \"RedundantSuspendModifier\")\n\npackage software.amazon.app.platform.presenter.molecule\n\nimport app.cash.turbine.ReceiveTurbine\nimport app.cash.turbine.test\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.time.Duration\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * Assert the emitted models of the given [MoleculePresenter] in the provided [validate] lambda.\n *\n * The used [CoroutineContext] for the presenter can be changed with [coroutineContext]. By default,\n * a [UnconfinedTestDispatcher] will be used, which means coroutines aren't confined to any thread\n * and usually execute immediately. This avoids unnecessary calls like `advanceUntilIdle`.\n *\n * This function can be used inside a [TestScope], e.g.\n *\n * ```\n * @Test fun myTest() = runTest {\n *     MyPresenter(input).test {\n *         // assert models\n *     }\n * }\n * ```\n */\npublic suspend fun <InputT : Any, ModelT : BaseModel> MoleculePresenter<InputT, ModelT>.test(\n  testScope: TestScope,\n  input: InputT,\n  coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  timeout: Duration? = null,\n  validate: suspend ReceiveTurbine<ModelT>.() -> Unit,\n) {\n  testScope\n    .moleculeScope(coroutineContext)\n    .launchMoleculePresenter(this, input)\n    .model\n    .test(validate = validate, timeout = timeout)\n}\n\n/**\n * Assert the emitted models of the given [MoleculePresenter] in the provided [validate] lambda.\n *\n * The used [CoroutineContext] for the presenter can be changed with [coroutineContext]. By default,\n * a [UnconfinedTestDispatcher] will be used, which means coroutines aren't confined to any thread\n * and usually execute immediately. This avoids unnecessary calls like `advanceUntilIdle`.\n *\n * This function can be used inside of a [TestScope], e.g.\n *\n * ```\n * @Test fun myTest() = runTest {\n *     MyPresenter(input).test {\n *         // assert models\n *     }\n * }\n * ```\n */\npublic suspend fun <InputT : Any, ModelT : BaseModel> MoleculePresenter<InputT, ModelT>.test(\n  testScope: TestScope,\n  input: StateFlow<InputT>,\n  coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  validate: suspend ReceiveTurbine<ModelT>.() -> Unit,\n) {\n  testScope\n    .moleculeScope(coroutineContext)\n    .launchMoleculePresenter(this, input)\n    .model\n    .test(validate = validate)\n}\n\n/**\n * Assert the emitted models of the given [MoleculePresenter] in the provided [validate] lambda.\n *\n * The used [CoroutineContext] for the presenter can be changed with [coroutineContext]. By default,\n * a [UnconfinedTestDispatcher] will be used, which means coroutines aren't confined to any thread\n * and usually execute immediately. This avoids unnecessary calls like `advanceUntilIdle`.\n *\n * This function can be used inside of a [TestScope], e.g.\n *\n * ```\n * @Test fun myTest() = runTest {\n *     MyPresenter().test {\n *         // assert models\n *     }\n * }\n * ```\n */\npublic suspend fun <ModelT : BaseModel> MoleculePresenter<Unit, ModelT>.test(\n  testScope: TestScope,\n  coroutineContext: CoroutineContext = EmptyCoroutineContext,\n  timeout: Duration? = null,\n  validate: suspend ReceiveTurbine<ModelT>.() -> Unit,\n) {\n  test(testScope, Unit, coroutineContext, timeout, validate)\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/TestBackGestureDispatcherPresenter.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.remember\nimport kotlin.jvm.JvmName\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.SharedFlow\nimport kotlinx.coroutines.flow.emptyFlow\nimport kotlinx.coroutines.flow.map\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\n\nprivate class TestBackGestureDispatcherPresenter<InputT : Any, ModelT : BaseModel>(\n  private val delegate: MoleculePresenter<InputT, ModelT>,\n  private val backEvents: Flow<Flow<BackEventPresenter>>,\n) : MoleculePresenter<InputT, ModelT> {\n  @Composable\n  override fun present(input: InputT): ModelT {\n    val dispatcher = remember { BackGestureDispatcherPresenter.createNewInstance() }\n\n    return returningCompositionLocalProvider(\n      LocalBackGestureDispatcherPresenter provides dispatcher\n    ) {\n      LaunchedEffect(Unit) { backEvents.collect { dispatcher.onPredictiveBack(it) } }\n\n      delegate.present(input)\n    }\n  }\n}\n\n/**\n * Wraps the receiver presenter with another presenter to provide a [BackGestureDispatcherPresenter]\n * as composition local. This is needed when your presenter uses [BackHandlerPresenter] or\n * [PredictiveBackHandlerPresenter] and requires the composition local.\n *\n * [backEvents] is an optional parameter to simulate back press events.\n */\n@JvmName(\"withBackGestureDispatcherUnit\")\npublic fun <InputT : Any, ModelT : BaseModel> MoleculePresenter<InputT, ModelT>\n  .withBackGestureDispatcher(\n  backEvents: SharedFlow<Unit> = MutableSharedFlow()\n): MoleculePresenter<InputT, ModelT> =\n  TestBackGestureDispatcherPresenter(this, backEvents.map { emptyFlow() })\n\n/**\n * Wraps the receiver presenter with another presenter to provide a [BackGestureDispatcherPresenter]\n * as composition local. This is needed when your presenter uses [BackHandlerPresenter] or\n * [PredictiveBackHandlerPresenter] and requires the composition local.\n *\n * [backEvents] provides predictive back press events.\n */\npublic fun <InputT : Any, ModelT : BaseModel> MoleculePresenter<InputT, ModelT>\n  .withBackGestureDispatcher(\n  backEvents: SharedFlow<Flow<BackEventPresenter>>\n): MoleculePresenter<InputT, ModelT> = TestBackGestureDispatcherPresenter(this, backEvents)\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/FakeMoleculeScopeFactoryTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.internal.IgnoreWasm\n\nclass FakeMoleculeScopeFactoryTest {\n\n  @Test\n  fun `a created MoleculeScope can be canceled`() = runTest {\n    val scope = FakeMoleculeScopeFactory(this).createMoleculeScope()\n    scope.cancel()\n\n    var didRun = false\n    backgroundScope.launch(start = CoroutineStart.UNDISPATCHED) { didRun = true }\n\n    assertThat(didRun).isTrue()\n    assertThat(scope.coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `a created MoleculeScope does not need to be canceled for the test to complete`() {\n    // Basically this test should not hang.\n    var didRun = false\n    runTest {\n      FakeMoleculeScopeFactory(this).createMoleculeScope()\n      didRun = true\n    }\n\n    assertThat(didRun).isTrue()\n  }\n\n  @Test\n  fun `the coroutine context is added to the scope`() = runTest {\n    val scope =\n      FakeMoleculeScopeFactory(this)\n        .createMoleculeScopeFromCoroutineScope(this, CoroutineName(\"test\"))\n\n    val name = scope.coroutineScope.coroutineContext[CoroutineName.Key]\n    assertThat(name?.name).isEqualTo(\"test\")\n  }\n\n  @Test\n  fun `a regular CoroutineScope can be used to create a MoleculeScope`() = runTest {\n    val coroutineScope = CoroutineScope(CoroutineName(\"test\"))\n    val moleculeScope =\n      FakeMoleculeScopeFactory(this).createMoleculeScopeFromCoroutineScope(coroutineScope)\n\n    val name = moleculeScope.coroutineScope.coroutineContext[CoroutineName.Key]\n    assertThat(name?.name).isEqualTo(\"test\")\n\n    moleculeScope.cancel()\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  fun `a regular CoroutineScope can be used to create a MoleculeScope without a TestScope`() {\n    val coroutineScope = CoroutineScope(CoroutineName(\"test\"))\n    val moleculeScope = FakeMoleculeScopeFactory(coroutineScope).createMoleculeScope()\n\n    val name = moleculeScope.coroutineScope.coroutineContext[CoroutineName.Key]\n    assertThat(name?.name).isEqualTo(\"test\")\n\n    moleculeScope.cancel()\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/TestMoleculeScopeTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport app.cash.molecule.RecompositionMode\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport assertk.assertions.messageContains\nimport assertk.assertions.rootCause\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runCurrent\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.internal.IgnoreWasm\n\nclass TestMoleculeScopeTest {\n\n  @Test\n  fun `test recompositionMode of moleculeScope is always Immediate`() = runTest {\n    var moleculeScope = moleculeScope()\n    assertThat(moleculeScope.recompositionMode).isEqualTo(RecompositionMode.Immediate)\n\n    moleculeScope = moleculeScope(CoroutineName(\"test\"))\n    assertThat(moleculeScope.recompositionMode).isEqualTo(RecompositionMode.Immediate)\n  }\n\n  @Test\n  fun `a standard test dispatcher is used by default`() = runTest {\n    val job =\n      moleculeScope().coroutineScope.launch {\n        // Do nothing\n      }\n    assertThat(job.isCompleted).isFalse()\n    runCurrent()\n    assertThat(job.isCompleted).isTrue()\n  }\n\n  @Test\n  fun `an unconfined test dispatcher can be used`() = runTest {\n    val job =\n      moleculeScope(UnconfinedTestDispatcher()).coroutineScope.launch {\n        // Do nothing\n      }\n    assertThat(job.isCompleted).isTrue()\n  }\n\n  @Test\n  fun `the coroutine context can be changed`() = runTest {\n    val name =\n      moleculeScope(CoroutineName(\"Test-abc\"))\n        .coroutineScope\n        .coroutineContext[CoroutineName.Key]\n        ?.name\n    assertThat(name).isEqualTo(\"Test-abc\")\n  }\n\n  @Test\n  fun `the coroutine context is canceled`() = runTest {\n    val moleculeScope = moleculeScope()\n    assertThat(moleculeScope.coroutineScope.isActive).isTrue()\n    moleculeScope.cancel()\n    assertThat(moleculeScope.coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `failures in a coroutine are reported`() {\n    assertFailure {\n        runTest {\n          val moleculeScope = moleculeScope()\n          moleculeScope.coroutineScope.launch { error(\"test failure\") }\n\n          runCurrent()\n        }\n      }\n      .rootCause()\n      .messageContains(\"test failure\")\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/TestPresenterTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.InternalComposeApi\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.currentComposer\nimport androidx.compose.runtime.getValue\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isTrue\nimport assertk.assertions.messageContains\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport kotlin.test.fail\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass TestPresenterTest {\n\n  @Test\n  fun `values are properly emitted`() = runTest {\n    data class Model(val value: String) : BaseModel\n\n    class TestPresenter : MoleculePresenter<StateFlow<String>, Model> {\n      @Composable\n      override fun present(input: StateFlow<String>): Model {\n        return Model(input.collectAsState().value)\n      }\n    }\n\n    val input = MutableStateFlow(\"a\")\n    TestPresenter().test(this, input) {\n      assertThat(awaitItem().value).isEqualTo(\"a\")\n\n      input.value = \"b\"\n      assertThat(awaitItem().value).isEqualTo(\"b\")\n    }\n  }\n\n  @Test\n  fun `the coroutine context can be changed`() = runTest {\n    data class Model(val name: CoroutineName?) : BaseModel\n\n    class TestPresenter : MoleculePresenter<Unit, Model> {\n      @Composable\n      override fun present(input: Unit): Model {\n        @OptIn(InternalComposeApi::class)\n        return Model(currentComposer.applyCoroutineContext[CoroutineName])\n      }\n    }\n\n    TestPresenter().test(this, CoroutineName(\"def\")) {\n      assertThat(awaitItem().name?.name).isEqualTo(\"def\")\n    }\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `failures in a presenter are reported in the first emission`() {\n    class Model : BaseModel\n\n    class TestPresenter : MoleculePresenter<Unit, Model> {\n      @Composable\n      override fun present(input: Unit): Model {\n        fail(\"test failure\")\n      }\n    }\n\n    val throwable =\n      assertFailsWith<Throwable> {\n        runTest {\n          TestPresenter().test(this) {\n            // This call will not succeed and timeout. Presenters are turned into a\n            // StateFlow and StateFlows never complete. Instead, the error is reported\n            // in the CoroutineScope running Molecule. That's why it's tearing down the\n            // entire test and this is good. A presenter should never throw an exception.\n            //\n            // This behavior also follows what would happen in production.\n            awaitError()\n          }\n        }\n      }\n    assertThat(throwable).messageContains(\"test failure\")\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `failures in a presenter are reported in later emissions`() {\n    class Model : BaseModel\n\n    val trigger = MutableStateFlow(0)\n\n    class TestPresenter : MoleculePresenter<Unit, Model> {\n      @Composable\n      override fun present(input: Unit): Model {\n        val triggerValue by trigger.collectAsState()\n        if (triggerValue > 0) {\n          fail(\"test failure\")\n        }\n        return Model()\n      }\n    }\n\n    var itemReceived = false\n    val throwable =\n      assertFailsWith<Throwable> {\n        runTest {\n          TestPresenter().test(this, timeout = 100.milliseconds) {\n            assertThat(awaitItem()).isNotNull()\n            itemReceived = true\n\n            trigger.value = 1\n\n            // This call will not succeed and timeout. Presenters are turned into a\n            // StateFlow and StateFlows never complete. Instead, the error is reported\n            // in the CoroutineScope running Molecule. That's why it's tearing down the\n            // entire test and this is good. A presenter should never throw an exception.\n            //\n            // This behavior also follows what would happen in production.\n            awaitError()\n          }\n        }\n      }\n    assertThat(throwable).messageContains(\"No value produced in 100ms\")\n    assertThat(throwable.suppressedExceptions.single()).messageContains(\"test failure\")\n\n    assertThat(itemReceived).isTrue()\n  }\n}\n"
  },
  {
    "path": "presenter-molecule/testing/src/commonTest/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/TestBackGestureDispatcherPresenterTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.messageContains\nimport kotlin.test.Test\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOf\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.test\n\nclass TestBackGestureDispatcherPresenterTest {\n  @Test\n  fun `a presenter cannot be tested without the dispatcher wrapper`() = runTest {\n    val presenter =\n      object : MoleculePresenter<Unit, BaseModel> {\n        @Composable\n        override fun present(input: Unit): BaseModel {\n          BackHandlerPresenter {}\n          return object : BaseModel {}\n        }\n      }\n\n    assertFailure { presenter.test(this) {} }\n      .messageContains(\n        \"Couldn't find the BackGestureDispatcherPresenter in the presenter hierarchy.\"\n      )\n  }\n\n  @Test\n  fun `a presenter can be tested with the dispatcher wrapper`() = runTest {\n    val presenter =\n      object : MoleculePresenter<Unit, BaseModel> {\n        @Composable\n        override fun present(input: Unit): BaseModel {\n          BackHandlerPresenter {}\n          return object : BaseModel {}\n        }\n      }\n\n    presenter.withBackGestureDispatcher().test(this) { awaitItem() }\n  }\n\n  @Test\n  fun `back press events can be provided in tests`() =\n    runTest(UnconfinedTestDispatcher()) {\n      data class Model(val count: Int) : BaseModel\n\n      val presenter =\n        object : MoleculePresenter<Unit, Model> {\n          @Composable\n          override fun present(input: Unit): Model {\n            var backPressCount by remember { mutableIntStateOf(0) }\n            BackHandlerPresenter { backPressCount++ }\n            return Model(backPressCount)\n          }\n        }\n\n      val backEvents = MutableSharedFlow<Unit>()\n      presenter.withBackGestureDispatcher(backEvents).test(this) {\n        assertThat(awaitItem().count).isEqualTo(0)\n\n        backEvents.emit(Unit)\n        assertThat(awaitItem().count).isEqualTo(1)\n\n        backEvents.emit(Unit)\n        assertThat(awaitItem().count).isEqualTo(2)\n      }\n    }\n\n  @Test\n  fun `predictive back press events can be provided in tests`() =\n    runTest(UnconfinedTestDispatcher()) {\n      data class Model(val eventCount: Int, val doneCount: Int) : BaseModel\n\n      val presenter =\n        object : MoleculePresenter<Unit, Model> {\n          @Composable\n          override fun present(input: Unit): Model {\n            var eventCount by remember { mutableIntStateOf(0) }\n            var doneCount by remember { mutableIntStateOf(0) }\n\n            PredictiveBackHandlerPresenter { progress ->\n              try {\n                progress.collect { eventCount++ }\n                doneCount++\n              } catch (_: CancellationException) {}\n            }\n            return Model(eventCount = eventCount, doneCount = doneCount)\n          }\n        }\n\n      val backEvents = MutableSharedFlow<Flow<BackEventPresenter>>()\n      presenter.withBackGestureDispatcher(backEvents).test(this) {\n        with(awaitItem()) {\n          assertThat(eventCount).isEqualTo(0)\n          assertThat(doneCount).isEqualTo(0)\n        }\n\n        backEvents.emit(\n          flow {\n            emit(BackEventPresenter(1f, 1f, 1f, BackEventPresenter.EDGE_LEFT))\n            delay(1.seconds)\n            emit(BackEventPresenter(1f, 1f, 1f, BackEventPresenter.EDGE_LEFT))\n          }\n        )\n\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 1, doneCount = 0))\n\n        advanceTimeBy(1.seconds)\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 2, doneCount = 0))\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 2, doneCount = 1))\n\n        backEvents.emit(\n          flowOf(\n            BackEventPresenter(1f, 1f, 1f, BackEventPresenter.EDGE_LEFT),\n            BackEventPresenter(1f, 1f, 1f, BackEventPresenter.EDGE_LEFT),\n          )\n        )\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 3, doneCount = 1))\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 4, doneCount = 1))\n        assertThat(awaitItem()).isEqualTo(Model(eventCount = 4, doneCount = 2))\n      }\n    }\n}\n"
  },
  {
    "path": "recipes/app/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :recipes:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.app'\n}\n\n// This extension comes from our published plugin.\nappPlatform {\n    enableComposeUi true\n    enableKotlinInject true\n    enableModuleStructure true\n    enableMoleculePresenters true\n    addImplModuleDependencies true\n}\n\ndependencies {\n    commonMainImplementation project(':recipes:common:impl')\n\n    androidMainImplementation libs.androidx.activity.compose\n}\n"
  },
  {
    "path": "recipes/app/src/androidMain/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:name=\"software.amazon.app.platform.recipes.AndroidApplication\"\n        android:allowBackup=\"false\"\n        android:label=\"Recipes\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@android:style/Theme.Material.Light.NoActionBar\"\n        tools:ignore=\"DataExtractionRules,MissingApplicationIcon\">\n        <activity\n            android:exported=\"true\"\n            android:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode\"\n            android:name=\"software.amazon.app.platform.recipes.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "recipes/app/src/androidMain/kotlin/software/amazon/app/platform/recipes/AndroidAppComponent.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport android.app.Application\nimport me.tatarka.inject.annotations.Component\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * The final Android app component. Note that [application] is an Android specific type and classes\n * living in the Android source folder can therefore inject [Application].\n *\n * [rootScopeProvider] is provided in the [AndroidAppComponent] and can be injected.\n */\n@Component\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class AndroidAppComponent(\n  @get:Provides val application: Application,\n  @get:Provides val rootScopeProvider: RootScopeProvider,\n) : AndroidAppComponentMerged\n"
  },
  {
    "path": "recipes/app/src/androidMain/kotlin/software/amazon/app/platform/recipes/AndroidApplication.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport android.app.Application\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * The [Application] class of our recipes app. Note that this class implements [RootScopeProvider].\n * This is helpful to get access to the root scope from Android components such as activities.\n */\nopen class AndroidApplication : Application(), RootScopeProvider {\n\n  private val demoApplication = DemoApplication()\n\n  override val rootScope: Scope\n    get() = demoApplication.rootScope\n\n  override fun onCreate() {\n    demoApplication.create(component(demoApplication))\n    super.onCreate()\n  }\n\n  /** Create the [AppComponent]. In UI tests we use a different instance. */\n  protected open fun component(demoApplication: DemoApplication): AppComponent {\n    return AndroidAppComponent::class.create(this, demoApplication)\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/androidMain/kotlin/software/amazon/app/platform/recipes/MainActivity.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport software.amazon.app.platform.renderer.ComposeAndroidRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The only `Activity` of our recipes app. This class is just an entry point to start rendering\n * templates.\n */\nclass MainActivity : ComponentActivity() {\n\n  private val rootScopeProvider\n    get() = application as RootScopeProvider\n\n  private val viewModel by viewModels<MainActivityViewModel>()\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    enableEdgeToEdge()\n\n    val rendererFactory =\n      ComposeAndroidRendererFactory.createForComposeUi(rootScopeProvider = rootScopeProvider)\n\n    setContent {\n      val template by viewModel.templates.collectAsState()\n\n      val renderer = rendererFactory.getComposeRenderer(template)\n      renderer.renderCompose(template)\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/androidMain/kotlin/software/amazon/app/platform/recipes/MainActivityViewModel.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.recipes.template.RecipesAppTemplate\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/**\n * `ViewModel` that hosts the stream of templates and survives configuration changes. Note that we\n * use [application] to get access to the root scope.\n */\nclass MainActivityViewModel(application: Application) : AndroidViewModel(application) {\n\n  private val component =\n    (application as RootScopeProvider).rootScope.kotlinInjectComponent<Component>()\n  private val templateProvider = component.templateProviderFactory.createTemplateProvider()\n\n  /** The stream of templates that are rendered by [MainActivity]. */\n  val templates: StateFlow<RecipesAppTemplate> = templateProvider.templates\n\n  override fun onCleared() {\n    templateProvider.cancel()\n  }\n\n  /** Component interface to give us access to objects from the app component. */\n  @ContributesTo(AppScope::class)\n  interface Component {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/commonMain/kotlin/software/amazon/app/platform/recipes/AppComponent.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport me.tatarka.inject.annotations.IntoSet\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.presenter.molecule.MoleculeScopeFactory\nimport software.amazon.app.platform.recipes.swiftui.SwiftUiHomePresenter\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * Shared interface for the app component. The final components live in the platform specific source\n * folders in order to have access to platform specific code.\n */\n@ContributesTo(AppScope::class)\n@SingleIn(AppScope::class)\ninterface AppComponent {\n  /** All [Scoped] instances part of the app scope. */\n  @ForScope(AppScope::class) val appScopedInstances: Set<Scoped>\n\n  /** The coroutine scope that runs as long as the app scope is alive. */\n  @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped\n\n  /**\n   * Provide at least one implementation in the scope, otherwise kotlin-inject will complain. The\n   * recipes app actually doesn't have a [Scoped] instance in the app scope, that's why this is\n   * needed.\n   */\n  @Provides @IntoSet @ForScope(AppScope::class) fun provideEmptyScoped(): Scoped = Scoped.NO_OP\n\n  /** The root presenter for the SwiftUI recipe. */\n  val swiftUiHomePresenter: SwiftUiHomePresenter\n\n  /** Factory needed to launch presenters from native. */\n  val moleculeScopeFactory: MoleculeScopeFactory\n}\n"
  },
  {
    "path": "recipes/app/src/commonMain/kotlin/software/amazon/app/platform/recipes/DemoApplication.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.addKotlinInjectComponent\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.app.platform.scope.register\n\n/**\n * Shared class between the platform to manage the root scope. It itself implements the\n * [RootScopeProvider] interface.\n */\nclass DemoApplication : RootScopeProvider {\n\n  private var _rootScope: Scope? = null\n\n  override val rootScope: Scope\n    get() = checkNotNull(_rootScope) { \"Must call create() first.\" }\n\n  /** Provides the application scope DI component. */\n  val appComponent: AppComponent\n    get() = rootScope.kotlinInjectComponent<AppComponent>()\n\n  /** Creates the root scope and remembers the instance. */\n  fun create(appComponent: AppComponent) {\n    check(_rootScope == null) { \"create() should be called only once.\" }\n\n    _rootScope = Scope.buildRootScope {\n      addKotlinInjectComponent(appComponent)\n\n      addCoroutineScopeScoped(appComponent.appScopeCoroutineScopeScoped)\n    }\n\n    // Register instances after the rootScope has been set to avoid race conditions for Scoped\n    // instances that may use the rootScope.\n    rootScope.register(appComponent.appScopedInstances)\n  }\n\n  /** Destroys the root scope. */\n  fun destroy() {\n    rootScope.destroy()\n    _rootScope = null\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/commonMain/kotlin/software/amazon/app/platform/recipes/TemplateProvider.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport kotlinx.coroutines.flow.StateFlow\nimport me.tatarka.inject.annotations.Assisted\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.presenter.molecule.MoleculeScope\nimport software.amazon.app.platform.presenter.molecule.MoleculeScopeFactory\nimport software.amazon.app.platform.presenter.molecule.launchMoleculePresenter\nimport software.amazon.app.platform.recipes.template.RecipesAppTemplate\nimport software.amazon.app.platform.recipes.template.RootPresenter\n\n/**\n * Shared class between all platforms to start collecting\n * [software.amazon.app.platform.recipes.template.RecipesAppTemplate] in a [StateFlow]. Inject\n * [Factory] to create a new instance. Once the instance is no longer needed, call [cancel] to clean\n * up any resources.\n */\n@Inject\nclass TemplateProvider(\n  presenter: RootPresenter,\n  @Assisted private val moleculeScope: MoleculeScope,\n) {\n\n  /** The templates that should be rendered in the UI. */\n  val templates: StateFlow<RecipesAppTemplate> by lazy {\n    moleculeScope.launchMoleculePresenter(presenter = presenter, input = Unit).model\n  }\n\n  /** Releases all resources and stops [templates] from updating further. */\n  fun cancel() {\n    moleculeScope.cancel()\n  }\n\n  /** Factory class to create a new instance of [TemplateProvider]. */\n  // Note that the Factory class technically is not required. But since TemplateProvider\n  // contains a MoleculeScope that needs to be canceled explicitly, this Factory helps to\n  // highlight that the created instance contains resources that must be cleaned up.\n  @Inject\n  class Factory(\n    private val moleculeScopeFactory: MoleculeScopeFactory,\n    private val templateProvider: (MoleculeScope) -> TemplateProvider,\n  ) {\n    /**\n     * Creates a new instance of [TemplateProvider]. Call [TemplateProvider.cancel] when the\n     * instance not needed anymore to avoid leaking resources.\n     */\n    fun createTemplateProvider(): TemplateProvider {\n      return templateProvider(moleculeScopeFactory.createMoleculeScope())\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/desktopMain/kotlin/software/amazon/app/platform/recipes/DesktopApp.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/**\n * Responsible for creating the app component [component] and producing templates. Call [destroy] to\n * clean up any resources.\n */\nclass DesktopApp(private val component: (RootScopeProvider) -> AppComponent) : RootScopeProvider {\n\n  override val rootScope: Scope\n    get() = demoApplication.rootScope\n\n  private val demoApplication = DemoApplication().apply { create(component(this)) }\n\n  private val templateProvider =\n    rootScope.kotlinInjectComponent<Component>().templateProviderFactory.createTemplateProvider()\n\n  /** Call this composable function to start rendering templates on the screen. */\n  @Composable\n  fun renderTemplates() {\n    val template by templateProvider.templates.collectAsState()\n\n    val factory = remember { ComposeRendererFactory(demoApplication) }\n\n    val renderer = factory.getComposeRenderer(template)\n    renderer.renderCompose(template)\n  }\n\n  /** Cancels and releases all resources. */\n  fun destroy() {\n    templateProvider.cancel()\n    demoApplication.destroy()\n  }\n\n  /** Component interface to give us access to objects from the app component. */\n  @ContributesTo(AppScope::class)\n  interface Component {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/desktopMain/kotlin/software/amazon/app/platform/recipes/DesktopAppComponent.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport me.tatarka.inject.annotations.Component\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * The final Desktop app component. Unlike the Android and iOS specific counterpart, this class\n * doesn't have any platform specific types.\n *\n * [rootScopeProvider] is provided in the [DesktopAppComponent] and can be injected.\n */\n@Component\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class DesktopAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) :\n  DesktopAppComponentMerged\n"
  },
  {
    "path": "recipes/app/src/desktopMain/kotlin/software/amazon/app/platform/recipes/Main.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport androidx.compose.ui.window.Window\nimport androidx.compose.ui.window.application\n\n/** The main function to launch the Desktop app. */\nfun main() {\n  val desktopApp = DesktopApp { DesktopAppComponent::class.create(it) }\n\n  application {\n    Window(\n      onCloseRequest = {\n        desktopApp.destroy()\n        exitApplication()\n      },\n      // alwaysOnTop helps during development to see the application in foreground.\n      alwaysOnTop = true,\n      title = \"App Platform\",\n    ) {\n      desktopApp.renderTemplates()\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/app/src/iosMain/kotlin/software/amazon/app/platform/recipes/IosAppComponent.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport kotlin.reflect.KClass\nimport me.tatarka.inject.annotations.Provides\nimport platform.UIKit.UIApplication\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * The final iOS app component. Note that [uiApplication] is an iOS specific type and classes living\n * in the iOS source folder can therefore inject [UIApplication].\n *\n * [rootScopeProvider] is provided in the [IosAppComponent] and can be injected.\n */\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class IosAppComponent(\n  @get:Provides val uiApplication: UIApplication,\n  @get:Provides val rootScopeProvider: RootScopeProvider,\n) {\n  /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n  abstract val templateProviderFactory: TemplateProvider.Factory\n}\n\n/**\n * Factory function to instantiate the component. This is necessary, because `iosMain` is a shared\n * source folder and generated components live in the x64, arm64 and simulatorArm64 source folders.\n */\n@MergeComponent.CreateComponent\nexpect fun KClass<IosAppComponent>.createComponent(\n  uiApplication: UIApplication,\n  rootScopeProvider: RootScopeProvider,\n): IosAppComponent\n\n/** This function is called from Swift to create a new component instance. */\n@Suppress(\"unused\")\nfun createIosAppComponent(\n  application: UIApplication,\n  rootScopeProvider: RootScopeProvider,\n): AppComponent {\n  return IosAppComponent::class.createComponent(application, rootScopeProvider) as AppComponent\n}\n"
  },
  {
    "path": "recipes/app/src/iosMain/kotlin/software/amazon/app/platform/recipes/MainViewController.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.window.ComposeUIViewController\nimport platform.UIKit.UIViewController\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\n\n/**\n * This function is called from Swift to hook up the Compose Multiplatform UI.\n *\n * This is our entry point to start producing templates and hooking up our [Renderer] runtime. Other\n * platforms extract this code into classes that are effectively singletons. But this approach is\n * good enough for the iOS recipes app.\n */\n@Suppress(\"unused\")\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    // Create a single instance.\n    val templateProvider = remember {\n      rootScopeProvider.rootScope\n        .kotlinInjectComponent<IosAppComponent>()\n        .templateProviderFactory\n        .createTemplateProvider()\n    }\n\n    DisposableEffect(Unit) {\n      onDispose {\n        // Cancel the provider when it's no longer needed.\n        templateProvider.cancel()\n      }\n    }\n\n    // Only a single factory is needed.\n    val factory = remember { ComposeRendererFactory(rootScopeProvider) }\n\n    // Render templates using our Renderer runtime.\n    val template by templateProvider.templates.collectAsState()\n\n    val renderer = factory.getRenderer(template::class)\n    renderer.renderCompose(template)\n  }\n"
  },
  {
    "path": "recipes/app/src/wasmJsMain/kotlin/software/amazon/app/platform/recipes/Main.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.ExperimentalComposeUiApi\nimport androidx.compose.ui.window.ComposeViewport\nimport kotlinx.browser.document\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\n\n/** The entry point of our recipes app. */\n@OptIn(ExperimentalComposeUiApi::class)\nfun main() {\n  ComposeViewport(checkNotNull(document.body)) { AppPlatform() }\n}\n\n@Composable\nprivate fun AppPlatform() {\n  val application = remember {\n    DemoApplication().apply { create(WasmJsAppComponent::class.create(this)) }\n  }\n\n  // Create a single instance.\n  val templateProvider = remember {\n    application.rootScope\n      .kotlinInjectComponent<WasmJsAppComponent>()\n      .templateProviderFactory\n      .createTemplateProvider()\n  }\n\n  DisposableEffect(Unit) {\n    onDispose {\n      // Cancel the provider when it's no longer needed.\n      templateProvider.cancel()\n    }\n  }\n\n  // Only a single factory is needed.\n  val factory = remember { ComposeRendererFactory(application) }\n\n  // Render templates using our Renderer runtime.\n  val template by templateProvider.templates.collectAsState()\n\n  val renderer = factory.getRenderer(template::class)\n  renderer.renderCompose(template)\n}\n"
  },
  {
    "path": "recipes/app/src/wasmJsMain/kotlin/software/amazon/app/platform/recipes/WasmJsAppComponent.kt",
    "content": "package software.amazon.app.platform.recipes\n\nimport me.tatarka.inject.annotations.Component\nimport me.tatarka.inject.annotations.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.MergeComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/**\n * The final Wasm app component. Unlike the Android and iOS specific counterpart, this class doesn't\n * have any platform specific types.\n *\n * [rootScopeProvider] is provided in the [WasmJsAppComponent] and can be injected.\n */\n@Component\n@MergeComponent(AppScope::class)\n@SingleIn(AppScope::class)\nabstract class WasmJsAppComponent(@get:Provides val rootScopeProvider: RootScopeProvider) :\n  WasmJsAppComponentMerged {\n  /**\n   * Gives access to the [software.amazon.app.platform.recipes.TemplateProvider.Factory] from the\n   * object graph.\n   */\n  abstract val templateProviderFactory: TemplateProvider.Factory\n}\n"
  },
  {
    "path": "recipes/app/src/wasmJsMain/resources/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>App Platform</title>\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"styles.css\">\n    <script type=\"application/javascript\" src=\"recipes-app.js\"></script>\n</head>\n<body>\n</body>\n</html>\n"
  },
  {
    "path": "recipes/app/src/wasmJsMain/resources/styles.css",
    "content": "html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n}"
  },
  {
    "path": "recipes/common/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :recipes:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableKotlinInject true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n\ndependencies {\n    commonMainImplementation compose.components.resources\n    commonMainImplementation compose.material3\n    commonMainImplementation compose.materialIconsExtended\n    commonMainImplementation compose.runtimeSaveable\n    commonMainImplementation compose.ui\n\n    commonMainImplementation libs.androidx.collection\n    commonMainImplementation libs.navigation3.runtime\n    commonMainImplementation libs.navigation3.ui\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/appbar/AppBarConfig.kt",
    "content": "package software.amazon.app.platform.recipes.appbar\n\n/** Configures the look of the App Bar for the recipe app. */\ndata class AppBarConfig(\n  /** The title shown in the center of the App Bar. */\n  val title: String,\n  /**\n   * If not null, then the back arrow will be shown and the lambda invoked when there's an action.\n   */\n  val backArrowAction: (() -> Unit)? = null,\n\n  /** A list of menu items that should be shown in the overflow menu if any. */\n  val menuItems: List<MenuItem> = emptyList(),\n) {\n  /**\n   * An element in the overflow menu with [text] as the title. [action] is invoked when this element\n   * is pressed.\n   */\n  data class MenuItem(val text: String, val action: () -> Unit)\n\n  companion object {\n    /** The default configuration used when no presenter overrides the config. */\n    val DEFAULT = AppBarConfig(title = \"Recipes App\")\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/appbar/AppBarConfigModel.kt",
    "content": "package software.amazon.app.platform.recipes.appbar\n\nimport software.amazon.app.platform.presenter.BaseModel\n\n/** Can be implemented by a [BaseModel] class to change the configuration of the App Bar. */\ninterface AppBarConfigModel {\n  /** Returns the config that should be rendered. */\n  fun appBarConfig(): AppBarConfig\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/appbar/menu/MenuPresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.appbar.menu\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.recipes.appbar.AppBarConfigModel\nimport software.amazon.app.platform.recipes.appbar.menu.MenuPresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\n\n/** This presenter provides a custom menu in the App Bar. */\nclass MenuPresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    var itemCount by remember { mutableIntStateOf(2) }\n    var pressedItem by remember { mutableStateOf<Int?>(null) }\n\n    val items =\n      List(itemCount) {\n        val number = it + 1\n        AppBarConfig.MenuItem(text = \"Option $number\", action = { pressedItem = number })\n      }\n\n    LaunchedEffect(pressedItem) {\n      delay(3.seconds)\n      pressedItem = null\n    }\n\n    return Model(items, pressedItem) {\n      when (it) {\n        Event.AddMenuItem -> itemCount += 1\n      }\n    }\n  }\n\n  data class Model(\n    private val menuItems: List<AppBarConfig.MenuItem>,\n    val pressedItem: Int?,\n    val onEvent: (Event) -> Unit,\n  ) : BaseModel, AppBarConfigModel {\n    override fun appBarConfig(): AppBarConfig {\n      return AppBarConfig(title = \"Menu items\", menuItems = menuItems)\n    }\n  }\n\n  sealed interface Event {\n    data object AddMenuItem : Event\n  }\n}\n\n@ContributesRenderer\nclass MenuRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(\n      modifier = Modifier.fillMaxSize().padding(top = 12.dp),\n      horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n      Button(onClick = { model.onEvent(MenuPresenter.Event.AddMenuItem) }) { Text(\"Add menu item\") }\n\n      AnimatedContent(targetState = model, contentKey = { it.pressedItem != null }) { targetModel ->\n        if (targetModel.pressedItem != null) {\n          Text(\n            text = \"Pressed option ${targetModel.pressedItem}\",\n            style = MaterialTheme.typography.bodyLarge,\n            modifier = Modifier.padding(top = 12.dp),\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/CrossSlideBackstackPresenter.kt",
    "content": "package software.amazon.app.platform.recipes.backstack\n\nimport androidx.compose.runtime.Composable\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackHandlerPresenter\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.recipes.appbar.AppBarConfigModel\nimport software.amazon.app.platform.recipes.backstack.CrossSlideBackstackPresenter.Model\n\n/**\n * A generic presenter that wraps a presenter backstack to play a cross-fade animation whenever a\n * presenter is pushed to the stack or popped from the stack. A backstack always contains\n * [initialPresenter] as an element.\n */\nclass CrossSlideBackstackPresenter(\n  private val initialPresenter: MoleculePresenter<Unit, out BaseModel>\n) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    return presenterBackstack(initialPresenter) { model ->\n      // Pop the top presenter on a back press event.\n      BackHandlerPresenter(enabled = lastBackstackChange.value.backstack.size > 1) { pop() }\n\n      Model(delegate = model, backstackScope = this)\n    }\n  }\n\n  /**\n   * The model containing all information about the backstack to play a cross-slide animation in the\n   * renderer. [delegate] refers to the model of the top most presenter in the stack.\n   * [backstackScope] contains the backstack and allows you to modify the stack.\n   */\n  data class Model(val delegate: BaseModel, val backstackScope: PresenterBackstackScope) :\n    BaseModel, AppBarConfigModel {\n    override fun appBarConfig(): AppBarConfig {\n      return if (delegate is AppBarConfigModel) {\n        delegate.appBarConfig()\n      } else {\n        AppBarConfig.DEFAULT\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/CrossSlideBackstackRenderer.kt",
    "content": "package software.amazon.app.platform.recipes.backstack\n\nimport androidx.compose.animation.core.FiniteAnimationSpec\nimport androidx.compose.animation.core.MutableTransitionState\nimport androidx.compose.animation.core.Transition\nimport androidx.compose.animation.core.animateOffset\nimport androidx.compose.animation.core.rememberTransition\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.key\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.platform.LocalWindowInfo\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.recipes.backstack.CrossSlideBackstackPresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\n\n/**\n * Plays a cross-slide animation whenever the backstack provided [CrossSlideBackstackPresenter]\n * changes.\n */\n@Inject\n@ContributesRenderer\nclass CrossSlideBackstackRenderer(private val rendererFactory: RendererFactory) :\n  ComposeRenderer<Model>() {\n\n  @Composable\n  override fun Compose(model: Model) {\n    Column(modifier = Modifier.fillMaxSize()) {\n      CrossSlide(\n        targetState = model.delegate,\n        reverseAnimation =\n          model.backstackScope.lastBackstackChange.value.action ==\n            PresenterBackstackScope.BackstackChange.Action.POP,\n        contentKey = {\n          // Include size of the backstack in the key in case the model type is the same but the\n          // position in the stack differs.\n          it::class.hashCode() + model.backstackScope.lastBackstackChange.value.backstack.size\n        },\n      ) { screen ->\n        rendererFactory.getComposeRenderer(screen).renderCompose(screen)\n      }\n    }\n  }\n\n  // https://gist.github.com/DavidIbrahim/5f4c0387b571f657f4de976822c2a225\n  //\n  // This animation implementation comes from the link above, but was modified with the\n  // `contentKey` parameter to allows updates of the `targetState` without playing another\n  // cross slide animation.\n  //\n  // Further the distance was adjusted to the screen size.\n  @Composable\n  @Suppress(\"LongMethod\", \"MagicNumber\")\n  private fun <T : Any> CrossSlide(\n    targetState: T,\n    contentKey: (targetState: T) -> Any = { it },\n    modifier: Modifier = Modifier,\n    animationSpec: FiniteAnimationSpec<Offset> = tween(500),\n    reverseAnimation: Boolean = false,\n    content: @Composable (T) -> Unit,\n  ) {\n    data class SlideInOutAnimationState<T>(\n      val key: Any,\n      val state: T,\n      val content: @Composable () -> Unit,\n    )\n\n    val direction: Int = if (reverseAnimation) -1 else 1\n    val items = remember { mutableStateListOf<SlideInOutAnimationState<T>>() }\n\n    val targetKey = contentKey(targetState)\n\n    // We only play a cross slide animation / transition, if the key has changed. If the state has\n    // changed, but the key didn't, then we only need to update the rendered target state in the\n    // same item.\n    val transitionState = remember { MutableTransitionState(targetKey) }\n    transitionState.targetState = targetKey\n    val transition: Transition<Any> = rememberTransition(transitionState)\n\n    val targetContentChanged = items.firstOrNull { it.key == targetKey }?.state != targetState\n\n    if (targetContentChanged || items.isEmpty()) {\n      // Only manipulate the list when the state is changed, or in the first run.\n      items\n        .map {\n          if (it.key == targetKey) {\n            // Change the old state remembered in the list to the new state.\n            it.key to targetState\n          } else {\n            it.key to it.state\n          }\n        }\n        .let {\n          if (it.none { (key, _) -> key == targetKey }) {\n            it + Pair(targetKey, targetState)\n          } else {\n            it\n          }\n        }\n        .also { items.clear() }\n        .mapTo(items) { (key, state) ->\n          SlideInOutAnimationState(key, state) {\n            val xTransition by\n              transition.animateOffset(transitionSpec = { animationSpec }, label = \"\") {\n                val containerSize = LocalWindowInfo.current.containerSize\n                if (it == key) {\n                  Offset(0f, 0f)\n                } else {\n                  Offset(containerSize.width.toFloat(), containerSize.height.toFloat())\n                }\n              }\n            Box(\n              modifier.graphicsLayer {\n                this.translationX =\n                  if (transition.targetState == key) {\n                    direction * xTransition.x\n                  } else {\n                    direction * -xTransition.x\n                  }\n              }\n            ) {\n              content(state)\n            }\n          }\n        }\n    } else if (transitionState.isIdle) {\n      // Remove all the intermediate items from the list once the animation is finished.\n      items.removeAll { it.key != transitionState.targetState }\n    }\n\n    Box(modifier) { items.forEach { key(it.key) { it.content() } } }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/PresenterBackstackScope.kt",
    "content": "package software.amazon.app.platform.recipes.backstack\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.State\nimport androidx.compose.runtime.compositionLocalOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.recipes.backstack.PresenterBackstackScope.BackstackChange\nimport software.amazon.app.platform.recipes.backstack.PresenterBackstackScope.BackstackChange.Action\nimport software.amazon.app.platform.recipes.saveable.rememberReturningSaveableStateHolder\n\n/**\n * Receiver scope for content lambda for [presenterBackstack]. In this scope, [lastBackstackChange]\n * can be used to observe the state of the backstack, or to [push] and [pop] presenters.\n */\ninterface PresenterBackstackScope {\n\n  /** Provides the last change made to the backstack. */\n  val lastBackstackChange: State<BackstackChange>\n\n  /** Pushes a new presenter to the top of the backstack. This will update [lastBackstackChange]. */\n  fun push(presenter: MoleculePresenter<Unit, out BaseModel>)\n\n  /**\n   * Removes the top presenter from the backstack. This will update [lastBackstackChange]. Note that\n   * the stack will always contain the initial presenter provided in [presenterBackstack] and this\n   * presenter cannot be popped.\n   */\n  fun pop()\n\n  /** Describes the current state of the backstack with the last change. */\n  interface BackstackChange {\n\n    /**\n     * The stack of presenters. This list will always contain at least one element, which is the\n     * initial presenter provided in [presenterBackstack].\n     */\n    val backstack: List<MoleculePresenter<Unit, out BaseModel>>\n\n    /**\n     * The last executed for the backstack. Knowing what kind of change was made is helpful to\n     * animate changes in the UI.\n     */\n    val action: Action\n\n    /** Describes all actions that can be applied to the backstack. */\n    enum class Action {\n      /** A new presenter was added to the top of the backstack. */\n      PUSH,\n\n      /** The last top presenter in the stack was removed. */\n      POP,\n    }\n  }\n}\n\nprivate class PresenterBackstackScopeImpl(initial: MoleculePresenter<Unit, out BaseModel>) :\n  PresenterBackstackScope {\n  private var _lastBackstackChange =\n    mutableStateOf(BackstackChangeImpl(listOf(initial), Action.PUSH))\n  override val lastBackstackChange: State<BackstackChange> = _lastBackstackChange\n\n  override fun push(presenter: MoleculePresenter<Unit, out BaseModel>) {\n    _lastBackstackChange.value =\n      BackstackChangeImpl(\n        backstack = _lastBackstackChange.value.backstack + presenter,\n        action = Action.PUSH,\n      )\n  }\n\n  override fun pop() {\n    val oldStack = _lastBackstackChange.value.backstack\n    if (oldStack.size > 1) {\n      _lastBackstackChange.value =\n        BackstackChangeImpl(backstack = oldStack.subList(0, oldStack.size - 1), action = Action.POP)\n    }\n  }\n\n  private class BackstackChangeImpl(\n    override val backstack: List<MoleculePresenter<Unit, out BaseModel>>,\n    override val action: Action,\n  ) : BackstackChange\n}\n\n/**\n * Provides the backstack closest to this presenter or `null` if there's none. Presenters build a\n * tree. When the backstack is queried, then the backstack first found while walking the tree to the\n * root is returned. In the tree there can be multiple backstacks.\n *\n * ```\n * @Composable\n * override fun present(input: Unit): Model {\n *   val backstack = checkNotNull(LocalBackstackScope.current)\n *   ...\n * }\n * ```\n */\nval LocalBackstackScope = compositionLocalOf<PresenterBackstackScope?> { null }\n\n/**\n * Creates a new backstack for presenters. A backstack always contains [initialPresenter] as an\n * element.\n *\n * [content] is invoked with the [BaseModel] of the top presenter in the stack. The lambda returns\n * the model of the presenter this function [presenterBackstack] is called in. The receiver type\n * [PresenterBackstackScope] allows you to modify the backstack or get information about changes.\n *\n * ```\n * class SomePresenter : MoleculePresenter<Unit, SomeModel> {\n *   @Composable\n *   override fun present(input: Unit): SomeModel {\n *     val initialPresenter = ...\n *     return presenterBackstack(initialPresenter) { model ->\n *       SomeModel(...)\n *     }\n *   }\n *\n *   data class SomeModel(...) : BaseModel\n * }\n *\n * ```\n *\n * Child presenters get access to the backstack through [LocalBackstackScope], e.g.\n *\n * ```\n * @Composable\n * override fun present(input: Unit): Model {\n *   val backstack = checkNotNull(LocalBackstackScope.current)\n *   ...\n * }\n * ```\n *\n * To use a cross-slide animation by default for backstack changes, consider using\n * [CrossSlideBackstackPresenter].\n */\n@Composable\nfun <ModelT : BaseModel> presenterBackstack(\n  initialPresenter: MoleculePresenter<Unit, out BaseModel>,\n  content: @Composable PresenterBackstackScope.(model: BaseModel) -> ModelT,\n): ModelT {\n  val scope = remember { PresenterBackstackScopeImpl(initialPresenter) }\n  val saveableStateHolder = rememberReturningSaveableStateHolder()\n\n  val presenter = scope.lastBackstackChange.value.backstack.last()\n\n  return saveableStateHolder.SaveableStateProvider(key = presenter) {\n    returningCompositionLocalProvider(LocalBackstackScope provides scope) {\n      val backstackModel = presenter.present(Unit)\n      content.invoke(scope, backstackModel)\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/backstack/presenter/BackstackChildPresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.backstack.presenter\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.recipes.appbar.AppBarConfigModel\nimport software.amazon.app.platform.recipes.backstack.LocalBackstackScope\nimport software.amazon.app.platform.recipes.backstack.presenter.BackstackChildPresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\n\n/**\n * A presenter that is added to the backstack and has a button to put a new instance on top of the\n * stack.\n */\nclass BackstackChildPresenter(private val index: Int) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val backstack = checkNotNull(LocalBackstackScope.current)\n\n    var counter by rememberSaveable { mutableIntStateOf(0) }\n\n    LaunchedEffect(Unit) {\n      while (isActive) {\n        delay(1.seconds)\n        counter += 1\n      }\n    }\n\n    return Model(index = index, counter = counter) {\n      when (it) {\n        Event.AddPresenterToBackstack -> backstack.push(BackstackChildPresenter(index = index + 1))\n      }\n    }\n  }\n\n  data class Model(val index: Int, val counter: Int, val onEvent: (Event) -> Unit) :\n    BaseModel, AppBarConfigModel {\n    override fun appBarConfig(): AppBarConfig {\n      return AppBarConfig(title = \"Backstack\")\n    }\n  }\n\n  sealed interface Event {\n    data object AddPresenterToBackstack : Event\n  }\n}\n\n@ContributesRenderer\nclass BackstackChildRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(\n      modifier = Modifier.fillMaxSize().padding(top = 12.dp),\n      horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n      Text(\"Index: ${model.index}\")\n      Text(\"Counter: ${model.counter}\")\n\n      Button(onClick = { model.onEvent(BackstackChildPresenter.Event.AddPresenterToBackstack) }) {\n        Text(\"Add presenter to backstack\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/landing/LandingPresenter.kt",
    "content": "package software.amazon.app.platform.recipes.landing\n\nimport androidx.compose.runtime.Composable\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.appbar.menu.MenuPresenter\nimport software.amazon.app.platform.recipes.backstack.LocalBackstackScope\nimport software.amazon.app.platform.recipes.backstack.presenter.BackstackChildPresenter\nimport software.amazon.app.platform.recipes.landing.LandingPresenter.Model\nimport software.amazon.app.platform.recipes.nav3.Navigation3HomePresenter\n\n/** The presenter that is responsible to show the content of the landing page in the Recipes app. */\n@Inject\nclass LandingPresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val backstack = checkNotNull(LocalBackstackScope.current)\n\n    return Model {\n      when (it) {\n        Event.AddPresenterToBackstack -> {\n          backstack.push(BackstackChildPresenter(0))\n        }\n\n        Event.MenuPresenter -> {\n          backstack.push(MenuPresenter())\n        }\n\n        Event.Navigation3 -> {\n          backstack.push(Navigation3HomePresenter())\n        }\n      }\n    }\n  }\n\n  /** The state of the landing screen. */\n  data class Model(\n    /** Callback to send events back to the presenter. */\n    val onEvent: (Event) -> Unit\n  ) : BaseModel\n\n  /** All events that [LandingPresenter] can process. */\n  sealed interface Event {\n    /** Add a new presenter to the backstack. */\n    data object AddPresenterToBackstack : Event\n\n    /** Show the presenter with a custom App Bar menu. */\n    data object MenuPresenter : Event\n\n    /** Show the presenter highlighting navigation3 integration. */\n    data object Navigation3 : Event\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/landing/LandingRenderer.kt",
    "content": "package software.amazon.app.platform.recipes.landing\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.recipes.landing.LandingPresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\n\n/** Renders the content for [LandingPresenter] on screen. */\n@ContributesRenderer\nclass LandingRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(\n      modifier = Modifier.fillMaxSize().padding(top = 12.dp),\n      horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n      Button(onClick = { model.onEvent(LandingPresenter.Event.AddPresenterToBackstack) }) {\n        Text(\"Add presenter to backstack\")\n      }\n      Button(\n        onClick = { model.onEvent(LandingPresenter.Event.MenuPresenter) },\n        modifier = Modifier.padding(top = 12.dp),\n      ) {\n        Text(\"Menu presenter\")\n      }\n      Button(\n        onClick = { model.onEvent(LandingPresenter.Event.Navigation3) },\n        modifier = Modifier.padding(top = 12.dp),\n      ) {\n        Text(\"Navigation3\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3ChildPresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.nav3\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.produceState\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.snapshots.SnapshotStateList\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.nav3.Navigation3ChildPresenter.Model\n\nclass Navigation3ChildPresenter(\n  private val index: Int,\n  private val backstack: SnapshotStateList<MoleculePresenter<Unit, out BaseModel>>,\n) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val color = remember { nextColor() }\n\n    val counter by\n      produceState(0) {\n        while (isActive) {\n          delay(1.seconds)\n          value += 1\n        }\n      }\n\n    return Model(index = index, color = color, counter = counter) {\n      when (it) {\n        Event.AddPresenter ->\n          backstack.add(Navigation3ChildPresenter(index = index + 1, backstack = backstack))\n      }\n    }\n  }\n\n  data class Model(\n    val index: Int,\n    val color: Long,\n    val counter: Int,\n    val onEvent: (Event) -> Unit,\n  ) : BaseModel\n\n  sealed interface Event {\n    data object AddPresenter : Event\n  }\n\n  private companion object {\n    private val colors =\n      listOf(0xFFA5D6A7, 0xFF81D4FA, 0xFFB0BEC5, 0xFFBCAAA4, 0xFF80CBC4, 0xFFFFAB91)\n\n    private var index = 0\n\n    fun nextColor(): Long {\n      index = (index + 1).mod(colors.size)\n      return colors[index]\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3ChildRenderer.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.nav3\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.recipes.nav3.Navigation3ChildPresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\n\n@ContributesRenderer\nclass Navigation3ChildRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Box(modifier = Modifier.Companion.fillMaxSize().background(Color(model.color))) {\n      Column {\n        Text(\"Index: ${model.index}\")\n        Text(\"Count: ${model.counter}\")\n      }\n\n      Button(\n        onClick = { model.onEvent(Navigation3ChildPresenter.Event.AddPresenter) },\n        modifier = Modifier.Companion.align(Alignment.Companion.Center),\n      ) {\n        Text(\"Add Presenter\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3HomePresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.nav3\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.remember\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.recipes.appbar.AppBarConfigModel\nimport software.amazon.app.platform.recipes.nav3.Navigation3HomePresenter.Model\n\n/**\n * This presenter manages its own backstack. All presenters in the stack are always active, because\n * Navigation3 renders them during a back gesture. This could be optimized further in the future to\n * compute the model of the top 2 presenters only and remembering the state of all other presenters.\n */\n@Inject\nclass Navigation3HomePresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val backstack = remember {\n      mutableStateListOf<MoleculePresenter<Unit, out BaseModel>>().apply {\n        // There must be always one element.\n        add(Navigation3ChildPresenter(index = 0, backstack = this))\n      }\n    }\n\n    return Model(backstack = backstack.map { it.present(Unit) }) {\n      when (it) {\n        Event.Pop -> {\n          backstack.removeAt(backstack.size - 1)\n        }\n      }\n    }\n  }\n\n  data class Model(val backstack: List<BaseModel>, val onEvent: (Event) -> Unit) :\n    BaseModel, AppBarConfigModel {\n    override fun appBarConfig(): AppBarConfig = AppBarConfig(title = \"Navigation3\")\n  }\n\n  sealed interface Event {\n    data object Pop : Event\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/nav3/Navigation3HomeRenderer.kt",
    "content": "package software.amazon.app.platform.recipes.nav3\n\nimport androidx.compose.runtime.Composable\nimport androidx.navigation3.runtime.NavEntry\nimport androidx.navigation3.ui.NavDisplay\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.recipes.nav3.Navigation3HomePresenter.Event\nimport software.amazon.app.platform.recipes.nav3.Navigation3HomePresenter.Model\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\n\n/**\n * Renderer that integrates the multiplatform Navigation3 UI implementation. The backstack is\n * managed in the presenter for idiomatic navigation with presenters, but interactions with the\n * backstack are handled by the Navigation3 library, e.g. back gestures.\n */\n@Inject\n@ContributesRenderer\nclass Navigation3HomeRenderer(private val rendererFactory: RendererFactory) :\n  ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    // Use the position of the model in the backstack as key for `NavDisplay`. This way\n    // we can update models without Navigation 3 treating those changes as a new screen.\n    val backstack = model.backstack.mapIndexed { index, _ -> index }\n\n    NavDisplay(\n      backStack = backstack,\n      onBack = { model.onEvent(Event.Pop) },\n      entryProvider = { key ->\n        NavEntry(key) {\n          val model = model.backstack[it]\n          rendererFactory.getComposeRenderer(model).renderCompose(model)\n        }\n      },\n    )\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/saveable/ReturningSaveableStateHolder.kt",
    "content": "package software.amazon.app.platform.recipes.saveable\n\nimport androidx.collection.mutableScatterMapOf\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.ReusableContent\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.LocalSaveableStateRegistry\nimport androidx.compose.runtime.saveable.SaveableStateHolder\nimport androidx.compose.runtime.saveable.SaveableStateRegistry\nimport androidx.compose.runtime.saveable.Saver\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.saveable.rememberSaveableStateHolder\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\n\n/**\n * Allows to save the state defined with [rememberSaveable] for the subtree before disposing it to\n * make it possible to compose it back next time with the restored state. It allows different\n * navigation patterns to keep the ui state like scroll position for the currently not composed\n * screens from the backstack.\n *\n * The content should be composed using [SaveableStateProvider] while providing a key representing\n * this content. Next time [SaveableStateProvider] will be used with the same key its state will be\n * restored.\n *\n * This implementation is similar to [SaveableStateHolder] with the only difference that\n * [ReturningSaveableStateHolder] returns a result and doesn't have `Unit` as return type.\n */\ninterface ReturningSaveableStateHolder {\n  /**\n   * Put your content associated with a [key] inside the [content]. This will automatically save all\n   * the states defined with [rememberSaveable] before disposing the content and will restore the\n   * states when you compose with this key again.\n   *\n   * @param key to be used for saving and restoring the states for the subtree. Note that on Android\n   *   you can only use types which can be stored inside the Bundle.\n   * @param content the content for which [key] is associated.\n   */\n  @Suppress(\"ComposableNaming\")\n  @Composable\n  fun <ModelT : BaseModel> SaveableStateProvider(\n    key: Any,\n    content: @Composable () -> ModelT,\n  ): ModelT\n\n  /** Removes the saved state associated with the passed [key]. */\n  fun removeState(key: Any)\n}\n\n/**\n * Creates and remembers the instance of [ReturningSaveableStateHolder].\n *\n * This implementation is similar to [rememberSaveableStateHolder] with the only difference that\n * [ReturningSaveableStateHolder] returns a result and doesn't have `Unit` as return type.\n */\n@Composable\nfun rememberReturningSaveableStateHolder(): ReturningSaveableStateHolder =\n  rememberSaveable(saver = CustomSaveableStateHolderImpl.Saver) { CustomSaveableStateHolderImpl() }\n    .apply { parentSaveableStateRegistry = LocalSaveableStateRegistry.current }\n\nprivate class CustomSaveableStateHolderImpl(\n  private val savedStates: MutableMap<Any, Map<String, List<Any?>>> = mutableMapOf()\n) : ReturningSaveableStateHolder {\n  private val registries = mutableScatterMapOf<Any, SaveableStateRegistry>()\n  var parentSaveableStateRegistry: SaveableStateRegistry? = null\n  private val canBeSaved: (Any) -> Boolean = { parentSaveableStateRegistry?.canBeSaved(it) ?: true }\n\n  @Composable\n  override fun <ModelT : BaseModel> SaveableStateProvider(\n    key: Any,\n    content: @Composable () -> ModelT,\n  ): ModelT {\n    return returningReusableContent(key) {\n      val registry = remember {\n        require(canBeSaved(key)) {\n          \"Type of the key $key is not supported. On Android you can only use types \" +\n            \"which can be stored inside the Bundle.\"\n        }\n        SaveableStateRegistry(savedStates[key], canBeSaved)\n      }\n      val model =\n        returningCompositionLocalProvider(\n          LocalSaveableStateRegistry provides registry,\n          content = content,\n        )\n      DisposableEffect(Unit) {\n        require(key !in registries) { \"Key $key was used multiple times \" }\n        savedStates -= key\n        registries[key] = registry\n        onDispose {\n          if (registries.remove(key) === registry) {\n            registry.saveTo(savedStates, key)\n          }\n        }\n      }\n\n      model\n    }\n  }\n\n  private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>>? {\n    val map = savedStates\n    registries.forEach { key, registry -> registry.saveTo(map, key) }\n    return map.ifEmpty { null }\n  }\n\n  override fun removeState(key: Any) {\n    if (registries.remove(key) == null) {\n      savedStates -= key\n    }\n  }\n\n  private fun SaveableStateRegistry.saveTo(\n    map: MutableMap<Any, Map<String, List<Any?>>>,\n    key: Any,\n  ) {\n    val savedData = performSave()\n    if (savedData.isEmpty()) {\n      map -= key\n    } else {\n      map[key] = savedData\n    }\n  }\n\n  companion object {\n    val Saver: Saver<CustomSaveableStateHolderImpl, *> =\n      Saver(save = { it.saveAll() }, restore = { CustomSaveableStateHolderImpl(it) })\n  }\n\n  @Composable\n  private inline fun <ModelT : BaseModel> returningReusableContent(\n    key: Any?,\n    content: @Composable () -> ModelT,\n  ): ModelT {\n    lateinit var result: ModelT\n    ReusableContent(key) { result = content() }\n    return result\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/swiftui/SwiftUiChildPresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.swiftui\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.produceState\nimport androidx.compose.runtime.snapshots.SnapshotStateList\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.swiftui.SwiftUiChildPresenter.Model\n\nclass SwiftUiChildPresenter(\n  private val index: Int,\n  private val backstack: SnapshotStateList<MoleculePresenter<Unit, out BaseModel>>,\n) : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val counter by\n      produceState(0) {\n        while (isActive) {\n          delay(1.seconds)\n          value += 1\n        }\n      }\n\n    return Model(index = index, counter = counter) {\n      when (it) {\n        Event.AddPeer ->\n          backstack.add(SwiftUiChildPresenter(index = index + 1, backstack = backstack))\n      }\n    }\n  }\n\n  data class Model(val index: Int, val counter: Int, val onEvent: (Event) -> Unit) : BaseModel\n\n  sealed interface Event {\n    data object AddPeer : Event\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/swiftui/SwiftUiHomePresenter.kt",
    "content": "@file:Suppress(\"UndocumentedPublicProperty\", \"UndocumentedPublicClass\")\n\npackage software.amazon.app.platform.recipes.swiftui\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.remember\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.recipes.swiftui.SwiftUiHomePresenter.Model\n\n/**\n * A presenter that manages a backstack of presenters that are rendered by SwiftUI's\n * `NavigationStack`. All presenters in this backstack are always active, because `NavigationStack`\n * renders them on stack modification. In SwiftUI this is necessary as views remain alive even when\n * they are no longer visible.\n *\n * A detail of note for this class is that we pass a list of [BaseModel] to the view but receive a\n * list of [Int] back where each integer represents the position of a presenter in the backstack\n * list. This is because to share control of state with `NavigationStack` we need to initialize the\n * `NavigationStack` with a `Binding` to a collection of `Hashable` data values. [BaseModel] by\n * default is not `Hashable` and we cannot extend it to conform to `Hashable` due to current\n * Kotlin-Swift interop limitations. As such in Swift the list of [BaseModel] is converted to a list\n * of indices, which are hashable by default. This should be sufficient to handle most navigation\n * cases but if it is required to receive more information to determine how to modify the presenter\n * backstack, it is possible to create a generic class that implements [BaseModel] and wrap that\n * class in a hashable `struct`.\n */\n@Inject\nclass SwiftUiHomePresenter : MoleculePresenter<Unit, Model> {\n  @Composable\n  override fun present(input: Unit): Model {\n    val backstack = remember {\n      mutableStateListOf<MoleculePresenter<Unit, out BaseModel>>().apply {\n        // There must be always one element.\n        add(SwiftUiChildPresenter(index = 0, backstack = this))\n      }\n    }\n\n    return Model(modelBackstack = backstack.map { it.present(Unit) }) {\n      when (it) {\n        is Event.BackstackModificationEvent -> {\n          val updatedBackstack = it.indicesBackstack.map { index -> backstack[index] }\n\n          backstack.clear()\n          backstack.addAll(updatedBackstack)\n        }\n      }\n    }\n  }\n\n  /**\n   * Model that contains all the information needed for SwiftUI to render the backstack.\n   * [modelBackstack] contains the backage and [onEvent] exposes an event handling function that can\n   * be called by the binding that `NavigationStack` is initialized with.\n   */\n  data class Model(val modelBackstack: List<BaseModel>, val onEvent: (Event) -> Unit) : BaseModel\n\n  /** All events that [SwiftUiHomePresenter] can process. */\n  sealed interface Event {\n    /** Sent when `NavigationStack` has modified its stack. */\n    data class BackstackModificationEvent(val indicesBackstack: List<Int>) : Event\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/template/RecipesAppTemplate.kt",
    "content": "package software.amazon.app.platform.recipes.template\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.template.Template\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\n\n/** All [Template]s implemented in the recipes application. */\nsealed interface RecipesAppTemplate : Template {\n  /** A template that hosts a single model, which should rendered as full-screen element. */\n  data class FullScreenTemplate(\n    /** The model to be rendered fullscreen. */\n    val model: BaseModel,\n    /** The configuration for the app bar of the recipe app. */\n    val appBarConfig: AppBarConfig,\n  ) : RecipesAppTemplate\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/template/RootPresenter.kt",
    "content": "package software.amazon.app.platform.recipes.template\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.LocalBackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.presenter.template.toTemplate\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.recipes.appbar.AppBarConfigModel\nimport software.amazon.app.platform.recipes.backstack.CrossSlideBackstackPresenter\nimport software.amazon.app.platform.recipes.landing.LandingPresenter\n\n/**\n * A presenter that wraps any other presenter and turns the emitted models from the other presenter\n * into [RecipesAppTemplate]s.\n */\n@Inject\nclass RootPresenter(\n  private val landingPresenter: LandingPresenter,\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n) : MoleculePresenter<Unit, RecipesAppTemplate> {\n  @Composable\n  override fun present(input: Unit): RecipesAppTemplate {\n    return returningCompositionLocalProvider(\n      LocalBackGestureDispatcherPresenter provides backGestureDispatcherPresenter\n    ) {\n      val backstackPresenter = remember { CrossSlideBackstackPresenter(landingPresenter) }\n      val backstackModel = backstackPresenter.present(Unit)\n\n      backstackModelToTemplate(backstackModel)\n    }\n  }\n\n  @Composable\n  private fun backstackModelToTemplate(\n    backstackModel: CrossSlideBackstackPresenter.Model\n  ): RecipesAppTemplate {\n    val backstackScope = backstackModel.backstackScope\n    val showBackArrow = backstackScope.lastBackstackChange.value.backstack.size > 1\n\n    val backArrowAction =\n      if (showBackArrow) {\n        { backstackScope.pop() }\n      } else {\n        null\n      }\n\n    return backstackModel.toTemplate { model ->\n      val appBarConfig =\n        if (model is AppBarConfigModel) {\n          model.appBarConfig().copy(backArrowAction = backArrowAction)\n        } else {\n          AppBarConfig(title = AppBarConfig.DEFAULT.title, backArrowAction = backArrowAction)\n        }\n\n      RecipesAppTemplate.FullScreenTemplate(model, appBarConfig)\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/common/impl/src/commonMain/kotlin/software/amazon/app/platform/recipes/template/RootPresenterRenderer.kt",
    "content": "@file:OptIn(ExperimentalMaterial3Api::class)\n\npackage software.amazon.app.platform.recipes.template\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material3.CenterAlignedTopAppBar\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.rememberTopAppBarState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport me.tatarka.inject.annotations.Inject\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.ForwardBackPressEventsToPresenters\nimport software.amazon.app.platform.recipes.appbar.AppBarConfig\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\n\n/**\n * A Compose renderer implementation for templates used in the recipes application.\n *\n * [rendererFactory] is used to get the [Renderer] for the [BaseModel] wrapped in the template.\n */\n@Inject\n@ContributesRenderer\nclass RootPresenterRenderer(\n  private val rendererFactory: RendererFactory,\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n) : ComposeRenderer<RecipesAppTemplate>() {\n  @Composable\n  override fun Compose(model: RecipesAppTemplate) {\n    backGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()\n\n    when (model) {\n      is RecipesAppTemplate.FullScreenTemplate -> FullScreen(model)\n    }\n  }\n\n  @Composable\n  private fun FullScreen(template: RecipesAppTemplate.FullScreenTemplate) {\n    CenterAlignedTopAppBar(template.appBarConfig) {\n      Box(modifier = Modifier.padding(it)) {\n        val renderer = rendererFactory.getComposeRenderer(template.model)\n        renderer.renderCompose(template.model)\n      }\n    }\n  }\n\n  @Composable\n  private fun CenterAlignedTopAppBar(\n    appBarConfig: AppBarConfig,\n    content: @Composable (PaddingValues) -> Unit,\n  ) {\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())\n\n    Scaffold(\n      modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),\n      topBar = {\n        CenterAlignedTopAppBar(\n          colors =\n            TopAppBarDefaults.topAppBarColors(\n              containerColor = MaterialTheme.colorScheme.primaryContainer,\n              titleContentColor = MaterialTheme.colorScheme.primary,\n            ),\n          title = { Text(appBarConfig.title, maxLines = 1, overflow = TextOverflow.Ellipsis) },\n          navigationIcon = {\n            if (appBarConfig.backArrowAction != null) {\n              IconButton(onClick = appBarConfig.backArrowAction) {\n                Icon(\n                  imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                  contentDescription = \"Localized description\",\n                )\n              }\n            }\n          },\n          actions = {\n            if (appBarConfig.menuItems.isNotEmpty()) {\n              MinimalDropdownMenu(appBarConfig.menuItems)\n            }\n          },\n          scrollBehavior = scrollBehavior,\n        )\n      },\n    ) { innerPadding ->\n      content(innerPadding)\n    }\n  }\n\n  @Composable\n  private fun MinimalDropdownMenu(menuItems: List<AppBarConfig.MenuItem>) {\n    var expanded by remember { mutableStateOf(false) }\n    Box(modifier = Modifier.padding(16.dp)) {\n      IconButton(onClick = { expanded = !expanded }) {\n        Icon(imageVector = Icons.Filled.MoreVert, contentDescription = \"More options\")\n      }\n      DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {\n        menuItems.forEach { item ->\n          DropdownMenuItem(\n            text = { Text(item.text) },\n            onClick = {\n              expanded = false\n              item.action()\n            },\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"app-icon-1024.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"tinted\"\n        }\n      ],\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/ContentView.swift",
    "content": "//\n//  ContentView.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/10/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\nstruct ComposeView: UIViewControllerRepresentable {\n    private var rootScopeProvider: RootScopeProvider\n\n    init(rootScopeProvider: RootScopeProvider) {\n        self.rootScopeProvider = rootScopeProvider\n    }\n\n    func makeUIViewController(context: Context) -> UIViewController {\n        MainViewControllerKt.mainViewController(rootScopeProvider: rootScopeProvider)\n    }\n\n    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}\n}\n\nstruct ContentView: View {\n    var appDelegate: AppDelegate\n    \n    @State var showComposeRecipes = false\n    @State var showSwiftUIRecipe = false\n\n    init(appDelegate: AppDelegate) {\n        self.appDelegate = appDelegate\n    }\n\n    var body: some View {\n        VStack {\n            Spacer()\n            \n            Button(action: { showComposeRecipes.toggle() }) {\n                Text(\"CMP-rendered recipes\")\n            }\n            .buttonStyle(.borderedProminent)\n            \n            Spacer()\n            \n            Button(action: { showSwiftUIRecipe.toggle() }) {\n                Text(\"SwiftUI recipe\")\n            }\n            .buttonStyle(.borderedProminent)\n            \n            Spacer()\n        }\n        .sheet(isPresented: $showComposeRecipes) {\n            ComposeView(rootScopeProvider: appDelegate)\n                .ignoresSafeArea(.keyboard) // Compose has its own keyboard handler\n        }\n        .sheet(isPresented: $showSwiftUIRecipe) {\n            SwiftUiRootPresenterView(\n                homePresenter: SwiftUiHomePresenterBuilder(appDelegate: appDelegate).makeHomePresenter()\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CADisableMinimumFrameDurationOnPhone</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/PresenterViews/AppPlatform+Extensions.swift",
    "content": "//\n//  AppPlatform+Extensions.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport RecipesApp\nimport SwiftUI\n\nextension Presenter {\n    /// Returns an async sequence of type `Model` from a `Presenter` model `StateFlow`.\n    func viewModels<Model>(ofType type: Model.Type) -> AsyncThrowingStream<Model, Error> {\n        model\n            .values()\n            .compactMap { $0 as? Model }\n            .asAsyncThrowingStream()\n    }\n}\n\nenum KotlinFlowError {\n    case unexpectedValueInKotlinFlow(value: Any, expectedType: String)\n}\n\nextension Kotlinx_coroutines_coreFlow {\n\n    /// Returns an async sequence of Any? from the Kotlin Flow.\n    ///\n    /// The Flows send Any, so we lose type information and need to cast at runtime instead of getting a type-safe compile time check.\n    /// You can use `valuesOfType` instead which returns a stream that throws an error if the values are not of the right type.\n    /// `valuesOfType` is usually preferred because we want to catch bad values from Kotlin instead of the Flow going silent.\n    func values() -> AsyncThrowingStream<Any?, Error> {\n        let collector = Kotlinx_coroutines_coreFlowCollectorImpl<Any?>()\n        collect(collector: collector, completionHandler: collector.onComplete(_:))\n        return collector.values\n    }\n\n    /// Returns an async sequence from the Kotlin Flow.\n    ///\n    /// The Flows send Any, so we lose type information and need to cast at runtime instead of getting a type-safe compile time check.\n    /// If the Flow sends the right type, this stream will throw an error.\n    /// This is usually preferred because we want to catch bad values from Kotlin instead of the Flow going silent.\n    func valuesOfType<T>(_ type: T.Type = T.self) -> AsyncThrowingStream<T, Error> {\n        let collector = Kotlinx_coroutines_coreFlowCollectorImpl<T>()\n        Task { @MainActor in\n            do {\n                try await collect(collector: collector)\n                collector.onComplete(nil)\n            } catch {\n                collector.onComplete(error)\n            }\n        }\n        return collector.values\n    }\n}\n\nfileprivate class Kotlinx_coroutines_coreFlowCollectorImpl<Value>: Kotlinx_coroutines_coreFlowCollector {\n\n    let values: AsyncThrowingStream<Value, Error>\n    private let continuation: AsyncThrowingStream<Value, Error>.Continuation\n\n    init() {\n        let (values, continuation) = AsyncThrowingStream<Value, Error>.makeStream()\n        self.values = values\n        self.continuation = continuation\n    }\n\n    func emit(value: Any?) async throws {\n        if let castedValue = value as? Value {\n            continuation.yield(castedValue)\n        }\n    }\n\n    func onComplete(_ error: Error?) {\n        continuation.finish(throwing: error)\n    }\n\n    deinit {\n        print(\"Deiniting collector\")\n    }\n}\n\nextension AsyncSequence {\n\n    func asAsyncThrowingStream() -> AsyncThrowingStream<Element, Error> {\n        if let self = self as? AsyncThrowingStream<Element, Error> {\n            return self\n        }\n        var asyncIterator = self.makeAsyncIterator()\n        return AsyncThrowingStream<Element, Error> {\n            try await asyncIterator.next()\n        }\n    }\n}\n\nextension Int {\n    /// Converts Swift Int to Kotlin's Int type for interop.\n    func toKotlinInt() -> KotlinInt {\n        return KotlinInt(integerLiteral: self)\n    }\n}\n\nextension BaseModel {\n    /// Gets the view for some `BaseModel`\n    ///\n    /// Returns. created by `makeViewRenderer()` if a model conforms to `PresenterViewModel` otherwise, crash the build for\n    /// debug builds or return a default view.\n    @MainActor func getViewRenderer() -> AnyView {\n        guard let viewModel = self as? (any PresenterViewModel) else {\n            assertionFailure(\"ViewModel \\(self) does not conform to `PresenterViewModel`\")\n            \n            // This is an implementation detail. If crashing is preferred even in production builds, `fatalError(..)`\n            // can be used instead\n            return AnyView(Text(\"Error, some ViewModel was not implemented!\"))\n        }\n\n        return AnyView(viewModel.makeViewRenderer())\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/PresenterViews/MoleculePresenterWrapper.swift",
    "content": "//\n//  MoleculePresenterWrapper.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport RecipesApp\n\n/// Wraps a Molecule Presenter that has been converted into a regular Presenter.\n///\n/// In order to convert a Molecule Presenter to a regular Presenter, we need to create a MoleculeScope,\n/// and that scope needs to be cancelled when we are done,\n/// so we create this class which will automatically cancel the scope upon deinit.\nclass MoleculePresenterWrapper: Presenter {\n    var model: Kotlinx_coroutines_coreStateFlow { wrapped.model }\n\n    private let wrapped: Presenter\n    private let scope: MoleculeScope\n\n    init(moleculeScopeFactory: MoleculeScopeFactory, moleculePresenter: MoleculePresenter, input: Any) {\n        let scope = moleculeScopeFactory.createMoleculeScope()\n        self.scope = scope\n        self.wrapped = scope.launchMoleculePresenter(presenter: moleculePresenter, input: input)\n    }\n\n    deinit {\n        scope.cancel()\n    }\n\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/PresenterViews/PresenterView.swift",
    "content": "//\n//  PresenterView.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\n/// Displays the view model hieararchy from a root `Presenter`.\n///\n/// `PresenterView` can be instantiated by passing in a Kotlin `Presenter` or an `AsyncSequence` of `ViewModels`.\n///\n/// Note that `PresenterView` should only be used at the root of a `Presenter` hierarchy. `Presenters` are hierarchical. The view for a parent\n/// `Presenter` model should also present the model of its children, so `PresenterView` is only be needed for the root parent `Presenter`.\nstruct PresenterView<Model: BaseModel>: View {\n    @StateObject var viewModelObserver: ViewModelObserver\n\n    init(presenter: Presenter, viewModelType: Model.Type, handleViewModelError: @escaping (Error) -> ()) {\n        self.init(viewModels: presenter.viewModels(ofType: viewModelType), handleViewModelError: handleViewModelError)\n    }\n\n    init<ViewModels: AsyncSequence>(viewModels: ViewModels, handleViewModelError: @escaping (Error) -> ()) where ViewModels.Element == Model {\n        self._viewModelObserver = StateObject(wrappedValue: ViewModelObserver(\n            viewModels: viewModels,\n            handleError: handleViewModelError\n        ))\n    }\n\n    var body: some View {\n        if let viewModel = viewModelObserver.viewModel {\n            viewModel.getViewRenderer()\n        }\n    }\n\n    @MainActor\n    class ViewModelObserver: ObservableObject {\n        @Published var viewModel: Model?\n        private var task: Task<Void, Never>? = nil\n\n        init<ViewModels: AsyncSequence>(viewModels: ViewModels, handleError: @escaping (Error) -> ()) where ViewModels.Element == Model {\n            task = Task { @MainActor [weak self] in\n                do {\n                    for try await viewModel in viewModels {\n                        self?.viewModel = viewModel\n                    }\n                } catch {\n                    handleError(error)\n                }\n            }\n        }\n\n        deinit {\n            task?.cancel()\n        }\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/PresenterViews/PresenterViewModel.swift",
    "content": "//\n//  PresenterViewModel.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\n/// A protocol for view models that create their own SwiftUI view representation.\nprotocol PresenterViewModel {\n    associatedtype Renderer : View\n    @ViewBuilder @MainActor func makeViewRenderer() -> Self.Renderer\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/RecipesIosApp.swift",
    "content": "//\n//  RecipesIosApp.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/10/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\nclass AppDelegate: NSObject, UIApplicationDelegate, RootScopeProvider {\n    let demoApplication: DemoApplication = DemoApplication()\n    \n    var rootScope: Scope {\n        get { demoApplication.rootScope }\n    }\n    \n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {\n        demoApplication.create(appComponent: IosAppComponentKt.createIosAppComponent(application: application, rootScopeProvider: demoApplication))\n        return true\n    }\n}\n\n@main\nstruct recipesIosApp: App {\n    \n    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n    \n    var body: some Scene {\n        WindowGroup {\n            ContentView(appDelegate: appDelegate)\n        }\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/SwiftUI/SwiftUiChildPresenterView.swift",
    "content": "//\n//  SwiftUiChildPresenterView.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/23/25.\n//\n\nimport RecipesApp\nimport SwiftUI\n\nextension SwiftUiChildPresenter.Model: PresenterViewModel {\n    func makeViewRenderer() -> some View {\n        SwiftUiChildPresenterView(model: self)\n    }\n}\n\nstruct SwiftUiChildPresenterView: View {\n    var model: SwiftUiChildPresenter.Model\n\n    var body: some View {\n        Text(\"Index: \\(model.index)\")\n            .font(.system(size: 36))\n        Text(\"Counter: \\(model.counter)\")\n            .font(.system(size: 36))\n        Button(action: { model.onEvent(SwiftUiChildPresenterEventAddPeer()) }) {\n            Text(\"Add peer\")\n        }\n        .buttonStyle(.borderedProminent)\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/SwiftUI/SwiftUiHomePresenterBuilder.swift",
    "content": "//\n//  SwiftUiHomePresenterBuilder.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport RecipesApp\n\nstruct SwiftUiHomePresenterBuilder {\n    private let appComponent: AppComponent\n    \n    init(appDelegate: AppDelegate) {\n        self.appComponent = appDelegate.demoApplication.appComponent\n    }\n\n    func makeHomePresenter() -> Presenter {\n        MoleculePresenterWrapper(\n            moleculeScopeFactory: appComponent.moleculeScopeFactory,\n            moleculePresenter: appComponent.swiftUiHomePresenter,\n            input: Void()\n        )\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/SwiftUI/SwiftUiHomePresenterView.swift",
    "content": "//\n//  SwiftUiHomePresenterView.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/24/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\nextension SwiftUiHomePresenter.Model: PresenterViewModel {\n\n    func makeViewRenderer() -> some View {\n        SwiftUiHomePresenterView(model: self)\n    }\n}\n\n// Creates a binding for the workflow presenter model backstack so we can provide it to\n// NavigationStack. The backstack is indexed here as the type of the Binding needs to be hashable.\n// SwiftUiHomePresenter.Model accepts a modified list of indices\nextension SwiftUiHomePresenter.Model {\n    func pathBinding() -> Binding<[Int]> {\n        .init {\n            // drop the first value of the backstack from the path because that should be the root view\n            Array(self.modelBackstack.indices.dropFirst())\n        } set: { modifiedIndices in\n\n            // the resulting backstack indices the presenter should compute on is the first index (0) that was\n            // dropped as well as the remaining indices post modification\n            let indicesBackstack = [0] + modifiedIndices.map { $0.toKotlinInt() }\n\n            self.onEvent(\n                SwiftUiHomePresenterEventBackstackModificationEvent (\n                    indicesBackstack: indicesBackstack\n                )\n            )\n        }\n    }\n}\n\nstruct SwiftUiHomePresenterView: View {\n    var model: SwiftUiHomePresenter.Model\n\n    var body: some View {\n        NavigationStackView(model: self.model)\n    }\n}\n\nprivate struct NavigationStackView: View {\n    var backstack: [BaseModel]\n    var model: SwiftUiHomePresenter.Model\n\n    init(model: SwiftUiHomePresenter.Model) {\n        self.backstack = model.modelBackstack\n        self.model = model\n    }\n\n    var body: some View {\n        NavigationStack(path: model.pathBinding()) {\n            backstack[0].getViewRenderer()\n                .navigationDestination(for: Int.self) { index in\n                    backstack[index].getViewRenderer()\n                }\n        }\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp/SwiftUI/SwiftUiRootPresenterView.swift",
    "content": "//\n//  SwiftUiRootPresenterView.swift\n//  recipesIosApp\n//\n//  Created by Wang, Jessalyn on 11/23/25.\n//\n\nimport SwiftUI\nimport RecipesApp\n\n/// Creates the view model hierarchy for the root presenter of this recipe, `SwiftUiHomePresenter`.\nstruct SwiftUiRootPresenterView: View {\n    var homePresenter: Presenter\n\n    var body: some View {\n        PresenterView(\n            presenter: homePresenter,\n            viewModelType: BaseModel.self,\n            handleViewModelError: { error in\n                fatalError(\"View model error occured: \\(error)\")\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "recipes/recipesIosApp/recipesIosApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 77;\n\tobjects = {\n\n/* Begin PBXFileReference section */\n\t\t3CDBC1702EC2A90F0038D55D /* recipesIosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = recipesIosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\t3CDBC1A22EC2D0300038D55D /* Exceptions for \"recipesIosApp\" folder in \"recipesIosApp\" target */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tInfo.plist,\n\t\t\t);\n\t\t\ttarget = 3CDBC16F2EC2A90F0038D55D /* recipesIosApp */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t3CDBC1722EC2A90F0038D55D /* recipesIosApp */ = {\n\t\t\tisa = PBXFileSystemSynchronizedRootGroup;\n\t\t\texceptions = (\n\t\t\t\t3CDBC1A22EC2D0300038D55D /* Exceptions for \"recipesIosApp\" folder in \"recipesIosApp\" target */,\n\t\t\t);\n\t\t\tpath = recipesIosApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t3CDBC16D2EC2A90F0038D55D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t3CDBC1672EC2A90F0038D55D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3CDBC1722EC2A90F0038D55D /* recipesIosApp */,\n\t\t\t\t3CDBC1712EC2A90F0038D55D /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CDBC1712EC2A90F0038D55D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3CDBC1702EC2A90F0038D55D /* recipesIosApp.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t3CDBC16F2EC2A90F0038D55D /* recipesIosApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 3CDBC1912EC2A9110038D55D /* Build configuration list for PBXNativeTarget \"recipesIosApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t3CDBC19A2EC2A94B0038D55D /* Compile Kotlin Framework */,\n\t\t\t\t3CDBC16C2EC2A90F0038D55D /* Sources */,\n\t\t\t\t3CDBC16D2EC2A90F0038D55D /* Frameworks */,\n\t\t\t\t3CDBC16E2EC2A90F0038D55D /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t3CDBC1722EC2A90F0038D55D /* recipesIosApp */,\n\t\t\t);\n\t\t\tname = recipesIosApp;\n\t\t\tpackageProductDependencies = (\n\t\t\t);\n\t\t\tproductName = recipesIosApp;\n\t\t\tproductReference = 3CDBC1702EC2A90F0038D55D /* recipesIosApp.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t3CDBC1682EC2A90F0038D55D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1630;\n\t\t\t\tLastUpgradeCheck = 1630;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t3CDBC16F2EC2A90F0038D55D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.3;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 3CDBC16B2EC2A90F0038D55D /* Build configuration list for PBXProject \"recipesIosApp\" */;\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 3CDBC1672EC2A90F0038D55D;\n\t\t\tminimizedProjectReferenceProxies = 1;\n\t\t\tpreferredProjectObjectVersion = 77;\n\t\t\tproductRefGroup = 3CDBC1712EC2A90F0038D55D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t3CDBC16F2EC2A90F0038D55D /* recipesIosApp */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t3CDBC16E2EC2A90F0038D55D /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t3CDBC19A2EC2A94B0038D55D /* Compile Kotlin Framework */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Compile Kotlin Framework\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"if [ \\\"YES\\\" = \\\"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\\\" ]; then\\n  echo \\\"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\\\\\"YES\\\\\\\"\\\"\\n  exit 0\\nfi\\ncd \\\"$SRCROOT/..\\\"\\n../gradlew :recipes:app:embedAndSignAppleFrameworkForXcode\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t3CDBC16C2EC2A90F0038D55D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t3CDBC18F2EC2A9110038D55D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 18.4;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t3CDBC1902EC2A9110038D55D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 18.4;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t3CDBC1922EC2A9110038D55D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = recipesIosApp/Info.plist;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = software.amazon.app.platform.recipes.recipesIosApp;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t3CDBC1932EC2A9110038D55D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = recipesIosApp/Info.plist;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = software.amazon.app.platform.recipes.recipesIosApp;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t3CDBC16B2EC2A90F0038D55D /* Build configuration list for PBXProject \"recipesIosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t3CDBC18F2EC2A9110038D55D /* Debug */,\n\t\t\t\t3CDBC1902EC2A9110038D55D /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t3CDBC1912EC2A9110038D55D /* Build configuration list for PBXNativeTarget \"recipesIosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t3CDBC1922EC2A9110038D55D /* Debug */,\n\t\t\t\t3CDBC1932EC2A9110038D55D /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 3CDBC1682EC2A90F0038D55D /* Project object */;\n}\n"
  },
  {
    "path": "renderer/public/api/android/public.api",
    "content": "public class software/amazon/app/platform/renderer/BaseRendererFactory : software/amazon/app/platform/renderer/RendererFactory {\n\tpublic fun <init> (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n\tpublic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/Renderer {\n\tpublic abstract fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererComponent {\n\tpublic abstract fun getModelToRendererMapping ()Ljava/util/Map;\n\tpublic abstract fun getRenderers ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererComponent$Parent {\n\tpublic abstract fun rendererComponent (Lsoftware/amazon/app/platform/renderer/RendererFactory;)Lsoftware/amazon/app/platform/renderer/RendererComponent;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererFactory {\n\tpublic abstract fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic abstract fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/RendererFactory$DefaultImpls {\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/RendererFactoryKt {\n\tpublic static final fun createRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static final fun getRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph {\n\tpublic abstract fun getModelToRendererMapping ()Ljava/util/Map;\n\tpublic abstract fun getRenderers ()Ljava/util/Map;\n}\n\npublic abstract class software/amazon/app/platform/renderer/RendererGraph$BindsMirror {\n\tpublic abstract fun modelToRendererMapping9894420054205200196 ()Ljava/util/Map;\n\tpublic abstract fun renderers4205200196 ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph$Factory {\n\tpublic abstract fun createRendererGraph (Lsoftware/amazon/app/platform/renderer/RendererFactory;)Lsoftware/amazon/app/platform/renderer/RendererGraph;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph$Factory$MetroContributionToAppScope : software/amazon/app/platform/renderer/RendererGraph$Factory {\n}\n\npublic abstract class software/amazon/app/platform/renderer/RendererScope {\n}\n\n"
  },
  {
    "path": "renderer/public/api/desktop/public.api",
    "content": "public class software/amazon/app/platform/renderer/BaseRendererFactory : software/amazon/app/platform/renderer/RendererFactory {\n\tpublic fun <init> (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n\tpublic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/Renderer {\n\tpublic abstract fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererComponent {\n\tpublic abstract fun getModelToRendererMapping ()Ljava/util/Map;\n\tpublic abstract fun getRenderers ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererComponent$Parent {\n\tpublic abstract fun rendererComponent (Lsoftware/amazon/app/platform/renderer/RendererFactory;)Lsoftware/amazon/app/platform/renderer/RendererComponent;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererFactory {\n\tpublic abstract fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic abstract fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/RendererFactory$DefaultImpls {\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/RendererFactoryKt {\n\tpublic static final fun createRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static final fun getRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph {\n\tpublic abstract fun getModelToRendererMapping ()Ljava/util/Map;\n\tpublic abstract fun getRenderers ()Ljava/util/Map;\n}\n\npublic abstract class software/amazon/app/platform/renderer/RendererGraph$BindsMirror {\n\tpublic abstract fun modelToRendererMapping9894420054205200196 ()Ljava/util/Map;\n\tpublic abstract fun renderers4205200196 ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph$Factory {\n\tpublic abstract fun createRendererGraph (Lsoftware/amazon/app/platform/renderer/RendererFactory;)Lsoftware/amazon/app/platform/renderer/RendererGraph;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/RendererGraph$Factory$MetroContributionToAppScope : software/amazon/app/platform/renderer/RendererGraph$Factory {\n}\n\npublic abstract class software/amazon/app/platform/renderer/RendererScope {\n}\n\n"
  },
  {
    "path": "renderer/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enableMetro true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonMainApi project(':scope:public')\n\n    commonMainImplementation libs.kotlin.atomicfu\n\n    commonTestImplementation project(':internal:testing')\n    commonTestImplementation project(':scope:testing')\n}\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/BaseRendererFactory.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport kotlin.reflect.KClass\nimport kotlinx.atomicfu.locks.SynchronizedObject\nimport kotlinx.atomicfu.locks.synchronized\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * Default implementation for [RendererFactory]. Implementations usually override [createRenderer]\n * and [getRenderer] to configure the [Renderer]s provided by the dependency graph further.\n */\npublic open class BaseRendererFactory(rootScopeProvider: RootScopeProvider) : RendererFactory {\n\n  private val rendererComponent =\n    rootScopeProvider.rootScope\n      .kotlinInjectComponentOrNull<RendererComponent.Parent>()\n      ?.rendererComponent(this)\n\n  private val rendererGraph =\n    rootScopeProvider.rootScope\n      .metroDependencyGraphOrNull<RendererGraph.Factory>()\n      ?.createRendererGraph(this)\n\n  private val renderers: Map<KClass<out BaseModel>, () -> Renderer<*>> =\n    rendererComponent?.renderers.orEmpty() +\n      rendererGraph?.renderers.orEmpty().mapValues { { it.value() } }\n\n  private val cacheKeys: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n    rendererComponent?.modelToRendererMapping.orEmpty() +\n      rendererGraph?.modelToRendererMapping.orEmpty()\n\n  private val rendererCache = mutableMapOf<CacheKey, Renderer<*>>()\n  private val lock = SynchronizedObject()\n\n  override fun <T : BaseModel> createRenderer(modelType: KClass<out T>): Renderer<T> {\n    @Suppress(\"UNCHECKED_CAST\")\n    return checkNotNull(renderers[modelType]?.invoke() as? Renderer<T>) {\n      errorMessageForMissingRenderer(modelType)\n    }\n  }\n\n  @Suppress(\"UNCHECKED_CAST\")\n  override fun <T : BaseModel> getRenderer(modelType: KClass<out T>, rendererId: Int): Renderer<T> {\n    val rendererClass =\n      checkNotNull(cacheKeys[modelType]) { errorMessageForMissingRenderer(modelType) }\n\n    return synchronized(lock) {\n      rendererCache.getOrPut(CacheKey(rendererClass, rendererId)) { createRenderer(modelType) }\n    }\n      as Renderer<T>\n  }\n\n  @Suppress(\"NOTHING_TO_INLINE\")\n  private inline fun <T : BaseModel> errorMessageForMissingRenderer(\n    modelType: KClass<out T>\n  ): String {\n    return \"No renderer was provided for $modelType. Did you add @ContributesRenderer?\"\n  }\n\n  private data class CacheKey(val clazz: KClass<out Renderer<*>>, val rendererId: Int)\n\n  private companion object {\n    inline fun <reified T : Any> Scope.metroDependencyGraphOrNull(): T? {\n      return try {\n        metroDependencyGraph<T>()\n      } catch (_: NoSuchElementException) {\n        null\n      }\n    }\n\n    inline fun <reified T : Any> Scope.kotlinInjectComponentOrNull(): T? {\n      return try {\n        kotlinInjectComponent<T>()\n      } catch (_: NoSuchElementException) {\n        null\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/Renderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.Presenter\n\n/**\n * A renderer handles the conversion of a given [BaseModel] into UI. Models are created and updated\n * in the [Presenter] layer.\n *\n * This interface will rarely be used directly and a more specific renderer implementation for a\n * concrete platform should be favored unless there is a specific need.\n *\n * [Renderer]s are composable and can be injected into other renderers to create more complex UI\n * hierarchies. A parent model should contain sub models that map to the required type of the child\n * renderer. In this way Presenter compositions and Renderer compositions often mirror each other.\n *\n * ```\n * data class ParentModel(\n *      val childModel: ChildModel\n * ): BaseModel\n * ```\n *\n * A parent renderer is responsible for initializing and calling [render] on their child renderers.\n *\n * ```\n * @Inject\n * class ParentRenderer(\n *      private val childRenderer: ChildRenderer\n * ): Renderer<MyModel> {\n *\n *      override fun render(model: ParentModel) {\n *          childRenderer.render(model.childModel)\n *      }\n * }\n * ```\n */\npublic interface Renderer<in ModelT : BaseModel> {\n  /** Ask the [Renderer] to render the given model. */\n  public fun render(model: ModelT)\n}\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/RendererComponent.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesSubcomponent\nimport software.amazon.lastmile.kotlin.inject.anvil.ForScope\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/** Component that provides all [Renderer] instance from the kotlin-inject dependency graph. */\n@ContributesSubcomponent(RendererScope::class)\n@SingleIn(RendererScope::class)\npublic interface RendererComponent {\n  /** All [Renderer]s provided in the dependency graph. */\n  public val renderers: Map<KClass<out BaseModel>, () -> Renderer<*>>\n\n  /**\n   * [RendererFactory]s cache renderers based on the model type. This works well, when there's a one\n   * to one relationship between a model type and a renderer. However, for sealed hierarchies there\n   * are multiple model types pointing to the same renderer.\n   *\n   * This map, given any model type as key, returns the type that should be used as key for caching.\n   * For non-sealed hierarchies (aka a single model type per renderer) there is only single mapping\n   * between the model and renderer class. For sealed hierarchies there are multiple entries with\n   * all keys pointing to the same renderer class.\n   */\n  @ForScope(RendererScope::class)\n  public val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>>\n\n  /** The parent interface to create a [RendererComponent]. */\n  @ContributesSubcomponent.Factory(AppScope::class)\n  public interface Parent {\n    /** Creates a new [RendererComponent]. */\n    public fun rendererComponent(factory: RendererFactory): RendererComponent\n  }\n}\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/RendererFactory.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\n\n/** Responsible for initializing a [Renderer] for a given model. */\npublic interface RendererFactory {\n  /**\n   * Creates a new [Renderer] for the given [BaseModel] type. This function always creates a new\n   * [Renderer] and the result is never cached.\n   */\n  public fun <T : BaseModel> createRenderer(modelType: KClass<out T>): Renderer<T>\n\n  /**\n   * Obtains the [Renderer] for the given [BaseModel] type. The result is either a new [Renderer] if\n   * it wasn't requested before or a cached result from the previous request. Which [Renderer] is\n   * returned is based on [modelType]. [rendererId] allows you to change the cache key and cache\n   * multiple renderers for the same [BaseModel] type.\n   */\n  public fun <T : BaseModel> getRenderer(modelType: KClass<out T>, rendererId: Int = 0): Renderer<T>\n}\n\n/**\n * Creates a new [Renderer] for the given [BaseModel]. This function always creates a new [Renderer]\n * and the result is never cached.\n */\npublic fun <T : BaseModel> RendererFactory.createRenderer(model: T): Renderer<T> =\n  createRenderer(model::class)\n\n/**\n * Obtains the [Renderer] for the given [BaseModel]. The result is either a new [Renderer] if it\n * wasn't requested before or a cached result from the previous request. Which [Renderer] is\n * returned is based on the type of [model]. [rendererId] allows you to change the cache key and\n * cache multiple renderers for the same [BaseModel] type.\n */\npublic fun <T : BaseModel> RendererFactory.getRenderer(model: T, rendererId: Int = 0): Renderer<T> =\n  getRenderer(model::class, rendererId)\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/RendererGraph.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.GraphExtension\nimport dev.zacsweers.metro.Multibinds\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\n\n/** Graph that provides all [Renderer] instance from the Metro dependency graph. */\n@GraphExtension(RendererScope::class)\n@SingleIn(RendererScope::class)\npublic interface RendererGraph {\n  /** All [Renderer]s provided in the dependency graph. */\n  @Multibinds(allowEmpty = true)\n  public val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>>\n\n  /**\n   * [RendererFactory]s cache renderers based on the model type. This works well, when there's a one\n   * to one relationship between a model type and a renderer. However, for sealed hierarchies there\n   * are multiple model types pointing to the same renderer.\n   *\n   * This map, given any model type as key, returns the type that should be used as key for caching.\n   * For non-sealed hierarchies (aka a single model type per renderer) there is only single mapping\n   * between the model and renderer class. For sealed hierarchies there are multiple entries with\n   * all keys pointing to the same renderer class.\n   */\n  @ForScope(RendererScope::class)\n  @Multibinds(allowEmpty = true)\n  public val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>>\n\n  /** The parent interface to create a [RendererGraph]. */\n  @ContributesTo(AppScope::class)\n  @GraphExtension.Factory\n  public interface Factory {\n    /** Creates a new [RendererGraph]. */\n    public fun createRendererGraph(@Provides factory: RendererFactory): RendererGraph\n  }\n}\n"
  },
  {
    "path": "renderer/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/RendererScope.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport software.amazon.app.platform.inject.ContributesRenderer\n\n/**\n * A Scope specifically for `Renderer` implementations.\n *\n * Renderers annotated with the [ContributesRenderer] annotation have a kotlin-inject component or\n * Metro graph generated with all necessary bindings.\n *\n * This scope should not be contributed to directly, instead prefer [ContributesRenderer]. See\n * [ContributesRenderer] for more details.\n */\npublic abstract class RendererScope private constructor()\n"
  },
  {
    "path": "renderer/public/src/commonTest/kotlin/software/amazon/app/platform/renderer/BaseRendererFactoryTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotSameInstanceAs\nimport assertk.assertions.isSameInstanceAs\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.addKotlinInjectComponent\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\nclass BaseRendererFactoryTest {\n\n  @Test\n  fun `creating a renderer without mapping throws an error`() {\n    val exception =\n      assertFailsWith<Exception> {\n        rendererFactory(\n            kotlinInjectRenderers = emptyMap(),\n            kotlinInjectModelToRendererMapping = emptyMap(),\n            metroRenderers = emptyMap(),\n            metroModelToRendererMapping = emptyMap(),\n          )\n          .getRenderer(TestModel(1))\n      }\n\n    // Transform the message, because it's slightly different on iOS than on\n    // Android and Desktop.\n    val message =\n      exception.message?.replace('\\$', '.')?.replace(\" (Kotlin reflection is not available)\", \"\")\n\n    val errorMessage =\n      \"No renderer was provided for class \" +\n        \"software.amazon.app.platform.renderer.BaseRendererFactoryTest.TestModel. \" +\n        \"Did you add @ContributesRenderer?\"\n\n    assertThat(message).isEqualTo(errorMessage)\n  }\n\n  @Test\n  fun `the createRenderer function always creates new renderers`() {\n    val factory = rendererFactory()\n    val model = TestModel(1)\n\n    assertThat(factory.createRenderer(model)).isNotSameInstanceAs(factory.createRenderer(model))\n  }\n\n  @Test\n  fun `the createRenderer function always creates new renderers after a renderer was cached`() {\n    val factory = rendererFactory()\n    val model = TestModel(1)\n\n    assertThat(factory.getRenderer(model)).isNotSameInstanceAs(factory.createRenderer(model))\n  }\n\n  @Test\n  fun `the getRenderer function caches renderers based on type`() {\n    val factory = rendererFactory()\n    val model1 = TestModel(1)\n    val model2 = TestModel(2)\n\n    assertThat(factory.getRenderer(model1)).isSameInstanceAs(factory.getRenderer(model2))\n  }\n\n  @Test\n  fun `the getRenderer function caches renderers based on type and id`() {\n    val factory = rendererFactory()\n    val model1 = TestModel(1)\n    val model2 = TestModel(2)\n\n    assertThat(factory.getRenderer(model1, rendererId = 1))\n      .isSameInstanceAs(factory.getRenderer(model2, rendererId = 1))\n  }\n\n  @Test\n  fun `the getRenderer function returns a new renderer on ID mismatch`() {\n    val factory = rendererFactory()\n    val model = TestModel(1)\n\n    assertThat(factory.getRenderer(model, rendererId = 1))\n      .isNotSameInstanceAs(factory.getRenderer(model, rendererId = 2))\n  }\n\n  @Test\n  fun `the getRenderer function caches renderers based on the sealed type`() {\n    val factory = rendererFactory()\n    val model1 = SealedTestModel.Model1(1)\n    val model2 = SealedTestModel.Model2(1)\n\n    assertThat(factory.getRenderer(model1)).isSameInstanceAs(factory.getRenderer(model2))\n  }\n\n  @Test\n  fun `renderers can be provided by kotlin-inject alone`() {\n    val factory = rendererFactory(useKotlinInject = true, useMetro = false)\n    val model = TestModel(1)\n\n    assertThat(factory.getRenderer(model)).isSameInstanceAs(factory.getRenderer(model))\n\n    assertFailsWith<Exception> { factory.getRenderer(SealedTestModel.Model1(1)) }\n  }\n\n  @Test\n  fun `renderers can be provided by Metro alone`() {\n    val factory = rendererFactory(useKotlinInject = false, useMetro = true)\n    val model = SealedTestModel.Model1(1)\n\n    assertThat(factory.getRenderer(model)).isSameInstanceAs(factory.getRenderer(model))\n\n    assertFailsWith<Exception> { factory.getRenderer(TestModel(1)) }\n  }\n\n  @Test\n  fun `renderers can be provided by kotlin-inject and Metro together`() {\n    val factory = rendererFactory(useKotlinInject = true, useMetro = true)\n    val model1 = TestModel(1)\n    val model2 = SealedTestModel.Model1(1)\n\n    assertThat(factory.getRenderer(model1)).isSameInstanceAs(factory.getRenderer(model1))\n    assertThat(factory.getRenderer(model2)).isSameInstanceAs(factory.getRenderer(model2))\n  }\n\n  private fun rendererFactory(\n    kotlinInjectRenderers: Map<KClass<out BaseModel>, () -> Renderer<*>> =\n      mapOf(TestModel::class to { TestRenderer() }),\n    kotlinInjectModelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n      mapOf(TestModel::class to TestRenderer::class),\n    metroRenderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>> =\n      mapOf(\n        SealedTestModel::class to provider { SealedTestRenderer() },\n        SealedTestModel.Model1::class to provider { SealedTestRenderer() },\n        SealedTestModel.Model2::class to provider { SealedTestRenderer() },\n      ),\n    metroModelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n      mapOf(\n        SealedTestModel::class to SealedTestRenderer::class,\n        SealedTestModel.Model1::class to SealedTestRenderer::class,\n        SealedTestModel.Model2::class to SealedTestRenderer::class,\n      ),\n    useKotlinInject: Boolean = true,\n    useMetro: Boolean = true,\n  ): RendererFactory =\n    BaseRendererFactory(\n      rootScopeProvider =\n        object : RootScopeProvider {\n          override val rootScope: Scope = Scope.buildRootScope {\n            if (useKotlinInject) {\n              addKotlinInjectComponent(\n                object : RendererComponent.Parent {\n                  override fun rendererComponent(factory: RendererFactory): RendererComponent =\n                    object : RendererComponent {\n                      override val renderers: Map<KClass<out BaseModel>, () -> Renderer<*>> =\n                        kotlinInjectRenderers\n\n                      override val modelToRendererMapping:\n                        Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n                        kotlinInjectModelToRendererMapping\n                    }\n                }\n              )\n            }\n\n            if (useMetro) {\n              addMetroDependencyGraph(\n                object : RendererGraph.Factory {\n                  override fun createRendererGraph(factory: RendererFactory): RendererGraph =\n                    object : RendererGraph {\n                      override val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>> =\n                        metroRenderers\n                      override val modelToRendererMapping:\n                        Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n                        metroModelToRendererMapping\n                    }\n                }\n              )\n            }\n          }\n        }\n    )\n\n  private data class TestModel(val value: Int) : BaseModel\n\n  private class TestRenderer : Renderer<TestModel> {\n    override fun render(model: TestModel) = Unit\n  }\n\n  private sealed interface SealedTestModel : BaseModel {\n    data class Model1(val value: Int) : SealedTestModel\n\n    data class Model2(val value: Int) : SealedTestModel\n  }\n\n  private class SealedTestRenderer : Renderer<SealedTestModel> {\n    override fun render(model: SealedTestModel) = Unit\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/api/public.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterAndroidKt {\n\tpublic static final fun forwardBackPressEventsToPresenters (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;Landroidx/activity/OnBackPressedDispatcherOwner;)V\n}\n\npublic class software/amazon/app/platform/renderer/AndroidRendererFactory : software/amazon/app/platform/renderer/BaseRendererFactory {\n\tpublic fun <init> (Lsoftware/amazon/app/platform/scope/RootScopeProvider;Landroid/app/Activity;Landroid/view/ViewGroup;)V\n\tpublic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic final fun createRenderer (Lkotlin/reflect/KClass;Landroid/view/ViewGroup;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic final fun getRenderer (Lkotlin/reflect/KClass;Landroid/view/ViewGroup;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/AndroidRendererFactory;Lkotlin/reflect/KClass;Landroid/view/ViewGroup;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/AndroidRendererFactoryKt {\n\tpublic static final fun createRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;Landroid/view/ViewGroup;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static final fun getChildRendererForParent (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;Landroid/view/ViewGroup;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static final fun getChildRendererForParent (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;Landroid/view/ViewGroup;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static final fun getRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;Landroid/view/ViewGroup;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic static synthetic fun getRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;Landroid/view/ViewGroup;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/BaseAndroidViewRenderer : software/amazon/app/platform/renderer/Renderer {\n\tpublic abstract fun init (Landroid/app/Activity;Landroid/view/ViewGroup;)V\n}\n\npublic abstract class software/amazon/app/platform/renderer/RecyclerViewViewHolderRenderer : software/amazon/app/platform/renderer/BaseAndroidViewRenderer {\n\tpublic fun <init> ()V\n\tprotected final fun getActivity ()Landroid/app/Activity;\n\tprotected abstract fun inflate (Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;)Landroid/view/View;\n\tpublic final fun init (Landroid/app/Activity;Landroid/view/ViewGroup;)V\n\tpublic final fun viewHolder ()Lsoftware/amazon/app/platform/renderer/RecyclerViewViewHolderRenderer$ViewHolder;\n}\n\npublic final class software/amazon/app/platform/renderer/RecyclerViewViewHolderRenderer$ViewHolder : androidx/recyclerview/widget/RecyclerView$ViewHolder {\n\tpublic final fun getRenderer ()Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic abstract class software/amazon/app/platform/renderer/ViewBindingRenderer : software/amazon/app/platform/renderer/ViewRenderer {\n\tpublic fun <init> ()V\n\tprotected final fun getBinding ()Landroidx/viewbinding/ViewBinding;\n\tprotected final fun inflate (Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;Lsoftware/amazon/app/platform/presenter/BaseModel;)Landroid/view/View;\n\tprotected abstract fun inflateViewBinding (Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;Lsoftware/amazon/app/platform/presenter/BaseModel;)Landroidx/viewbinding/ViewBinding;\n}\n\npublic abstract class software/amazon/app/platform/renderer/ViewRenderer : software/amazon/app/platform/renderer/BaseAndroidViewRenderer {\n\tpublic fun <init> ()V\n\tprotected final fun getActivity ()Landroid/app/Activity;\n\tprotected final fun getCoroutineScope ()Lkotlinx/coroutines/CoroutineScope;\n\tprotected abstract fun inflate (Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/LayoutInflater;Lsoftware/amazon/app/platform/presenter/BaseModel;)Landroid/view/View;\n\tpublic final fun init (Landroid/app/Activity;Landroid/view/ViewGroup;)V\n\tpublic fun onDetach ()V\n\tprotected fun releaseViewOnDetach ()Z\n\tpublic final fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n\tprotected fun renderModel (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n\tprotected fun renderModel (Lsoftware/amazon/app/platform/presenter/BaseModel;Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n}\n\npublic abstract class software/amazon/app/platform/renderer/template/AndroidTemplateRenderer : software/amazon/app/platform/renderer/ViewRenderer {\n\tpublic fun <init> (Lsoftware/amazon/app/platform/renderer/RendererFactory;)V\n}\n\nprotected final class software/amazon/app/platform/renderer/template/AndroidTemplateRenderer$Container {\n\tpublic fun <init> (Lsoftware/amazon/app/platform/renderer/template/AndroidTemplateRenderer;Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/ViewGroup;)V\n\tpublic final fun hide ()V\n\tpublic final fun renderModel (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n\tpublic final fun reset ()V\n\tpublic final fun show ()V\n}\n\n"
  },
  {
    "path": "renderer-android-view/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n    enableInstrumentedTests true\n}\n\nandroid {\n    buildFeatures {\n        viewBinding = true\n    }\n}\n\n// This doesn't work automatically in KMP modules. It's only needed for the IDE and indexing the\n// generated sources.\nif (project.properties['android.injected.invoked.from.ide'] == 'true') {\n    kotlin {\n        sourceSets {\n            androidInstrumentedTest {\n                kotlin.srcDir('build/generated/data_binding_base_class_source_out/debugAndroidTest/out')\n            }\n        }\n    }\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonMainApi project(':renderer:public')\n\n    commonMainImplementation project(':presenter-molecule:public')\n\n    // Use a lower version to not force a higher version on consumers.\n    androidMainApi libs.androidx.activity\n    androidMainApi libs.viewbinding.api\n    androidMainApi libs.recyclerView\n    androidMainImplementation libs.androidx.core\n\n    androidInstrumentedTestImplementation project(':kotlin-inject:public')\n    androidInstrumentedTestImplementation libs.androidx.test.espresso\n    // Use the version aligned with AGP in tests.\n    androidInstrumentedTestImplementation libs.viewbinding.agp\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application android:name=\"software.amazon.app.platform.renderer.TestApplication\">\n        <activity\n            android:name=\"software.amazon.app.platform.renderer.TestActivity\"\n            android:exported=\"false\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/ForwardBackPressEventsToPresentersAndroidTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.test.espresso.Espresso\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.renderer.TestActivity\n\nclass ForwardBackPressEventsToPresentersAndroidTest {\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @Test\n  fun back_press_events_are_forwarded_to_presenters() {\n    val dispatcher = InterceptorBackGestureDispatcherPresenter()\n\n    activityRule.scenario.onActivity { activity ->\n      dispatcher.forwardBackPressEventsToPresenters(activity)\n    }\n\n    assertThat(dispatcher.onPredictiveBackCount).isEqualTo(0)\n\n    Espresso.pressBack()\n    assertThat(dispatcher.onPredictiveBackCount).isEqualTo(1)\n\n    Espresso.pressBack()\n    assertThat(dispatcher.onPredictiveBackCount).isEqualTo(2)\n  }\n\n  private class InterceptorBackGestureDispatcherPresenter :\n    BackGestureDispatcherPresenter by BackGestureDispatcherPresenter.createNewInstance() {\n    override val listenersCount: StateFlow<Int> = MutableStateFlow(1)\n\n    var onPredictiveBackCount = 0\n\n    override suspend fun onPredictiveBack(progress: Flow<BackEventPresenter>) {\n      progress.collect {}\n      onPredictiveBackCount++\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/AndroidRendererFactoryTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.core.view.children\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotSameInstanceAs\nimport assertk.assertions.isSameInstanceAs\nimport assertk.assertions.messageContains\nimport kotlin.reflect.KClass\nimport kotlin.test.assertFailsWith\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass AndroidRendererFactoryTest {\n\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @Before\n  fun prepare() {\n    testApplication.rendererComponent = TestRendererComponent()\n  }\n\n  @After\n  fun tearDown() {\n    testApplication.rendererComponent = null\n  }\n\n  @Test\n  fun android_renderer_factory_uses_the_default_parent_view() {\n    activityRule.scenario.onActivity { activity ->\n      val factory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val model = TestModel(1)\n      factory.getRenderer(model).render(model)\n\n      assertThat(activity.testView.text.toString()).isEqualTo(\"Test: 1\")\n    }\n  }\n\n  @Test\n  fun android_renderer_factory_can_override_the_parent_view() {\n    activityRule.scenario.onActivity { activity ->\n      val factory: RendererFactory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val container = FrameLayout(activity)\n      activity.contentView.addView(container)\n\n      val model = TestModel(1)\n      factory.getRenderer(model::class, container).render(model)\n\n      assertThat(activity.testView.text.toString()).isEqualTo(\"Test: 1\")\n    }\n  }\n\n  @Test\n  fun android_renderer_factory_caches_renderers() {\n    activityRule.scenario.onActivity { activity ->\n      val factory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val actualRenderer = factory.getRenderer(TestModel(1))\n      val expectedRenderer = factory.getRenderer(TestModel(2))\n\n      assertThat(actualRenderer).isSameInstanceAs(expectedRenderer)\n    }\n  }\n\n  @Test\n  fun android_renderer_factory_caches_renderers_based_on_parent_view() {\n    activityRule.scenario.onActivity { activity ->\n      val frameLayout1 = FrameLayout(activity)\n      val frameLayout2 = FrameLayout(activity)\n\n      val linearLayout =\n        LinearLayout(activity).apply {\n          addView(frameLayout1)\n          addView(frameLayout2)\n        }\n\n      activity.contentView.addView(linearLayout)\n\n      val factory: BaseRendererFactory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val renderer1 = factory.getRenderer(TestModel::class, frameLayout1)\n      renderer1.render(TestModel(1))\n\n      val renderer2 = factory.getRenderer(TestModel::class, frameLayout2)\n      renderer2.render(TestModel(2))\n\n      assertThat(renderer1).isNotSameInstanceAs(renderer2)\n      assertThat(renderer1).isSameInstanceAs(factory.getRenderer(TestModel::class, frameLayout1))\n      assertThat(renderer2).isSameInstanceAs(factory.getRenderer(TestModel::class, frameLayout2))\n\n      assertThat(frameLayout1.testView.text.toString()).isEqualTo(\"Test: 1\")\n      assertThat(frameLayout2.testView.text.toString()).isEqualTo(\"Test: 2\")\n    }\n  }\n\n  @Test\n  fun android_renderer_factory_uses_a_different_cache_key_for_different_renderer_ids() {\n    activityRule.scenario.onActivity { activity ->\n      val linearLayout = LinearLayout(activity)\n\n      val factory: BaseRendererFactory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val renderer1 = factory.getRenderer(TestModel::class, linearLayout, rendererId = 1)\n      val renderer2 = factory.getRenderer(TestModel::class, linearLayout, rendererId = 1)\n      val renderer3 = factory.getRenderer(TestModel::class, linearLayout, rendererId = 2)\n\n      assertThat(renderer1).isSameInstanceAs(renderer2)\n      assertThat(renderer1).isNotSameInstanceAs(renderer3)\n    }\n  }\n\n  @Test\n  fun android_renderer_factory_allows_to_change_the_parent_view() {\n    activityRule.scenario.onActivity { activity ->\n      val frameLayout = FrameLayout(activity)\n      activity.contentView.addView(frameLayout)\n\n      val factory: BaseRendererFactory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val renderer = factory.getRenderer(TestModel::class, frameLayout)\n      renderer.render(TestModel(2))\n\n      assertThat(activity.testView.text.toString()).isEqualTo(\"Test: 2\")\n\n      assertThat(activity.contentView.childCount).isEqualTo(1)\n      assertThat(activity.contentView.children.single()).isSameInstanceAs(frameLayout)\n    }\n  }\n\n  @Test\n  fun an_unknown_model_type_gives_a_meaningful_error() {\n    testApplication.rendererComponent =\n      object : RendererComponent {\n        override val renderers: Map<KClass<out BaseModel>, () -> Renderer<*>> = emptyMap()\n        override val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n          emptyMap()\n      }\n\n    activityRule.scenario.onActivity { activity ->\n      val factory =\n        AndroidRendererFactory(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = activity.contentView,\n        )\n\n      val exception = assertFailsWith<IllegalStateException> { factory.getRenderer(TestModel(1)) }\n\n      assertThat(exception)\n        .messageContains(\n          \"No renderer was provided for class \" +\n            \"software.amazon.app.platform.renderer.AndroidRendererFactoryTest\" +\n            \"\\$TestModel (Kotlin reflection is not available). \" +\n            \"Did you add @ContributesRenderer?\"\n        )\n    }\n  }\n\n  private val Activity.contentView: ViewGroup\n    get() = findViewById(android.R.id.content)\n\n  private val Activity.testView: TextView\n    get() = contentView.testView\n\n  private val View.testView: TextView\n    get() = findViewWithTag(\"testView\")\n\n  private data class TestModel(val value: Int) : BaseModel\n\n  private class TestViewRenderer : ViewRenderer<TestModel>() {\n\n    private lateinit var textView: TextView\n\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: TestModel,\n    ): View =\n      TextView(activity).also {\n        textView = it\n        textView.tag = \"testView\"\n      }\n\n    override fun renderModel(model: TestModel) {\n      textView.text = \"Test: ${model.value}\"\n    }\n  }\n\n  private class TestRendererComponent : RendererComponent {\n    override val renderers: Map<KClass<out BaseModel>, () -> Renderer<*>> =\n      mapOf(TestModel::class to { TestViewRenderer() })\n    override val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n      mapOf(TestModel::class to TestViewRenderer::class)\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/RecyclerViewViewHolderRendererTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport androidx.core.view.children\nimport androidx.core.view.descendants\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isGreaterThan\nimport assertk.assertions.isLessThan\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isSameInstanceAs\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass RecyclerViewViewHolderRendererTest {\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @Test\n  fun renderModel_is_invoked_for_new_model() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      assertThat(renderer.inflateCalled).isEqualTo(0)\n      assertThat(renderer.renderCalled).isEqualTo(0)\n\n      renderer.viewHolder().renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      renderer.viewHolder().renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun the_view_holder_is_cached() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n      assertThat(renderer.viewHolder()).isSameInstanceAs(renderer.viewHolder())\n    }\n  }\n\n  @Test\n  fun inflate_is_not_invoked_after_detach() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      renderer.viewHolder().renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      activity.contentView.removeAllViews()\n\n      renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun view_holders_and_nested_renderers_are_reused_while_scrolling() {\n    // This test creates a RecyclerView with 200 rows. Each row has a\n    // RecyclerViewViewHolderRenderer and each one of them has a nested ViewRenderer.\n    //\n    // This test verifies that renderers are reused / recycled in the RecyclerView. It also\n    // ensures that the nested ViewRenderer is optimized for the recycling behavior and\n    // onDetach() does not release the view.\n\n    activityRule.scenario.onActivity { activity -> activity.addRecyclerView() }\n\n    activityRule.scenario.onActivity { activity ->\n      assertThat(activity.recyclerView.childTextView(\"Test: 2\")).isNotNull()\n\n      // There must be an equal number of created and bound ViewHolders.\n      val adapter = activity.recyclerView.adapter as TestAdapter\n      assertThat(adapter.bindCalled).isEqualTo(adapter.createCalled)\n\n      // The renderers were only inflated once (and otherwise recycled).\n      assertThat(activity.recyclerView.viewHolderRenderer(0).inflateCalled).isEqualTo(1)\n      assertThat(activity.recyclerView.viewHolderRenderer(0).nestedRenderer.inflateCalled)\n        .isEqualTo(1)\n    }\n\n    smoothScrollTo(200)\n\n    activityRule.scenario.onActivity { activity ->\n      assertThat(activity.recyclerView.childTextView(\"Test: 200\")).isNotNull()\n\n      val adapter = activity.recyclerView.adapter as TestAdapter\n\n      // Conservative numbers that account for slow scrolling, stuttering and lag.\n      assertThat(adapter.createCalled).isLessThan(140)\n      assertThat(adapter.bindCalled).isGreaterThan(150)\n\n      // The renderers were only inflated once (and otherwise recycled).\n      for (i in 190..199) {\n        assertThat(activity.recyclerView.viewHolderRenderer(i).inflateCalled).isEqualTo(1)\n        assertThat(activity.recyclerView.viewHolderRenderer(i).nestedRenderer.inflateCalled)\n          .isEqualTo(1)\n      }\n    }\n\n    smoothScrollTo(0)\n\n    activityRule.scenario.onActivity { activity ->\n      assertThat(activity.recyclerView.childTextView(\"Test: 1\")).isNotNull()\n\n      val adapter = activity.recyclerView.adapter as TestAdapter\n\n      // Conservative numbers that account for slow scrolling, stuttering and lag.\n      assertThat(adapter.createCalled).isLessThan(180)\n      assertThat(adapter.bindCalled).isGreaterThan(300)\n\n      // The renderers were only inflated once (and otherwise recycled).\n      for (i in 0..10) {\n        assertThat(activity.recyclerView.viewHolderRenderer(i).inflateCalled).isEqualTo(1)\n        assertThat(activity.recyclerView.viewHolderRenderer(i).nestedRenderer.inflateCalled)\n          .isEqualTo(1)\n      }\n    }\n  }\n\n  private fun renderer(activity: Activity): TestRecyclerViewViewHolderRenderer {\n    return TestRecyclerViewViewHolderRenderer().also { it.init(activity, activity.contentView) }\n  }\n\n  private val Activity.contentView: ViewGroup\n    get() = findViewById(android.R.id.content)\n\n  private val Activity.recyclerView: RecyclerView\n    get() = contentView.children.filterIsInstance<RecyclerView>().single()\n\n  private fun Activity.addRecyclerView() {\n    val recyclerView = RecyclerView(this)\n    val models = List(200) { TestModel(it + 1) }\n\n    recyclerView.layoutManager = LinearLayoutManager(this)\n    recyclerView.adapter = TestAdapter(models)\n\n    contentView.addView(recyclerView)\n  }\n\n  private fun RecyclerView.childTextView(text: String): TextView? {\n    return descendants.filterIsInstance<TextView>().singleOrNull { it.text == text }\n  }\n\n  private fun smoothScrollTo(position: Int) {\n    activityRule.scenario.onActivity { activity ->\n      activity.recyclerView.layoutManager!!.startSmoothScroll(\n        object : LinearSmoothScroller(activity) {\n          init {\n            targetPosition = position\n          }\n\n          override fun getVerticalSnapPreference(): Int = SNAP_TO_START\n        }\n      )\n    }\n\n    // Let the scroll from above finish.\n    Thread.sleep(2_000L)\n  }\n\n  @Suppress(\"UNCHECKED_CAST\")\n  private fun RecyclerView.viewHolderRenderer(\n    adapterPosition: Int\n  ): TestRecyclerViewViewHolderRenderer {\n    val viewHolder =\n      findViewHolderForAdapterPosition(adapterPosition)\n        as RecyclerViewViewHolderRenderer.ViewHolder<TestModel>\n\n    return viewHolder.renderer as TestRecyclerViewViewHolderRenderer\n  }\n\n  private data class TestModel(val value: Int) : BaseModel\n\n  private class TestRecyclerViewViewHolderRenderer : RecyclerViewViewHolderRenderer<TestModel>() {\n\n    lateinit var nestedRenderer: NestedViewRenderer\n      private set\n\n    var inflateCalled = 0\n      private set\n\n    var renderCalled = 0\n      private set\n\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n    ): View {\n      inflateCalled++\n\n      val frameLayout = FrameLayout(activity)\n\n      nestedRenderer = NestedViewRenderer()\n      nestedRenderer.init(activity, frameLayout)\n      return frameLayout\n    }\n\n    override fun render(model: TestModel) {\n      renderCalled++\n      nestedRenderer.render(model)\n    }\n  }\n\n  private class NestedViewRenderer : ViewRenderer<TestModel>() {\n    private lateinit var textView: TextView\n\n    var inflateCalled = 0\n      private set\n\n    var renderCalled = 0\n      private set\n\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: TestModel,\n    ): View =\n      TextView(activity).also {\n        textView = it\n        inflateCalled++\n      }\n\n    override fun renderModel(model: TestModel) {\n      textView.text = \"Test: ${model.value}\"\n      renderCalled++\n    }\n  }\n\n  private class TestAdapter(private val models: List<TestModel>) :\n    RecyclerView.Adapter<RecyclerViewViewHolderRenderer.ViewHolder<TestModel>>() {\n\n    var createCalled = 0\n      private set\n\n    var bindCalled = 0\n      private set\n\n    override fun getItemCount(): Int = models.size\n\n    override fun onCreateViewHolder(\n      parent: ViewGroup,\n      viewType: Int,\n    ): RecyclerViewViewHolderRenderer.ViewHolder<TestModel> {\n      createCalled++\n\n      val renderer = TestRecyclerViewViewHolderRenderer()\n      renderer.init(parent.context as Activity, parent)\n      return renderer.viewHolder()\n    }\n\n    override fun onBindViewHolder(\n      holder: RecyclerViewViewHolderRenderer.ViewHolder<TestModel>,\n      position: Int,\n    ) {\n      bindCalled++\n\n      holder.renderer.render(models[position])\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/TestActivity.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.activity.ComponentActivity\n\nclass TestActivity : ComponentActivity()\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/TestApplication.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Application\nimport androidx.test.platform.app.InstrumentationRegistry\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.Job\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.addKotlinInjectComponent\n\nclass TestApplication : Application(), RootScopeProvider {\n\n  var rendererComponent: RendererComponent? = null\n\n  override val rootScope: Scope = Scope.buildRootScope {\n    addKotlinInjectComponent(Component())\n    addCoroutineScopeScoped(CoroutineScopeScoped(Job() + CoroutineName(\"test\")))\n  }\n\n  private inner class Component : RendererComponent.Parent {\n    override fun rendererComponent(factory: RendererFactory): RendererComponent =\n      requireNotNull(rendererComponent)\n  }\n}\n\nval testApplication: TestApplication\n  get() =\n    InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestApplication\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/ViewBindingRendererTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.renderer.android.view.test.databinding.ViewbindingLayoutBinding\n\nclass ViewBindingRendererTest {\n\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @Test\n  fun renderModel_is_invoked_for_new_model() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      assertThat(renderer.inflateCalled).isEqualTo(0)\n      assertThat(renderer.renderCalled).isEqualTo(0)\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  private fun renderer(activity: Activity): TestViewBindingRenderer {\n    return TestViewBindingRenderer().also { it.init(activity, activity.contentView) }\n  }\n\n  private val Activity.contentView: ViewGroup\n    get() = findViewById(android.R.id.content)\n\n  private data class TestModel(val value: Int) : BaseModel\n\n  private class TestViewBindingRenderer :\n    ViewBindingRenderer<TestModel, ViewbindingLayoutBinding>() {\n\n    var inflateCalled = 0\n      private set\n\n    var renderCalled = 0\n      private set\n\n    override fun inflateViewBinding(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: TestModel,\n    ): ViewbindingLayoutBinding {\n      inflateCalled++\n      return ViewbindingLayoutBinding.inflate(layoutInflater, parent, false)\n    }\n\n    override fun renderModel(model: TestModel) {\n      binding.viewbindingTextView.text = \"Test: ${model.value}\"\n      renderCalled++\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/ViewRendererTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport androidx.core.view.children\nimport androidx.lifecycle.Lifecycle\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport androidx.test.platform.app.InstrumentationRegistry\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isNotSameInstanceAs\nimport assertk.assertions.isNull\nimport assertk.assertions.isTrue\nimport assertk.assertions.messageContains\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.isActive\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\n\nclass ViewRendererTest {\n\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @Test\n  fun renderModel_is_invoked_for_new_model() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      assertThat(renderer.inflateCalled).isEqualTo(0)\n      assertThat(renderer.renderCalled).isEqualTo(0)\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun renderModel_is_not_invoked_for_equal_model() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      assertThat(renderer.inflateCalled).isEqualTo(0)\n      assertThat(renderer.renderCalled).isEqualTo(0)\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n    }\n  }\n\n  @Test\n  fun inflate_is_invoked_after_detach() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer = renderer(activity)\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      activity.contentView.removeAllViews()\n\n      renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(2)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun inflate_is_not_invoked_after_detach_when_not_released() {\n    activityRule.scenario.onActivity { activity ->\n      val renderer =\n        TestViewRenderer(releaseViewOnDetach = false).also {\n          it.init(activity, activity.contentView)\n        }\n\n      renderer.render(TestModel(1))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(1)\n\n      activity.contentView.removeAllViews()\n\n      renderer.render(TestModel(2))\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n    }\n  }\n\n  @Test\n  fun renderModel_provides_the_previous_model() {\n    activityRule.scenario.onActivity { activity ->\n      var currentModel: TestModel? = null\n      var previousModel: TestModel? = null\n\n      val renderer =\n        object : ViewRenderer<TestModel>() {\n          override fun inflate(\n            activity: Activity,\n            parent: ViewGroup,\n            layoutInflater: LayoutInflater,\n            initialModel: TestModel,\n          ): View = View(activity)\n\n          override fun renderModel(model: TestModel, lastModel: TestModel?) {\n            currentModel = model\n            previousModel = lastModel\n          }\n        }\n\n      renderer.init(activity, activity.contentView)\n\n      renderer.render(TestModel(1))\n      assertThat(currentModel).isEqualTo(TestModel(1))\n      assertThat(previousModel).isNull()\n\n      renderer.render(TestModel(2))\n      assertThat(currentModel).isEqualTo(TestModel(2))\n      assertThat(previousModel).isEqualTo(TestModel(1))\n\n      activity.contentView.removeAllViews()\n\n      renderer.render(TestModel(3))\n      assertThat(currentModel).isEqualTo(TestModel(3))\n      assertThat(previousModel).isNull()\n    }\n  }\n\n  @Test\n  fun the_coroutine_scope_is_canceled_after_detach() {\n    activityRule.scenario.onActivity { activity ->\n      lateinit var coroutineScope: CoroutineScope\n\n      val renderer =\n        object : ViewRenderer<TestModel>() {\n          override fun inflate(\n            activity: Activity,\n            parent: ViewGroup,\n            layoutInflater: LayoutInflater,\n            initialModel: TestModel,\n          ): View {\n            coroutineScope = this.coroutineScope\n            return View(activity)\n          }\n        }\n\n      renderer.init(activity, activity.contentView)\n\n      renderer.render(TestModel(1))\n      assertThat(coroutineScope.isActive).isTrue()\n\n      val oldScope = coroutineScope\n\n      activity.contentView.removeAllViews()\n      assertThat(coroutineScope.isActive).isFalse()\n\n      renderer.render(TestModel(2))\n      assertThat(coroutineScope.isActive).isTrue()\n      assertThat(coroutineScope).isNotSameInstanceAs(oldScope)\n    }\n  }\n\n  @Test\n  fun the_coroutine_scope_is_canceled_when_the_view_was_never_attached() {\n    lateinit var coroutineScope: CoroutineScope\n\n    activityRule.scenario.onActivity { activity ->\n      val renderer =\n        object : ViewRenderer<TestModel>() {\n          override fun inflate(\n            activity: Activity,\n            parent: ViewGroup,\n            layoutInflater: LayoutInflater,\n            initialModel: TestModel,\n          ): View {\n            coroutineScope = this.coroutineScope\n            return View(activity)\n          }\n        }\n\n      // Note that this parent is never attached to the view hierarchy and therefore onDetach\n      // never gets called.\n      val parent = FrameLayout(activity)\n      renderer.init(activity, parent)\n\n      renderer.render(TestModel(1))\n      assertThat(coroutineScope.isActive).isTrue()\n\n      activity.finish()\n    }\n\n    activityRule.scenario.moveToState(Lifecycle.State.DESTROYED)\n\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  fun the_coroutine_scope_is_canceled_when_the_view_is_not_released_on_detach() {\n    lateinit var coroutineScope: CoroutineScope\n\n    activityRule.scenario.onActivity { activity ->\n      val renderer =\n        object : ViewRenderer<TestModel>() {\n          override fun inflate(\n            activity: Activity,\n            parent: ViewGroup,\n            layoutInflater: LayoutInflater,\n            initialModel: TestModel,\n          ): View {\n            coroutineScope = this.coroutineScope\n            return View(activity)\n          }\n\n          override fun releaseViewOnDetach(): Boolean = false\n        }\n\n      renderer.init(activity, activity.contentView)\n\n      renderer.render(TestModel(1))\n      assertThat(coroutineScope.isActive).isTrue()\n    }\n\n    activityRule.scenario.moveToState(Lifecycle.State.RESUMED)\n    assertThat(coroutineScope.isActive).isTrue()\n\n    activityRule.scenario.moveToState(Lifecycle.State.DESTROYED)\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  fun onDetach_is_called_once() {\n    // This test verifies an edge case we suppressed for a long time. The crash happened in the\n    // ViewRenderer implementation when not correctly unregistering onAttach / onDetach callbacks.\n    //\n    // java.lang.NullPointerException: Attempt to write to field 'android.view.ViewParent\n    // android.view.View.mParent' on a null object reference\n    //   at android.view.ViewGroup.removeFromArray(ViewGroup.java:5384)\n    //   at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5581)\n    //   at android.view.ViewGroup.removeViewInternal(ViewGroup.java:5543)\n    //   at android.view.ViewGroup.removeView(ViewGroup.java:5474)\n\n    activityRule.scenario.onActivity { activity ->\n      val grandParent = FrameLayout(activity)\n      activity.setContentView(grandParent)\n\n      val parent = FrameLayout(activity)\n\n      // To trigger the crash it is important that the 'parent' container is not attached to the\n      // view hierarchy yet (not a child of 'grandParent' yet).\n      val renderer = renderer(activity, parent)\n      renderer.render(TestModel(1))\n      // By removing the view we'll add it to the 'parent' in the next render() call again. The bug\n      // used to be that we didn't clear the callbacks. In production the issue also manifested\n      // during Activity.onCreate() before Activity.onStart() without explicitly removing all views.\n      parent.removeAllViews()\n      renderer.render(TestModel(2))\n\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n      assertThat(renderer.onDetachCalled).isEqualTo(0)\n\n      // Adding the view invoked the onAttach callback and removing it will invoke the onDetach\n      // callback. In the past without clearing the callbacks properly onDetach was called\n      // twice and triggered the exception.\n      grandParent.addView(parent)\n      grandParent.removeAllViews()\n\n      assertThat(renderer.inflateCalled).isEqualTo(1)\n      assertThat(renderer.renderCalled).isEqualTo(2)\n      assertThat(renderer.onDetachCalled).isEqualTo(1)\n    }\n  }\n\n  @Test\n  fun it_is_forbidden_to_change_the_parent() {\n    activityRule.scenario.onActivity { activity ->\n      val parent1 = FrameLayout(activity)\n      val parent2 = FrameLayout(activity)\n\n      val renderer = TestViewRenderer()\n\n      renderer.init(activity, parent1)\n      // It is allowed to change the parent before the view gets created.\n      renderer.init(activity, parent2)\n\n      // This creates the view.\n      renderer.render(TestModel(1))\n\n      assertFailure { renderer.init(activity, parent1) }\n        .messageContains(\"A ViewRenderer should ever be only attached to one parent view.\")\n    }\n  }\n\n  @Test\n  fun multiple_renderers_with_the_same_parent_can_be_detached_when_the_activity_is_destroyed() {\n    val renderers = List(10) { TestViewRenderer() }\n\n    activityRule.scenario.onActivity { activity ->\n      val parent = FrameLayout(activity).also { activity.contentView.addView(it) }\n\n      renderers.forEachIndexed { index, renderer ->\n        renderer.init(activity, parent)\n\n        // This creates and attaches the view.\n        renderer.render(TestModel(index))\n      }\n    }\n\n    activityRule.scenario.moveToState(Lifecycle.State.RESUMED)\n\n    activityRule.scenario.onActivity { activity ->\n      val childViews =\n        activity.contentView.children.filterIsInstance<ViewGroup>().single().children.toList()\n\n      childViews.forEach { assertThat(it.isAttachedToWindow).isTrue() }\n      renderers.forEach {\n        assertThat(it.inflateCalled).isEqualTo(1)\n        assertThat(it.renderCalled).isEqualTo(1)\n        assertThat(it.onDetachCalled).isEqualTo(0)\n      }\n    }\n\n    activityRule.scenario.moveToState(Lifecycle.State.DESTROYED)\n\n    // Wait for the idle sync, otherwise not all onDetach callbacks may have been invoked.\n    InstrumentationRegistry.getInstrumentation().waitForIdleSync()\n\n    renderers.forEach {\n      assertThat(it.inflateCalled).isEqualTo(1)\n      assertThat(it.renderCalled).isEqualTo(1)\n      assertThat(it.onDetachCalled).isEqualTo(1)\n    }\n  }\n\n  private fun renderer(\n    activity: Activity,\n    parent: ViewGroup = activity.contentView,\n  ): TestViewRenderer {\n    return TestViewRenderer().also { it.init(activity, parent) }\n  }\n\n  private val Activity.contentView: ViewGroup\n    get() = findViewById(android.R.id.content)\n\n  private data class TestModel(val value: Int) : BaseModel\n\n  private class TestViewRenderer(private val releaseViewOnDetach: Boolean = true) :\n    ViewRenderer<TestModel>() {\n\n    private lateinit var textView: TextView\n\n    var inflateCalled = 0\n      private set\n\n    var renderCalled = 0\n      private set\n\n    var onDetachCalled = 0\n      private set\n\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: TestModel,\n    ): View =\n      TextView(activity).also {\n        textView = it\n        inflateCalled++\n      }\n\n    override fun renderModel(model: TestModel) {\n      textView.text = \"Test: ${model.value}\"\n      renderCalled++\n    }\n\n    override fun releaseViewOnDetach(): Boolean {\n      return releaseViewOnDetach\n    }\n\n    override fun onDetach() {\n      onDetachCalled++\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidInstrumentedTest/res/layout/viewbinding_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView android:id=\"@+id/viewbinding_text_view\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterAndroid.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.activity.BackEventCompat\nimport androidx.activity.OnBackPressedCallback\nimport androidx.activity.OnBackPressedDispatcher\nimport androidx.activity.OnBackPressedDispatcherOwner\nimport androidx.lifecycle.lifecycleScope\nimport java.util.concurrent.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.channels.BufferOverflow.SUSPEND\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.channels.Channel.Factory.BUFFERED\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.consumeAsFlow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.launch\n\n/**\n * Registers a callback in the [OnBackPressedDispatcher] that is enabled as long as there is a\n * presenter with an enabled back handler. This function forwards back press events to presenters\n * until the lifecycle from [onBackPressedDispatcherOwner] is in the destroyed state.\n *\n * It's recommended to call this function from your Android `Activity`:\n * ```\n * class MainActivity : ComponentActivity() {\n *   override fun onCreate(savedInstanceState: Bundle?) {\n *     super.onCreate(savedInstanceState)\n *\n *     // Inject the dispatcher from the dependency injection graph.\n *     backGestureDispatcherPresenter.forwardBackPressEventsToPresenters(this)\n *\n *     ...\n *   }\n * }\n * ```\n */\npublic fun BackGestureDispatcherPresenter.forwardBackPressEventsToPresenters(\n  onBackPressedDispatcherOwner: OnBackPressedDispatcherOwner\n) {\n  // Later if needed we can consider limiting this to the STARTED lifecycle if needed with\n  // repeatOnLifecycle API. For now we forward events until the lifecycle changes to DESTROYED.\n  onBackPressedDispatcherOwner.lifecycleScope.launch {\n    val forwarder =\n      BackGestureForwarder(\n        onBackPressedDispatcherOwner = onBackPressedDispatcherOwner,\n        dispatcherPresenter = this@forwardBackPressEventsToPresenters,\n      )\n    forwarder.forwardEvents()\n  }\n}\n\nprivate class BackGestureForwarder(\n  private val onBackPressedDispatcherOwner: OnBackPressedDispatcherOwner,\n  private val dispatcherPresenter: BackGestureDispatcherPresenter,\n) {\n  // This function never returns and this is by design. We stop listening to updates when the\n  // coroutine get canceled.\n  suspend fun forwardEvents(): Nothing = coroutineScope {\n    val backCallBack = createOnBackPressCallback(this)\n    try {\n      onBackPressedDispatcherOwner.onBackPressedDispatcher.addCallback(\n        onBackPressedDispatcherOwner,\n        backCallBack,\n      )\n\n      dispatcherPresenter.listenersCount.collect { count -> backCallBack.isEnabled = count > 0 }\n    } finally {\n      backCallBack.remove()\n    }\n  }\n\n  private fun createOnBackPressCallback(coroutineScope: CoroutineScope): OnBackPressedCallback {\n    val enabled = dispatcherPresenter.listenersCount.value > 0\n\n    // This implementation comes mainly from\n    // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity-compose/src/main/java/androidx/activity/compose/PredictiveBackHandler.kt\n    return object : OnBackPressedCallback(enabled) {\n      var onBackInstance: OnBackInstance? = null\n\n      override fun handleOnBackStarted(backEvent: BackEventCompat) {\n        super.handleOnBackStarted(backEvent)\n        // in case the previous onBackInstance was started by a normal back gesture\n        // we want to make sure it's still cancelled before we start a predictive\n        // back gesture\n        onBackInstance?.cancel()\n        onBackInstance = OnBackInstance(coroutineScope, true) { onBack(it) }\n      }\n\n      override fun handleOnBackProgressed(backEvent: BackEventCompat) {\n        super.handleOnBackProgressed(backEvent)\n        onBackInstance?.send(backEvent)\n      }\n\n      override fun handleOnBackPressed() {\n        // handleOnBackPressed could be called by regular back to restart\n        // a new back instance. If this is the case (where current back instance\n        // was NOT started by handleOnBackStarted) then we need to reset the previous\n        // regular back.\n        onBackInstance?.run {\n          if (!isPredictiveBack) {\n            cancel()\n            onBackInstance = null\n          }\n        }\n        if (onBackInstance == null) {\n          onBackInstance = OnBackInstance(coroutineScope, false) { onBack(it) }\n        }\n\n        // finally, we close the channel to ensure no more events can be sent\n        // but let the job complete normally\n        onBackInstance?.close()\n      }\n\n      override fun handleOnBackCancelled() {\n        super.handleOnBackCancelled()\n        // cancel will purge the channel of any sent events that are yet to be received\n        onBackInstance?.cancel()\n      }\n    }\n  }\n\n  private suspend fun onBack(events: Flow<BackEventCompat>) {\n    dispatcherPresenter.onPredictiveBack(\n      events.map {\n        BackEventPresenter(\n          touchX = it.touchX,\n          touchY = it.touchY,\n          progress = it.progress,\n          swipeEdge = it.swipeEdge,\n        )\n      }\n    )\n  }\n}\n\nprivate class OnBackInstance(\n  scope: CoroutineScope,\n  val isPredictiveBack: Boolean,\n  onBack: suspend (progress: Flow<BackEventCompat>) -> Unit,\n) {\n  val channel = Channel<BackEventCompat>(capacity = BUFFERED, onBufferOverflow = SUSPEND)\n  val job = scope.launch {\n    var completed = false\n    onBack(channel.consumeAsFlow().onCompletion { completed = true })\n    check(completed) { \"You must collect the progress flow\" }\n  }\n\n  fun send(backEvent: BackEventCompat) = channel.trySend(backEvent)\n\n  // idempotent if invoked more than once\n  fun close() = channel.close()\n\n  fun cancel() {\n    channel.cancel(CancellationException(\"onBack cancelled\"))\n    job.cancel()\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/AndroidRendererFactory.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.ViewGroup\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/** [RendererFactory] that is able to create [ViewRenderer] instances for Android. */\npublic open class AndroidRendererFactory(\n  rootScopeProvider: RootScopeProvider,\n  private val activity: Activity,\n  private val parent: ViewGroup,\n) : BaseRendererFactory(rootScopeProvider) {\n\n  override fun <T : BaseModel> createRenderer(modelType: KClass<out T>): Renderer<T> {\n    return createRenderer(modelType, parent)\n  }\n\n  /**\n   * Allows you to override the parent ViewGroup passed in as constructor argument. This is helpful\n   * for renderers that embed other renderers and want to change the parent ViewGroup for them.\n   *\n   * @see [createRenderer] for more details.\n   */\n  public fun <T : BaseModel> createRenderer(\n    modelType: KClass<out T>,\n    parent: ViewGroup,\n  ): Renderer<T> {\n    val renderer = super.createRenderer(modelType)\n\n    if (renderer is BaseAndroidViewRenderer<*>) {\n      renderer.init(activity, parent)\n    }\n\n    return renderer\n  }\n\n  override fun <T : BaseModel> getRenderer(modelType: KClass<out T>, rendererId: Int): Renderer<T> {\n    return getRenderer(modelType, parent, rendererId)\n  }\n\n  /**\n   * Allows you to override the parent ViewGroup passed in as constructor argument. This is helpful\n   * for renderers that embed other renderers and want to change the parent ViewGroup for them.\n   *\n   * @see [getRenderer] for more details.\n   */\n  public fun <T : BaseModel> getRenderer(\n    modelType: KClass<out T>,\n    parent: ViewGroup,\n    rendererId: Int = 0,\n  ): Renderer<T> {\n    // For different parents we must return unique renderers. Therefore, include the parent in the\n    // rendererId.\n    val finalRendererId = parent.hashCode() + rendererId\n\n    val renderer = super.getRenderer(modelType, finalRendererId)\n\n    if (renderer is BaseAndroidViewRenderer<*>) {\n      renderer.init(activity, parent)\n    }\n\n    return renderer\n  }\n}\n\n/**\n * Convenience function to pass in [parent] to [RendererFactory.createRenderer], if it's an\n * [AndroidRendererFactory]. Usually, the parent needs to be changed when a [Renderer] embeds\n * another [Renderer].\n */\npublic fun <T : BaseModel> RendererFactory.createRenderer(\n  modelType: KClass<out T>,\n  parent: ViewGroup,\n): Renderer<T> {\n  return if (this is AndroidRendererFactory) {\n    createRenderer(modelType, parent)\n  } else {\n    createRenderer(modelType)\n  }\n}\n\n/**\n * Convenience function to pass in [parent] to [RendererFactory.getRenderer], if it's an\n * [AndroidRendererFactory]. Usually, the parent needs to be changed when a [Renderer] embeds\n * another [Renderer].\n */\npublic fun <T : BaseModel> RendererFactory.getRenderer(\n  modelType: KClass<out T>,\n  parent: ViewGroup,\n  rendererId: Int = 0,\n): Renderer<T> {\n  return if (this is AndroidRendererFactory) {\n    getRenderer(modelType, parent, rendererId)\n  } else {\n    getRenderer(modelType, rendererId)\n  }\n}\n\n/**\n * Creates and caches the returned [Renderer] with the given [parent] as renderer ID. This implies a\n * one-to-one relationship between the returned [Renderer] and the [parent] view.\n *\n * This function should generally only be used, when the returned [Renderer] is part of a\n * `RecyclerView`. All child views of the recycled views should be cached for efficiency. This is\n * what this function achieves by having the one-to-one relationship.\n *\n * Another use case is showing multiple renderers of the same type on the same screen simultaneously\n * and their identity is determined by the position in the layout or on other words by the [parent]\n * view.\n */\n@Deprecated(\n  message = \"getRenderer() takes the parent into account for caching.\",\n  replaceWith = ReplaceWith(\"getRenderer(modelType, parent)\"),\n  level = DeprecationLevel.WARNING,\n)\npublic fun <T : BaseModel> RendererFactory.getChildRendererForParent(\n  modelType: KClass<out T>,\n  parent: ViewGroup,\n): Renderer<T> {\n  val rendererId = parent.hashCode()\n\n  return if (this is AndroidRendererFactory) {\n    getRenderer(modelType, parent, rendererId)\n  } else {\n    getRenderer(modelType, rendererId)\n  }\n}\n\n/**\n * Creates and caches the returned [Renderer] with the given [parent] as renderer ID. This implies a\n * one-to-one relationship between the returned [Renderer] and the [parent] view.\n *\n * This function should generally only be used, when the returned [Renderer] is part of a\n * `RecyclerView`. All child views of the recycled views should be cached for efficiency. This is\n * what this function achieves by having the one-to-one relationship.\n *\n * Another use case is showing multiple renderers of the same type on the same screen simultaneously\n * and their identity is determined by the position in the layout or on other words by the [parent]\n * view.\n */\n@Deprecated(\n  message = \"getRenderer() takes the parent into account for caching.\",\n  replaceWith = ReplaceWith(\"getRenderer(modelType, parent)\"),\n  level = DeprecationLevel.WARNING,\n)\npublic fun <T : BaseModel> RendererFactory.getChildRendererForParent(\n  model: T,\n  parent: ViewGroup,\n): Renderer<T> = getRenderer(model::class, parent)\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/BaseAndroidViewRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.ViewGroup\nimport software.amazon.app.platform.presenter.BaseModel\n\n/** Base type for [Renderer]s that leverage the Android View system. */\npublic interface BaseAndroidViewRenderer<in ModelT : BaseModel> : Renderer<ModelT> {\n\n  /**\n   * Initialize this [Renderer] with an [Activity] and a parent [ViewGroup], in which the [Renderer]\n   * should add its views as children.\n   */\n  public fun init(activity: Activity, parent: ViewGroup)\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/RecyclerViewViewHolderRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * A specific [Renderer] implementation that can be used for single elements in a [RecyclerView].\n * This implementation is different than [ViewRenderer] in the way that views, which are\n * instantiated by [inflate], aren't removed from the view hierarchy nor nullified when they get\n * detached. It's expected in a [RecyclerView] that views frequently are detached and attached\n * (recycled).\n *\n * An implementation could look like this:\n * ```\n * @ContributesRenderer\n * class SampleRowRenderer : RecyclerViewHolderRenderer<SampleRowPresenter.Model>() {\n *\n *     private lateinit var textView: TextView\n *\n *     override fun inflate(\n *         activity: Activity,\n *         parent: ViewGroup,\n *         layoutInflater: LayoutInflater,\n *     ): View {\n *         return layoutInflater.inflate(android.R.layout.simple_list_item_1, parent, false).also {\n *             textView = it.findViewById(android.R.id.text1)\n *         }\n *     }\n *\n *     override fun render(model: SampleRowPresenter.Model) {\n *         textView.text = model.title\n *     }\n * }\n * ```\n *\n * The expected pattern is to use [RendererFactory] in a [RecyclerView.Adapter] to create instances\n * of [RecyclerViewViewHolderRenderer] and call them whenever a view needs to be updated:\n * ```\n * private class SampleAdapter(\n *     private val rendererFactory: RendererFactory,\n * ) : RecyclerView.Adapter<RecyclerViewHolderRenderer.ViewHolder<SampleRowPresenter.Model>>() {\n *\n *     override fun onCreateViewHolder(\n *         parent: ViewGroup,\n *         viewType: Int,\n *     ): RecyclerViewHolderRenderer.ViewHolder<SampleRowPresenter.Model> {\n *         val renderer = rendererFactory\n *             .createRenderer(SampleRowPresenter.Model::class, parent) as RecyclerViewHolderRenderer\n *\n *         return renderer.viewHolder()\n *     }\n *\n *     override fun onBindViewHolder(\n *         holder: RecyclerViewHolderRenderer.ViewHolder<SampleRowPresenter.Model>,\n *         position: Int,\n *     ) {\n *         val sampleRowModel = currentModel(position)\n *\n *         holder.renderer.render(sampleRowModel.model)\n *     }\n * }\n * ```\n *\n * [viewHolder] provides the [RecyclerView.ViewHolder] instance, which keeps a reference to the\n * [RecyclerViewViewHolderRenderer].\n */\npublic abstract class RecyclerViewViewHolderRenderer<ModelT : BaseModel> :\n  BaseAndroidViewRenderer<ModelT> {\n\n  private var _activity: Activity? = null\n  private var _parent: ViewGroup? = null\n\n  /** The Android [Activity] associated with this [Renderer]. This value will never change. */\n  protected val activity: Activity\n    get() = checkNotNull(_activity) { \"Call init() first.\" }\n\n  private val parent: ViewGroup\n    get() = checkNotNull(_parent) { \"Call init() first.\" }\n\n  private var viewHolder: ViewHolder<ModelT>? = null\n\n  final override fun init(activity: Activity, parent: ViewGroup) {\n    if (_activity == null) {\n      _activity = activity\n      _parent = parent\n    } else if (activity != this.activity || parent != this.parent) {\n      // This is because the ViewHolder is associated with a single RecyclerView. The\n      // memory will be reclaimed when the RecyclerView is removed from the view hierarchy.\n      @Suppress(\"UseCheckOrError\")\n      throw IllegalStateException(\n        \"A RecyclerViewViewHolderRenderer should only be reused with the same parent view.\"\n      )\n    }\n  }\n\n  /**\n   * The [RecyclerView.ViewHolder] associated with this [RecyclerViewViewHolderRenderer]. This\n   * `ViewHolder` should be returned from [RecyclerView.Adapter.onCreateViewHolder]:\n   * ```\n   * override fun onCreateViewHolder(\n   *     parent: ViewGroup,\n   *     viewType: Int,\n   * ): RecyclerViewHolderRenderer.ViewHolder<SampleRowPresenter.Model> {\n   *     val renderer = rendererFactory\n   *         .createRenderer(SampleRowPresenter.Model::class, parent) as RecyclerViewHolderRenderer\n   *\n   *     return renderer.viewHolder()\n   * }\n   * ```\n   */\n  public fun viewHolder(): ViewHolder<ModelT> {\n    return viewHolder\n      ?: ViewHolder(inflate(activity, parent, activity.layoutInflater), this).also {\n        viewHolder = it\n      }\n  }\n\n  /**\n   * Perform one time view inflation for the layout of this `Renderer`. This method is called when\n   * the [viewHolder] is initialized and won't be called again. It is therefore a good place to do\n   * initial setup that need only be done a single time.\n   */\n  protected abstract fun inflate(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n  ): View\n\n  /**\n   * Common [RecyclerView.ViewHolder] implementation that provides access to your particular\n   * [renderer] instance. Retrieve the [renderer] in [RecyclerView.Adapter.onBindViewHolder] and\n   * call [Renderer.render]:\n   * ```\n   * override fun onBindViewHolder(\n   *     holder: RecyclerViewHolderRenderer.ViewHolder<SampleRowPresenter.Model>,\n   *     position: Int,\n   * ) {\n   *     val sampleRowModel = currentModel(position)\n   *\n   *     holder.renderer.render(sampleRowModel.model)\n   * }\n   * ```\n   */\n  public class ViewHolder<M : BaseModel>\n  internal constructor(view: View, public val renderer: Renderer<M>) : RecyclerView.ViewHolder(view)\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ViewBindingRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.viewbinding.ViewBinding\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * An implementation of [Renderer] that is specific to Android View based UI and uses Android\n * ViewBinding. This renderer provides some built in convenience for rendering Android Views such as\n * managing your child's UI in relation to the provided parent's lifecycle and inflating\n * ViewBindings.\n *\n * Your custom renderers should extend this abstract class and provide implementations for\n * [inflateViewBinding] and [renderModel].\n *\n * [inflateViewBinding] is where one time ViewBinding inflation should take place. This method will\n * not be called again while the Renderer is in use. This method is roughly equivalent to\n * `onCreateView`.\n *\n * ```\n *  override fun inflateViewBinding(\n *      activity: Activity,\n *      parent: ViewGroup,\n *      layoutInflater: LayoutInflater\n *  ): MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater, parent, false)\n * ```\n *\n * [renderModel] is called each time there is a new model for this renderer to render. Place logic\n * here that is specific to binding model data or callbacks to your view layer.\n *\n * ```\n * override fun renderModel(model: MyModel) {\n *     binding.myTextView.text = model.text\n * }\n * ```\n */\npublic abstract class ViewBindingRenderer<ModelT : BaseModel, ViewBindingT : ViewBinding> :\n  ViewRenderer<ModelT>() {\n\n  protected lateinit var binding: ViewBindingT\n    private set\n\n  /** Inflates the [ViewBindingT]. */\n  final override fun inflate(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n    initialModel: ModelT,\n  ): View {\n    binding = inflateViewBinding(activity, parent, layoutInflater, initialModel)\n    return binding.root\n  }\n\n  /**\n   * Provides a hook to inflate [ViewBindingT] and do any other one time set up work for the\n   * [Renderer]. Also provides the [initialModel] to enable any needed callbacks to be done.\n   */\n  protected abstract fun inflateViewBinding(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n    initialModel: ModelT,\n  ): ViewBindingT\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ViewRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.app.Application\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.children\nimport androidx.core.view.doOnDetach\nimport androidx.recyclerview.widget.RecyclerView\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.cancel\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * An implementation of [Renderer] that is specific to Android View based UI. This renderer provides\n * some built in convenience for rendering Android Views such as managing your child's UI in\n * relation to the provided parent's lifecycle.\n *\n * Your custom renderers should extend this abstract class and provide implementations for [inflate]\n * and [renderModel].\n *\n * [inflate] is where one time view set up should take place. This method will not be called again\n * while the Renderer is in use. This method is roughly equivalent to `onCreateView` and should be\n * used inflate layouts or write UI code directly in Kotlin.\n *\n * ```\n *  private lateinit var myView: TextView\n *\n *  override fun inflate(\n *      activity: Activity,\n *      parent: ViewGroup,\n *      layoutInflater: LayoutInflater\n *  ): View {\n *      return layoutInflator.inflate(R.layout.myView, parent, false).also {\n *          // Here you can initialize other variables you need to use in your [renderModel] method.\n *          myView = it.findViewById(R.id.myView)\n *      }\n *  }\n * ```\n *\n * [renderModel] is called each time there is a new model for this renderer to render. Place logic\n * here that is specific to binding model data or callbacks to your view layer.\n *\n * ```\n * override fun renderModel(model: MyModel) {\n *     myView.text = model.text\n * }\n * ```\n *\n * Optionally your custom renderer can override [onDetach] to perform cleanup tasks before your\n * views are removed from the hierarchy. Once the inflated view was detached from the window, it\n * will be removed from the parent and nullified. If this [ViewRenderer] is reused, then a new view\n * will be inflated and added to the parent.\n */\npublic abstract class ViewRenderer<in ModelT : BaseModel> : BaseAndroidViewRenderer<ModelT> {\n\n  /**\n   * The [Activity] that was provided to the [AndroidRendererFactory]. The same [Activity] is passed\n   * as argument to [inflate].\n   */\n  protected lateinit var activity: Activity\n    private set\n\n  /**\n   * A [CoroutineScope] gets created before the view gets inflated in [inflate]. In can be used to\n   * register UI related background work. This [CoroutineScope] gets canceled when the view is\n   * removed from the view hierarchy or [activity] gets destroyed.\n   */\n  protected lateinit var coroutineScope: CoroutineScope\n    private set\n\n  private lateinit var parent: ViewGroup\n\n  private var view: View? = null\n  private var lastModel: ModelT? = null\n\n  private val onAttachListener =\n    object : View.OnAttachStateChangeListener {\n      override fun onViewAttachedToWindow(v: View) {\n        // Invoke this callback only once.\n        v.removeOnAttachStateChangeListener(this)\n\n        onViewAttached(v)\n      }\n\n      override fun onViewDetachedFromWindow(v: View) = Unit\n    }\n\n  final override fun init(activity: Activity, parent: ViewGroup) {\n    if (!this::activity.isInitialized) {\n      // Renderer is not initialized.\n      this.activity = activity\n      this.parent = parent\n    }\n\n    // We don't want to allow changing the parent. However, during the initialization procedure\n    // we call init() with the default parent from the RendererFactory and eventually init()\n    // again with the parent provided getRenderer() call. If no view has been created yet and the\n    // parent hasn't been used yet, then it is okay to update the value.\n\n    if (view == null) {\n      this.parent = parent\n    }\n\n    check(this.activity == activity && this.parent == parent) {\n      \"A ViewRenderer should ever be only attached to one parent view. Current parent is \" +\n        \"${this.parent}, new parent is $parent\"\n    }\n  }\n\n  private fun createView(model: ModelT): View {\n    val mainCoroutineScope = MainScope().also { coroutineScope = it }\n\n    activity.doOnDestroy { mainCoroutineScope.cancel() }\n\n    return inflate(activity, parent, activity.layoutInflater, model).also { view = it }\n  }\n\n  private fun onViewAttached(view: View) {\n    lastModel = null\n\n    // Wait for the view to be attached first, otherwise doOnDetach gets\n    // called immediately.\n    view.doOnDetach {\n      if (releaseViewOnDetach()) {\n        // call onDetach first so view is still available during cleanup tasks\n        onDetach()\n        resetView(view)\n      }\n    }\n  }\n\n  private fun resetView(view: View) {\n    coroutineScope.cancel()\n\n    // Allows us to reclaim the memory. Reset all cached value before calling removeView() in case\n    // there are any recursive calls with doOnDetach for child views.\n    this.view = null\n\n    // Reset the last model, because we want to re-render when the view is attached again even\n    // for the same model.\n    lastModel = null\n\n    // Remove the view from the parent. In case the Renderer is reused we inflate a new\n    // View and add it to the parent. This action must run after all currently queued main\n    // thread operations finish. This is needed because we cannot remove views from parents in\n    // the onDetach callback as this may lead to inconsistent view state and trigger crashes.\n    Handler(Looper.getMainLooper()).post {\n      if (view.parent === parent) {\n        parent.removeView(view)\n      }\n    }\n  }\n\n  final override fun render(model: ModelT) {\n    val view = view ?: createView(model)\n\n    if (parent.children.none { it === view }) {\n      parent.addView(view)\n\n      // In case we registered the callback before, remove it first. There is no API to check\n      // whether this callback has been registered before. The callback should only be registered\n      // once.\n      view.removeOnAttachStateChangeListener(onAttachListener)\n\n      // This implementation below is similar to `doOnAttach {}`, which we used to use. However,\n      // this extension function didn't allow us to unregister the previous callback and we\n      // accidentally registered too many. That's why we had to extract `onAttachListener` into\n      // a variable.\n      if (view.isAttachedToWindow) {\n        onViewAttached(view)\n      } else {\n        view.addOnAttachStateChangeListener(onAttachListener)\n      }\n    }\n\n    // Avoid re-rendering same model\n    if (model == lastModel) {\n      return\n    }\n\n    renderModel(model, lastModel)\n    lastModel = model\n  }\n\n  /**\n   * Perform one time view inflation for the layout of this Renderer. This method is called on\n   * initial Render and won't be called again; it is therefore a good place to do initial setup that\n   * need only be done a single time.\n   */\n  protected abstract fun inflate(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n    initialModel: ModelT,\n  ): View\n\n  /**\n   * Called when a new Model is given to the Renderer, use if there is no need to compare [model]\n   * with any prior model in order to render specific views. This function will not be called if\n   * `renderModel(model: T, lastModel: T?)` is overridden.\n   */\n  protected open fun renderModel(model: ModelT): Unit = Unit\n\n  /**\n   * Called when a new Model is given to the Renderer. Provides an opportunity for a Renderer to\n   * compare [model] with [lastModel]. By default, this calls `renderModel(model: T)`.\n   */\n  protected open fun renderModel(model: ModelT, lastModel: ModelT?): Unit = renderModel(model)\n\n  /**\n   * Inheritors of this class can optionally override this method to do cleanup tasks before their\n   * views are removed from the hierarchy.\n   */\n  public open fun onDetach(): Unit = Unit\n\n  /**\n   * This function is called when the view inflated by this renderer was detached, meaning the view\n   * itself or one of its parents got removed from the view hierarchy.\n   *\n   * When this function returns `true`, then the view is manually removed from the parent and\n   * released, meaning it can be garbage collected. [coroutineScope] is canceled at the same time.\n   * The next time [render] is called, [inflate] will be called, a new view gets inflated and added\n   * to the parent.\n   *\n   * When this function returns `false`, then the view is cached and will not be removed from the\n   * parent. [coroutineScope] isn't canceled either. This behavior is usually desired in view groups\n   * that manually manage their children, such as a [RecyclerView].\n   *\n   * The default implementation of this function checks whether any of the parent views is an\n   * instance of [RecyclerView] and returns the appropriate result. This behavior can be changed by\n   * subclasses of [ViewRenderer].\n   */\n  protected open fun releaseViewOnDetach(): Boolean {\n    val parents =\n      generateSequence(parent) { viewGroup -> viewGroup.parent as? ViewGroup }\n        .takeWhile { it.id != android.R.id.content }\n\n    return parents.none { it is RecyclerView }\n  }\n\n  private companion object {\n    fun Activity.doOnDestroy(block: (Activity) -> Unit) {\n      if (isDestroyed) {\n        block(this)\n        return\n      }\n\n      application.registerActivityLifecycleCallbacks(\n        object : Application.ActivityLifecycleCallbacks {\n          override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit\n\n          override fun onActivityStarted(activity: Activity) = Unit\n\n          override fun onActivityResumed(activity: Activity) = Unit\n\n          override fun onActivityPaused(activity: Activity) = Unit\n\n          override fun onActivityStopped(activity: Activity) = Unit\n\n          override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit\n\n          override fun onActivityDestroyed(activity: Activity) {\n            if (activity == this@doOnDestroy) {\n              application.unregisterActivityLifecycleCallbacks(this)\n              block(this@doOnDestroy)\n            }\n          }\n        }\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-android-view/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/template/AndroidTemplateRenderer.kt",
    "content": "package software.amazon.app.platform.renderer.template\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.ViewGroup\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.template.Template\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.ViewRenderer\nimport software.amazon.app.platform.renderer.getRenderer\n\n/**\n * Base [Renderer] for Android that wraps concrete templates in a [Container] conceptionally. A\n * [Container] is backed by a [ViewGroup] and when switching between different templates the old\n * container (read `ViewGroup`) gets hidden and the new container (read `ViewGroup`) gets shown.\n */\npublic abstract class AndroidTemplateRenderer<T : Template>(\n  private val rendererFactory: RendererFactory\n) : ViewRenderer<T>() {\n\n  protected inner class Container(\n    @Suppress(\"unused\") private val activity: Activity,\n    private val viewGroup: ViewGroup,\n    private val parentViewGroup: ViewGroup?,\n  ) {\n    private var lastRenderer: Renderer<*>? = null\n\n    public fun renderModel(model: BaseModel) {\n      show()\n\n      val renderer = rendererFactory.getRenderer(model::class, viewGroup)\n      if (renderer !== lastRenderer) {\n        viewGroup.removeAllViews()\n        lastRenderer = renderer\n      }\n\n      renderer.render(model)\n    }\n\n    public fun reset() {\n      viewGroup.removeAllViews()\n      hide()\n    }\n\n    public fun hide() {\n      viewGroup.visibility = View.GONE\n      parentViewGroup?.visibility = View.GONE\n    }\n\n    public fun show() {\n      viewGroup.visibility = View.VISIBLE\n      parentViewGroup?.visibility = View.VISIBLE\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/api/android/public.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterComposeKt {\n\tpublic static final fun ForwardBackPressEventsToPresenters (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/BaseComposeRenderer {\n\tpublic abstract fun renderCompose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/ComposeAndroidRendererFactory : software/amazon/app/platform/renderer/RendererFactory {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/renderer/ComposeAndroidRendererFactory$Companion;\n}\n\npublic final class software/amazon/app/platform/renderer/ComposeAndroidRendererFactory$Companion {\n\tpublic final fun createForAndroidViews (Lsoftware/amazon/app/platform/scope/RootScopeProvider;Landroid/app/Activity;Landroid/view/ViewGroup;)Lsoftware/amazon/app/platform/renderer/ComposeAndroidRendererFactory;\n\tpublic final fun createForComposeUi (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)Lsoftware/amazon/app/platform/renderer/ComposeAndroidRendererFactory;\n}\n\npublic abstract class software/amazon/app/platform/renderer/ComposeRenderer : software/amazon/app/platform/renderer/BaseComposeRenderer, software/amazon/app/platform/renderer/Renderer {\n\tpublic static final field $stable I\n\tpublic fun <init> ()V\n\tprotected abstract fun Compose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n\tpublic final synthetic fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)Ljava/lang/Void;\n\tpublic synthetic fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n\tpublic final fun renderCompose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic final class software/amazon/app/platform/renderer/ComposeRendererFactory : software/amazon/app/platform/renderer/BaseRendererFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n\tpublic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/ComposeRenderer;\n\tpublic synthetic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/ComposeRenderer;\n\tpublic synthetic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/ComposeRendererFactoryKt {\n\tpublic static final fun createComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun createComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun getComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun getComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;I)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static synthetic fun getComposeRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static synthetic fun getComposeRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n}\n\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/api/desktop/public.api",
    "content": "public final class software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterComposeKt {\n\tpublic static final fun ForwardBackPressEventsToPresenters (Lsoftware/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenter;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic abstract interface class software/amazon/app/platform/renderer/BaseComposeRenderer {\n\tpublic abstract fun renderCompose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic abstract class software/amazon/app/platform/renderer/ComposeRenderer : software/amazon/app/platform/renderer/BaseComposeRenderer, software/amazon/app/platform/renderer/Renderer {\n\tpublic static final field $stable I\n\tpublic fun <init> ()V\n\tprotected abstract fun Compose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n\tpublic final synthetic fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)Ljava/lang/Void;\n\tpublic synthetic fun render (Lsoftware/amazon/app/platform/presenter/BaseModel;)V\n\tpublic final fun renderCompose (Lsoftware/amazon/app/platform/presenter/BaseModel;Landroidx/compose/runtime/Composer;I)V\n}\n\npublic final class software/amazon/app/platform/renderer/ComposeRendererFactory : software/amazon/app/platform/renderer/BaseRendererFactory {\n\tpublic static final field $stable I\n\tpublic fun <init> (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n\tpublic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/ComposeRenderer;\n\tpublic synthetic fun createRenderer (Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/Renderer;\n\tpublic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/ComposeRenderer;\n\tpublic synthetic fun getRenderer (Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/Renderer;\n}\n\npublic final class software/amazon/app/platform/renderer/ComposeRendererFactoryKt {\n\tpublic static final fun createComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun createComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun getComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;I)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static final fun getComposeRenderer (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;I)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static synthetic fun getComposeRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lkotlin/reflect/KClass;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n\tpublic static synthetic fun getComposeRenderer$default (Lsoftware/amazon/app/platform/renderer/RendererFactory;Lsoftware/amazon/app/platform/presenter/BaseModel;IILjava/lang/Object;)Lsoftware/amazon/app/platform/renderer/BaseComposeRenderer;\n}\n\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableCompose true\n    enableInstrumentedTests true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':presenter:public')\n    commonMainApi project(':renderer:public')\n    commonMainApi project(':scope:public')\n\n    commonMainImplementation project(':presenter-molecule:public')\n    commonMainImplementation libs.navigation.event.compose\n\n    androidMainApi project(':renderer-android-view:public')\n\n    commonTestImplementation project(':metro:public')\n    commonTestImplementation project(':scope:testing')\n    commonTestImplementation libs.metro.runtime\n\n    androidTestImplementation project(':metro:public')\n    androidTestImplementation project(':presenter-molecule:impl')\n    androidTestImplementation libs.androidx.activity.compose\n    androidTestImplementation libs.androidx.test.espresso\n    androidTestImplementation libs.compose.ui.test.junit4\n    androidTestImplementation libs.compose.ui.test.junit4.android\n    androidTestImplementation libs.compose.ui.test.manifest\n    androidTestImplementation libs.metro.runtime\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidInstrumentedTest/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application android:name=\"software.amazon.app.platform.renderer.TestApplication\">\n        <activity\n            android:name=\"software.amazon.app.platform.renderer.TestActivity\"\n            android:exported=\"false\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/ForwardBackPressEventsToPresentersComposeTest.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.test.assertTextEquals\nimport androidx.compose.ui.test.junit4.AndroidComposeTestRule\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.test.espresso.Espresso\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.ForwardBackPressEventsToPresentersComposeTest.TestPresenter.Model\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.TestActivity\nimport software.amazon.app.platform.renderer.getActivityFromTestRule\n\nclass ForwardBackPressEventsToPresentersComposeTest {\n\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @get:Rule val composeTestRule = AndroidComposeTestRule(activityRule, ::getActivityFromTestRule)\n\n  @Test\n  fun back_press_events_are_forwarded_to_presenters() {\n    val backGestureDispatcherPresenter = BackGestureDispatcherPresenter.createNewInstance()\n\n    val testPresenter = TestPresenter(backGestureDispatcherPresenter)\n    val testRenderer = TestRenderer()\n    val rootRenderer = RootRenderer(backGestureDispatcherPresenter, testRenderer)\n\n    activityRule.scenario.onActivity { activity ->\n      activity.setContent {\n        val model = testPresenter.present(Unit)\n        rootRenderer.renderCompose(model)\n      }\n    }\n\n    composeTestRule.onNodeWithTag(\"count\").assertTextEquals(\"Count: 0\")\n\n    Espresso.pressBack()\n    composeTestRule.onNodeWithTag(\"count\").assertTextEquals(\"Count: 1\")\n\n    Espresso.pressBack()\n    composeTestRule.onNodeWithTag(\"count\").assertTextEquals(\"Count: 2\")\n  }\n\n  private class RootRenderer(\n    private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n    private val testRenderer: TestRenderer,\n  ) : ComposeRenderer<Model>() {\n    @Composable\n    override fun Compose(model: Model) {\n      backGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()\n\n      testRenderer.renderCompose(model)\n    }\n  }\n\n  private class TestPresenter(\n    private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter\n  ) : MoleculePresenter<Unit, Model> {\n    @Composable\n    override fun present(input: Unit): Model {\n      return returningCompositionLocalProvider(\n        LocalBackGestureDispatcherPresenter provides backGestureDispatcherPresenter\n      ) {\n        var backPressCount by remember { mutableIntStateOf(0) }\n\n        BackHandlerPresenter { backPressCount++ }\n\n        Model(backPressCount = backPressCount)\n      }\n    }\n\n    data class Model(val backPressCount: Int) : BaseModel\n  }\n\n  private class TestRenderer : ComposeRenderer<Model>() {\n    @Composable\n    override fun Compose(model: Model) {\n      BasicText(text = \"Count: ${model.backPressCount}\", modifier = Modifier.testTag(\"count\"))\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/ComposeAndroidRendererFactoryDeviceTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.test.ExperimentalTestApi\nimport androidx.compose.ui.test.assertAny\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.assertTextEquals\nimport androidx.compose.ui.test.hasText\nimport androidx.compose.ui.test.junit4.AndroidComposeTestRule\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.onNodeWithText\nimport androidx.compose.ui.test.onRoot\nimport androidx.compose.ui.test.onSiblings\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isInstanceOf\nimport assertk.assertions.isNull\nimport assertk.assertions.isSameInstanceAs\nimport assertk.assertions.messageContains\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.presenter.BaseModel\n\n@ExperimentalTestApi\nclass ComposeAndroidRendererFactoryDeviceTest {\n\n  @get:Rule val activityRule = ActivityScenarioRule(TestActivity::class.java)\n\n  @get:Rule val composeTestRule = AndroidComposeTestRule(activityRule, ::getActivityFromTestRule)\n\n  private lateinit var activity: TestActivity\n  private lateinit var factory: RendererFactory\n\n  private var createdRenderers = 0\n\n  @Before\n  fun prepare() {\n    testApplication.rendererGraph = TestRendererGraph { factory }\n\n    activityRule.scenario.onActivity {\n      activity = it\n\n      val container = LinearLayout(activity)\n      activity.setContentView(container)\n\n      factory =\n        ComposeAndroidRendererFactory.createForAndroidViews(\n          rootScopeProvider = testApplication,\n          activity = activity,\n          parent = container,\n        )\n    }\n  }\n\n  @After\n  fun tearDown() {\n    // The view containers seem to get confused and crash after the test run finished while\n    // tearing down the Activity. This workaround of manually removing the views solves it.\n    composeTestRule.runOnUiThread {\n      val childAt = activity.contentView.getChildAt(0) as ViewGroup\n      childAt.removeAllViews()\n      childAt\n    }\n\n    testApplication.rendererGraph = null\n  }\n\n  @Test\n  fun a_compose_renderer_renders_content_on_screen() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val composeModel = ComposeModel(it)\n        factory.getRenderer(composeModel).render(composeModel)\n      }\n\n      composeTestRule.onNodeWithTag(\"testCompose\").assertTextEquals(\"Compose test: $it\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(1)\n  }\n\n  @Test\n  fun a_compose_renderer_renders_content_on_screen_in_compose_ui_as_parent() {\n    // Notice that this test overrides the factory and uses Compose UI as entry point\n    // instead of Android Views as the other tests.\n    factory = ComposeAndroidRendererFactory.createForComposeUi(testApplication)\n\n    val composeModels = MutableStateFlow<ComposeModel?>(null)\n    composeTestRule.runOnUiThread {\n      activity.setContent {\n        val composeModel = composeModels.collectAsState().value\n        if (composeModel != null) {\n          factory.getComposeRenderer(composeModel).renderCompose(composeModel)\n        }\n      }\n    }\n\n    repeat(10) {\n      composeModels.value = ComposeModel(it)\n      composeTestRule.onNodeWithTag(\"testCompose\").assertTextEquals(\"Compose test: $it\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(1)\n  }\n\n  @Test\n  fun a_view_renderer_renders_content_on_screen() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val viewModel = ViewModel(it)\n        factory.getRenderer(viewModel).render(viewModel)\n        assertThat(activity.testView.text.toString()).isEqualTo(\"View test: $it\")\n      }\n    }\n\n    assertThat(createdRenderers).isEqualTo(1)\n  }\n\n  @Test\n  fun a_view_renderer_renders_content_on_screen_in_compose_ui_as_parent() {\n    // Notice that this test overrides the factory and uses Compose UI as entry point\n    // instead of Android Views as the other tests.\n    factory = ComposeAndroidRendererFactory.createForComposeUi(testApplication)\n\n    val viewModels = MutableStateFlow<ViewModel?>(null)\n    composeTestRule.runOnUiThread {\n      activity.setContent {\n        val viewModel = viewModels.collectAsState().value\n        if (viewModel != null) {\n          factory.getComposeRenderer(viewModel).renderCompose(viewModel)\n        }\n      }\n    }\n\n    repeat(10) {\n      viewModels.value = ViewModel(it)\n\n      waitForRenderPass()\n\n      assertThat(activity.testView.text.toString()).isEqualTo(\"View test: $it\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(1)\n  }\n\n  @Test\n  fun a_renderer_cannot_render_android_views_without_a_parent_view_when_compose_is_expected() {\n    // Notice that this test overrides the factory and uses Compose UI as entry point\n    // instead of Android Views as the other tests.\n    factory = ComposeAndroidRendererFactory.createForComposeUi(testApplication)\n\n    composeTestRule.runOnUiThread {\n      activity.setContent {\n        assertFailure {\n            val viewModel = ViewModel(1)\n            factory.getRenderer(viewModel).render(viewModel)\n          }\n          .messageContains(\n            \"Tried to call render() on an AndroidViewRenderer without a parent view.\"\n          )\n      }\n    }\n  }\n\n  @Test\n  fun a_view_renderer_can_embed_a_compose_renderer() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val viewModel = ViewModel(it, composeModel = ComposeModel(it + 1))\n\n        factory.getRenderer(viewModel).render(viewModel)\n        assertThat(activity.testView.text.toString()).isEqualTo(\"View test: $it\")\n      }\n\n      composeTestRule.onNodeWithTag(\"testCompose\").assertTextEquals(\"Compose test: ${it + 1}\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(2)\n  }\n\n  @Test\n  fun a_view_renderer_can_embed_a_view_renderer() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val viewModel = ViewModel(it, viewModel = ViewModel(it + 1))\n\n        factory.getRenderer(viewModel).render(viewModel)\n\n        val outerTextView =\n          activity.contentView.getChildViewGroupAt(0).getChildViewGroupAt(0).getChildAt(0)\n            as TextView\n\n        assertThat(outerTextView.text.toString()).isEqualTo(\"View test: $it\")\n\n        val innerTextView =\n          activity.contentView\n            .getChildViewGroupAt(0)\n            .getChildViewGroupAt(0)\n            .getChildViewGroupAt(1)\n            .getChildAt(0) as TextView\n\n        assertThat(innerTextView.text.toString()).isEqualTo(\"View test: ${it + 1}\")\n      }\n    }\n\n    assertThat(createdRenderers).isEqualTo(2)\n  }\n\n  @Test\n  fun a_compose_renderer_can_embed_a_view_renderer() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val composeModel = ComposeModel(it, viewModel = ViewModel(it + 1))\n        factory.getRenderer(composeModel).render(composeModel)\n      }\n\n      composeTestRule.onNodeWithTag(\"testCompose\").assertTextEquals(\"Compose test: $it\")\n      assertThat(activity.testView.text.toString()).isEqualTo(\"View test: ${it + 1}\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(2)\n  }\n\n  @Test\n  fun a_compose_renderer_can_embed_a_view_renderer_in_compose_ui_as_parent() {\n    // Notice that this test overrides the factory and uses Compose UI as entry point\n    // instead of Android Views as the other tests.\n    factory = ComposeAndroidRendererFactory.createForComposeUi(testApplication)\n\n    val composeModels = MutableStateFlow<ComposeModel?>(null)\n    composeTestRule.runOnUiThread {\n      activity.setContent {\n        val composeModel = composeModels.collectAsState().value\n        if (composeModel != null) {\n          factory.getComposeRenderer(composeModel).renderCompose(composeModel)\n        }\n      }\n    }\n\n    repeat(10) {\n      composeModels.value = ComposeModel(it, viewModel = ViewModel(it + 1))\n\n      composeTestRule.onNodeWithTag(\"testCompose\").assertTextEquals(\"Compose test: $it\")\n      assertThat(activity.testView.text.toString()).isEqualTo(\"View test: ${it + 1}\")\n    }\n\n    assertThat(createdRenderers).isEqualTo(2)\n  }\n\n  @Test\n  fun a_compose_renderer_can_embed_a_compose_renderer() {\n    repeat(10) {\n      composeTestRule.runOnUiThread {\n        val composeModel = ComposeModel(it, composeModel = ComposeModel(it + 1))\n        factory.getRenderer(composeModel).render(composeModel)\n      }\n\n      composeTestRule.onNodeWithText(\"Compose test: $it\").assertIsDisplayed()\n      composeTestRule\n        .onNodeWithText(\"Compose test: $it\")\n        .onSiblings()\n        .assertAny(hasText(\"Compose test: ${it + 1}\"))\n    }\n\n    assertThat(createdRenderers).isEqualTo(2)\n  }\n\n  @Test\n  fun compose_is_not_initialized_in_the_hierarchy_for_android_view_only() {\n    composeTestRule.runOnUiThread {\n      // Use two nested Android View renderers.\n      val viewModel = ViewModel(1, viewModel = ViewModel(2))\n      factory.getRenderer(viewModel).render(viewModel)\n\n      assertThat(activity.testView.text.toString()).isEqualTo(\"View test: 1\")\n    }\n\n    assertFailure { composeTestRule.onRoot().assertIsDisplayed() }\n      .messageContains(\"No compose hierarchies found in the app.\")\n  }\n\n  @Test\n  fun android_views_are_not_initialized_in_the_hierarchy_for_compose_ui_only() {\n    composeTestRule.runOnUiThread {\n      // Use two nested Compose UI renderers.\n      val composeModel = ComposeModel(1, composeModel = ComposeModel(2))\n      factory.getRenderer(composeModel).render(composeModel)\n    }\n\n    composeTestRule.onNodeWithText(\"Compose test: 1\").assertIsDisplayed()\n\n    // This is a ViewRenderer due to the wrapping in the factory.\n    //\n    // We use the inner renderer, because the parent Renderer is the Android Activity.\n    val innerRenderer = factory.getComposeRenderer(ComposeModel::class, rendererId = 1)\n    assertThat(innerRenderer).isInstanceOf<ViewRenderer<*>>()\n\n    // No view was initialized\n    val viewField = ViewRenderer::class.java.declaredFields.single { it.name == \"view\" }\n    viewField.isAccessible = true\n    assertThat(viewField.get(innerRenderer)).isNull()\n\n    // No coroutineScope was initialized was initialized\n    val coroutineScopeField =\n      ViewRenderer::class.java.declaredFields.single { it.name == \"coroutineScope\" }\n    coroutineScopeField.isAccessible = true\n    assertThat(coroutineScopeField.get(innerRenderer)).isNull()\n\n    // The activity field was set.\n    val activityField = ViewRenderer::class.java.declaredFields.single { it.name == \"activity\" }\n    activityField.isAccessible = true\n    assertThat(activityField.get(innerRenderer)).isSameInstanceAs(activity)\n  }\n\n  private val Activity.contentView: ViewGroup\n    get() = findViewById(android.R.id.content)\n\n  private val Activity.testView: TextView\n    get() = contentView.testView\n\n  private val View.testView: TextView\n    get() = findViewWithTag(\"testView\")\n\n  private fun ViewGroup.getChildViewGroupAt(index: Int): ViewGroup = getChildAt(index) as ViewGroup\n\n  private fun waitForRenderPass() {\n    // This ensures that rendering finished when Android Views are involved.\n    composeTestRule.onNodeWithTag(\"any\").assertDoesNotExist()\n  }\n\n  private data class ViewModel(\n    val value: Int,\n    val composeModel: ComposeModel? = null,\n    val viewModel: ViewModel? = null,\n  ) : BaseModel\n\n  private data class ComposeModel(\n    val value: Int,\n    val composeModel: ComposeModel? = null,\n    val viewModel: ViewModel? = null,\n  ) : BaseModel\n\n  private inner class TestViewRenderer(private val rendererFactory: RendererFactory) :\n    ViewRenderer<ViewModel>() {\n\n    private lateinit var container: ViewGroup\n    private lateinit var textView: TextView\n\n    init {\n      createdRenderers++\n    }\n\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: ViewModel,\n    ): View =\n      LinearLayout(activity).also { layout ->\n        container = layout\n        textView = TextView(activity).also { it.tag = \"testView\" }\n\n        layout.addView(textView)\n      }\n\n    override fun renderModel(model: ViewModel) {\n      textView.text = \"View test: ${model.value}\"\n\n      if (model.composeModel != null) {\n        rendererFactory.getRenderer(model.composeModel).render(model.composeModel)\n      }\n      if (model.viewModel != null) {\n        rendererFactory\n          .getRenderer(model.viewModel::class, container, rendererId = 1)\n          .render(model.viewModel)\n      }\n    }\n  }\n\n  private inner class TestComposeRenderer(private val rendererFactory: RendererFactory) :\n    ComposeRenderer<ComposeModel>() {\n\n    init {\n      createdRenderers++\n    }\n\n    @Composable\n    override fun Compose(model: ComposeModel) {\n      Column {\n        BasicText(text = \"Compose test: ${model.value}\", modifier = Modifier.testTag(\"testCompose\"))\n\n        if (model.viewModel != null) {\n          val renderer = rendererFactory.getComposeRenderer(model.viewModel)\n          renderer.renderCompose(model.viewModel)\n        }\n\n        if (model.composeModel != null) {\n          val renderer = rendererFactory.getComposeRenderer(model.composeModel, rendererId = 1)\n          renderer.renderCompose(model.composeModel)\n        }\n      }\n    }\n  }\n\n  private inner class TestRendererGraph(private val rendererFactory: () -> RendererFactory) :\n    RendererGraph {\n    override val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>> =\n      mapOf(\n        ViewModel::class to provider { TestViewRenderer(rendererFactory()) },\n        ComposeModel::class to provider { TestComposeRenderer(rendererFactory()) },\n      )\n    override val modelToRendererMapping: Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n      mapOf(\n        ViewModel::class to TestViewRenderer::class,\n        ComposeModel::class to TestComposeRenderer::class,\n      )\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/TestActivity.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.activity.ComponentActivity\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\n\nclass TestActivity : ComponentActivity()\n\n// Borrowed from AndroidComposeTestRule.\nfun <A : ComponentActivity> getActivityFromTestRule(rule: ActivityScenarioRule<A>): A {\n  var activity: A? = null\n  rule.scenario.onActivity { activity = it }\n\n  return with(activity) {\n    checkNotNull(this) { \"Activity was not set in the ActivityScenarioRule!\" }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/renderer/TestApplication.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Application\nimport androidx.test.platform.app.InstrumentationRegistry\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.Job\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\nclass TestApplication : Application(), RootScopeProvider {\n\n  var rendererGraph: RendererGraph? = null\n\n  override val rootScope: Scope = Scope.buildRootScope {\n    addMetroDependencyGraph(Graph())\n    addCoroutineScopeScoped(CoroutineScopeScoped(Job() + CoroutineName(\"test\")))\n  }\n\n  private inner class Graph : RendererGraph.Factory {\n    override fun createRendererGraph(factory: RendererFactory): RendererGraph =\n      requireNotNull(rendererGraph)\n  }\n}\n\nval testApplication: TestApplication\n  get() =\n    InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestApplication\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/AndroidViewWithinComposeRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.viewinterop.AndroidView\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * A renderer that allows you to embed a [BaseAndroidViewRenderer] within a [ComposeRenderer]. All\n * [renderCompose] calls are forwarded to the [androidRenderer]. The Android View hierarchy is\n * embedded within the Compose hierarchy.\n */\ninternal class AndroidViewWithinComposeRenderer<in ModelT : BaseModel>(\n  private val androidRenderer: BaseAndroidViewRenderer<ModelT>\n) : BaseAndroidViewRenderer<ModelT>, BaseComposeRenderer<ModelT> {\n\n  private var composeContainer: ViewGroup? = null\n\n  override fun init(activity: Activity, parent: ViewGroup) {\n    // Only use composeContainer when it's needed and initialize it lazily. This is needed\n    // to embed one ViewRenderer in another where Compose UI is not involved. Then we always\n    // need to use the parent passed in here. But once Compose is really used, then use the\n    // container from the Compose view and not whatever is passed in here.\n    androidRenderer.init(activity, composeContainer ?: parent)\n  }\n\n  override fun render(model: ModelT) {\n    try {\n      androidRenderer.render(model)\n    } catch (e: UninitializedPropertyAccessException) {\n      throw IllegalStateException(\n        \"Tried to call render() on an AndroidViewRenderer without a parent view. This usually \" +\n          \"only happens when an AndroidViewRenderer is embedded in Compose UI. Call \" +\n          \"renderCompose() instead.\",\n        e,\n      )\n    }\n  }\n\n  @Suppress(\"ComposableNaming\")\n  @Composable\n  override fun renderCompose(model: ModelT) {\n    AndroidView(\n      factory = { context ->\n        // Create a FrameLayout as parent. It's technically not needed, but the\n        // API from BaseAndroidViewRenderer requires a parent.\n        FrameLayout(context).also {\n          composeContainer = it\n          init(context as Activity, it)\n        }\n      }\n    ) {\n      render(model)\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/BaseComposeWithinAndroidViewRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.ui.platform.ComposeView\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * A renderer that allows you to embed Compose UI within an Android View hierarchy. All [render]\n * calls are forwarded to the [Compose] function using a flow.\n *\n * This class is abstract to allow for different Compose UI implementations, but it's not exposed to\n * consumers yet.\n */\ninternal abstract class BaseComposeWithinAndroidViewRenderer<in ModelT : BaseModel> :\n  ViewRenderer<ModelT>(), BaseComposeRenderer<ModelT> {\n\n  private val models = MutableStateFlow<ModelT?>(null)\n\n  final override fun inflate(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n    initialModel: ModelT,\n  ): View =\n    ComposeView(activity).apply {\n      setContent {\n        val model = models.collectAsState().value\n        if (model != null) {\n          // Render the new model using Compose.\n          renderCompose(model)\n        }\n      }\n    }\n\n  // Make these functions final. Concrete renderers should only operate in the Compose world.\n  final override fun onDetach() {\n    // Clear any references to the last model or presenter.\n    models.value = null\n  }\n\n  @Suppress(\"ComposableNaming\")\n  @Composable\n  final override fun renderCompose(model: ModelT) {\n    Compose(model)\n  }\n\n  final override fun renderModel(model: ModelT): Nothing = throw NotImplementedError()\n\n  final override fun renderModel(model: ModelT, lastModel: ModelT?) {\n    models.value = model\n  }\n\n  /** Render the given [model] on screen using Compose UI. */\n  @Composable protected abstract fun Compose(model: ModelT)\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ComposeAndroidRendererFactory.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.ViewGroup\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * [RendererFactory] that is able to create [ComposeRenderer] and [ViewRenderer] instances for\n * Android, unlike [ComposeRendererFactory] which only handles [ComposeRenderer] and unlike\n * [AndroidRendererFactory] which only handles [ViewRenderer]. Further, this implementation provides\n * adapters for renderers for a seamless interop between Compose UI and Android Views.\n *\n * Call either [createForComposeUi] or [createForAndroidViews] to create a new instance.\n */\npublic sealed interface ComposeAndroidRendererFactory : RendererFactory {\n\n  private class ComposeAndroidRendererFactoryComposeUi(rootScopeProvider: RootScopeProvider) :\n    BaseRendererFactory(rootScopeProvider), ComposeAndroidRendererFactory {\n    override fun <T : BaseModel> createRenderer(modelType: KClass<out T>): Renderer<T> {\n      return wrapRenderer(super.createRenderer(modelType), modelType)\n    }\n  }\n\n  private class ComposeAndroidRendererFactoryAndroidView(\n    rootScopeProvider: RootScopeProvider,\n    activity: Activity,\n    parent: ViewGroup,\n  ) : AndroidRendererFactory(rootScopeProvider, activity, parent), ComposeAndroidRendererFactory {\n    override fun <T : BaseModel> createRenderer(modelType: KClass<out T>): Renderer<T> {\n      return wrapRenderer(super.createRenderer(modelType), modelType)\n    }\n  }\n\n  public companion object {\n    /**\n     * Creates a new [RendererFactory] that implements interop for Compose UI and Android Views.\n     *\n     * Use this function only when you know that `Renderers` returned by this factory are instances\n     * of [ComposeRenderer]. This avoids unnecessary wrapping of root `Renderers` and allows you to\n     * embed them into a Compose UI hierarchy. Unlike [createForAndroidViews] it's not necessary to\n     * provide a parent view.\n     *\n     * This constraint only applies to root `Renderers`. Child `Renderers` can still be instances of\n     * [ViewRenderer] and are wrapped for interop when necessary.\n     *\n     * A typical pattern looks like this:\n     * ```\n     * override fun onCreate(savedInstanceState: Bundle?) {\n     *     super.onCreate(savedInstanceState)\n     *\n     *     val rendererFactory =\n     *         ComposeAndroidRendererFactory.createForComposeUi(rootScopeProvider = ...)\n     *\n     *     setContent {\n     *         val model by models.collectAsState()\n     *\n     *         val renderer = rendererFactory.getComposeRenderer(model)\n     *         renderer.renderCompose(model)\n     *     }\n     * }\n     * ```\n     */\n    public fun createForComposeUi(\n      rootScopeProvider: RootScopeProvider\n    ): ComposeAndroidRendererFactory = ComposeAndroidRendererFactoryComposeUi(rootScopeProvider)\n\n    /**\n     * Creates a new [RendererFactory] that implements interop for Compose UI and Android Views.\n     *\n     * Use this function when `Renderers` returned by this factory are instances of\n     * [ComposeRenderer] or [ViewRenderer]. `Renderers` are wrapped for interop when necessary and\n     * embedded as children in [parent].\n     *\n     * A typical pattern looks like this:\n     * ```\n     * override fun onCreate(savedInstanceState: Bundle?) {\n     *     super.onCreate(savedInstanceState)\n     *     setContentView(R.layout.activity_main)\n     *\n     *     val rendererFactory =\n     *         ComposeAndroidRendererFactory(\n     *             rootScopeProvider = ...,\n     *             activity = this,\n     *             parent = findViewById(R.id.main_container),\n     *         )\n     *\n     *     lifecycleScope.launch {\n     *         repeatOnLifecycle(Lifecycle.State.STARTED) {\n     *             models.collect { model ->\n     *                val renderer = rendererFactory.getRenderer(model)\n     *                renderer.render(model)\n     *             }\n     *         }\n     *     }\n     * }\n     * ```\n     */\n    public fun createForAndroidViews(\n      rootScopeProvider: RootScopeProvider,\n      activity: Activity,\n      parent: ViewGroup,\n    ): ComposeAndroidRendererFactory =\n      ComposeAndroidRendererFactoryAndroidView(rootScopeProvider, activity, parent)\n\n    private fun <T : BaseModel> wrapRenderer(\n      renderer: Renderer<T>,\n      modelType: KClass<out T>,\n    ): Renderer<T> {\n      check(renderer !is ComposeWithinAndroidViewRenderer) {\n        \"Trying to wrap a render that has been wrapped already for model $modelType.\"\n      }\n\n      check(renderer !is AndroidViewWithinComposeRenderer) {\n        \"Trying to wrap a render that has been wrapped already for model $modelType.\"\n      }\n\n      return when (renderer) {\n        // Wrap a ComposeRenderer to support embedding it in an Android View hierarchy.\n        is BaseComposeRenderer<*> ->\n          @Suppress(\"UNCHECKED_CAST\")\n          ComposeWithinAndroidViewRenderer(renderer as BaseComposeRenderer<T>)\n\n        // Wrap a ViewRenderer to support embedding it in a Compose UI hierarchy.\n        is BaseAndroidViewRenderer -> AndroidViewWithinComposeRenderer(renderer)\n\n        // Should not happen, each original Renderer is wrapped.\n        else -> error(\"Unsupported renderer type ${renderer::class} for model $modelType.\")\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidMain/kotlin/software/amazon/app/platform/renderer/ComposeWithinAndroidViewRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.compose.runtime.Composable\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * A renderer that allows you to embed a [BaseComposeRenderer] within a [ViewRenderer]. All [render]\n * calls are forwarded to the [composeRenderer]. The Compose UI hierarchy is embedded within the\n * Android View hierarchy.\n */\ninternal class ComposeWithinAndroidViewRenderer<in ModelT : BaseModel>(\n  private val composeRenderer: BaseComposeRenderer<ModelT>\n) : BaseComposeWithinAndroidViewRenderer<ModelT>() {\n  @Composable\n  override fun Compose(model: ModelT) {\n    composeRenderer.renderCompose(model)\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/androidUnitTest/kotlin/software/amazon/app/platform/renderer/ComposeAndroidRendererFactoryTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.compose.runtime.Composable\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport assertk.assertions.messageContains\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlin.test.Test\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.buildTestScope\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\nclass ComposeAndroidRendererFactoryTest {\n\n  @Test\n  fun `a ComposeRenderer is wrapped for Android View support`() = runTest {\n    val factory = factory()\n    assertThat(factory.getRenderer(ComposeModel::class))\n      .isInstanceOf<ComposeWithinAndroidViewRenderer<ComposeModel>>()\n  }\n\n  @Test\n  fun `a ViewRenderer is wrapped for Compose support`() = runTest {\n    val factory = factory()\n    assertThat(factory.getRenderer(AndroidModel::class))\n      .isInstanceOf<AndroidViewWithinComposeRenderer<AndroidModel>>()\n  }\n\n  @Test\n  fun `an unsupported renderer cannot be wrapped`() = runTest {\n    val factory = factory()\n    assertFailure { factory.getRenderer(UnsupportedModel::class) }\n      .isInstanceOf<IllegalStateException>()\n      .messageContains(\n        \"Unsupported renderer type class software.amazon.app.platform.\" +\n          \"renderer.ComposeAndroidRendererFactoryTest\\$UnsupportedRenderer\"\n      )\n  }\n\n  private fun TestScope.factory(): ComposeAndroidRendererFactory {\n    return ComposeAndroidRendererFactory.createForComposeUi(rootScopeProvider())\n  }\n\n  private fun TestScope.rootScopeProvider(): RootScopeProvider {\n    val scope =\n      Scope.buildTestScope(this) {\n        addMetroDependencyGraph(\n          object : RendererGraph.Factory {\n            override fun createRendererGraph(factory: RendererFactory): RendererGraph {\n              return object : RendererGraph {\n                override val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>> =\n                  mapOf(\n                    ComposeModel::class to provider { TestComposeRenderer() },\n                    AndroidModel::class to provider { TestAndroidRenderer() },\n                    UnsupportedModel::class to provider { UnsupportedRenderer() },\n                  )\n                override val modelToRendererMapping:\n                  Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n                  mapOf(\n                    ComposeModel::class to TestComposeRenderer::class,\n                    AndroidModel::class to TestAndroidRenderer::class,\n                    UnsupportedModel::class to UnsupportedRenderer::class,\n                  )\n              }\n            }\n          }\n        )\n      }\n    return object : RootScopeProvider {\n      override val rootScope: Scope = scope\n    }\n  }\n\n  private class ComposeModel : BaseModel\n\n  private class AndroidModel : BaseModel\n\n  private class UnsupportedModel : BaseModel\n\n  private class TestComposeRenderer : ComposeRenderer<ComposeModel>() {\n    @Composable override fun Compose(model: ComposeModel) = Unit\n  }\n\n  private class TestAndroidRenderer : ViewRenderer<AndroidModel>() {\n    override fun inflate(\n      activity: Activity,\n      parent: ViewGroup,\n      layoutInflater: LayoutInflater,\n      initialModel: AndroidModel,\n    ): View = throw NotImplementedError()\n  }\n\n  private class UnsupportedRenderer : Renderer<UnsupportedModel> {\n    override fun render(model: UnsupportedModel) = throw NotImplementedError()\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/presenter/molecule/backgesture/BackGestureDispatcherPresenterCompose.kt",
    "content": "package software.amazon.app.platform.presenter.molecule.backgesture\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.snapshotFlow\nimport androidx.navigationevent.NavigationEventInfo\nimport androidx.navigationevent.NavigationEventTransitionState\nimport androidx.navigationevent.compose.NavigationBackHandler\nimport androidx.navigationevent.compose.rememberNavigationEventState\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.consumeAsFlow\nimport kotlinx.coroutines.launch\nimport software.amazon.app.platform.renderer.ComposeRenderer\n\n/**\n * Registers a callback in the `BackGestureDispatcher` that is enabled as long as there is a\n * presenter with an enabled back handler.\n *\n * It's recommended to call this function from your root [ComposeRenderer], e.g.\n *\n * ```\n * @Inject\n * @ContributesRenderer\n * class RootPresenterRenderer(\n *   private val rendererFactory: RendererFactory,\n *   private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n * ) : ComposeRenderer<Model>() {\n *\n *   @Composable\n *   override fun Compose(model: Model) {\n *     backGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()\n *\n *     ...\n *   }\n * ```\n */\n@Composable\npublic fun BackGestureDispatcherPresenter.ForwardBackPressEventsToPresenters() {\n  val count by listenersCount.collectAsState()\n  val navState = rememberNavigationEventState(NavigationEventInfo.None)\n  var activeGestureChannel by remember { mutableStateOf<Channel<BackEventPresenter>?>(null) }\n  val scope = rememberCoroutineScope()\n\n  fun ensureGestureSession(): Channel<BackEventPresenter> {\n    activeGestureChannel?.let {\n      return it\n    }\n    val channel = Channel<BackEventPresenter>(Channel.BUFFERED)\n    activeGestureChannel = channel\n    scope.launch(start = CoroutineStart.UNDISPATCHED) { onPredictiveBack(channel.consumeAsFlow()) }\n    return channel\n  }\n\n  NavigationBackHandler(\n    state = navState,\n    isBackEnabled = count > 0,\n    onBackCompleted = {\n      ensureGestureSession().close()\n      activeGestureChannel = null\n    },\n    onBackCancelled = {\n      activeGestureChannel?.cancel(CancellationException(\"Back gesture cancelled\"))\n      activeGestureChannel = null\n    },\n  )\n\n  // Observe transition state and forward progress events\n  LaunchedEffect(Unit) {\n    snapshotFlow { navState.transitionState }\n      .collect { transitionState ->\n        if (transitionState is NavigationEventTransitionState.InProgress) {\n          val channel = ensureGestureSession()\n\n          val event = transitionState.latestEvent\n          channel.trySend(\n            BackEventPresenter(\n              touchX = event.touchX,\n              touchY = event.touchY,\n              progress = event.progress,\n              swipeEdge = event.swipeEdge,\n            )\n          )\n        }\n      }\n  }\n\n  DisposableEffect(Unit) {\n    onDispose {\n      activeGestureChannel?.cancel(CancellationException(\"Disposed\"))\n      activeGestureChannel = null\n    }\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/BaseComposeRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.compose.runtime.Composable\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * The base interface for Compose UI enabled renderers. This interface is similar to [Renderer], but\n * does not extend the [Renderer] interface itself. It has its own [renderCompose] function in order\n * to preserve the Compose UI context.\n *\n * This interface will rarely be used directly and a more specific renderer implementation for a\n * concrete platform such as [ComposeRenderer] should be favored unless there is a specific need.\n *\n * For more information see [Renderer].\n */\n// For future reviews:\n//\n// This interface was named \"BaseComposeRenderer\", because most consumers will\n// import \"ComposeRenderer\" directly and this class name is more user friendly than something\n// along the lines of \"RealComposeRenderer\".\n//\n// This separate base interface is needed for several implementations that cannot extend the\n// abstract ComposeRenderer class. It also helps to distinguish between the normal Renderer API\n// vs this BaseComposeRenderer API. Note that BaseComposeRenderer is not extending the Renderer\n// interface. This distinction is similar to Presenter and MoleculePresenter.\npublic interface BaseComposeRenderer<in ModelT : BaseModel> {\n\n  /** Render the given [model] on screen using Compose UI. */\n  // Android Lint will complain that this function should start with an uppercase letter, but\n  // the name \"renderCompose\" was chosen to align it with the \"render\" function from the\n  // Renderer interface. Implementations will usually have a separate \"Compose()\" function, see\n  // ComposeRenderer for example.\n  @Composable public fun renderCompose(model: ModelT)\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/ComposeRenderer.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.compose.runtime.Composable\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * An implementation of [Renderer] that is specific to Compose UI Multiplatform.\n *\n * Your custom renderers should extend this abstract class and provide implementations for\n * [Compose]. This function gets called each time a new [BaseModel] is available and the UI elements\n * should be updated, e.g.\n *\n * ```\n * @ContributesRenderer\n * class MyRenderer : ComposeRenderer<MyModel>() {\n *     @Composable\n *     override fun Compose(model: MyModel) {\n *         Text(\n *             text = \"MyModel value: ${model.value}\",\n *         )\n *     }\n * }\n * ```\n *\n * It's strongly recommended to follow Compose UI best practices, e.g. state should be retained\n * within the [Compose] function between updates and not as a field of the [ComposeRenderer], e.g.\n *\n * ```\n * // Do this\n * @Composable\n * override fun Compose(model: MyModel) {\n *     var string by remember { mutableStateOf(..) }\n * }\n *\n * // DO NOT DO THIS\n * private var string: String? = null\n *\n * @Composable\n * override fun Compose(model: MyModel) {\n *     string = ...\n * }\n * ```\n *\n * While [ComposeRenderer] implements the [Renderer] interface for seamless integration in the whole\n * stack, the [Renderer.render] function is not supported and throws an error. The function itself\n * is deprecated and hidden. Instead, [renderCompose] should be called, which preserves the Compose\n * UI context.\n */\npublic abstract class ComposeRenderer<in ModelT : BaseModel> :\n  BaseComposeRenderer<ModelT>, Renderer<ModelT> {\n\n  @Deprecated(\n    message = \"ComposeRenderers must invoke renderCompose(model)\",\n    level = DeprecationLevel.HIDDEN,\n  )\n  final override fun render(model: ModelT): Nothing {\n    error(\"ComposeRenderers must invoke renderCompose(model).\")\n  }\n\n  @Composable\n  final override fun renderCompose(model: ModelT) {\n    // This function seems redundant and implementations could implement it instead of the\n    // separate Compose() function. However, it will allow us to intercept rendering calls\n    // in the platform for future use cases. Compare this with ViewRenderer.render() and\n    // ViewRenderer.renderModel().\n    Compose(model)\n  }\n\n  /** Render the given [model] on screen using Compose UI. */\n  @Suppress(\"FunctionName\") @Composable protected abstract fun Compose(model: ModelT)\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/renderer/ComposeRendererFactory.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * A [RendererFactory] specific to [ComposeRenderer]s. It can only handle [ComposeRenderer]\n * implementations, because they're integrated differently into each runtime.\n */\npublic class ComposeRendererFactory(rootScopeProvider: RootScopeProvider) :\n  BaseRendererFactory(rootScopeProvider) {\n\n  override fun <T : BaseModel> createRenderer(modelType: KClass<out T>): ComposeRenderer<T> {\n    return super.createRenderer(modelType).asComposeRenderer(modelType)\n  }\n\n  override fun <T : BaseModel> getRenderer(\n    modelType: KClass<out T>,\n    rendererId: Int,\n  ): ComposeRenderer<T> {\n    return super.getRenderer(modelType, rendererId).asComposeRenderer(modelType)\n  }\n\n  private fun <T : BaseModel> Renderer<T>.asComposeRenderer(\n    modelType: KClass<out T>\n  ): ComposeRenderer<T> {\n    check(this is ComposeRenderer<T>) {\n      \"Expected a ComposeRenderer for model type $modelType, \" +\n        \"but found $this of type ${this::class}.\"\n    }\n    return this\n  }\n}\n\n/**\n * Convenience function to create [BaseComposeRenderer] for the given [modelType]. This is helpful\n * to call from another [BaseComposeRenderer] to embed a child renderer.\n */\npublic fun <T : BaseModel> RendererFactory.createComposeRenderer(\n  modelType: KClass<out T>\n): BaseComposeRenderer<T> = createRenderer(modelType).asBaseComposeRenderer()\n\n/**\n * Convenience function to create [BaseComposeRenderer] for the given [model]. This is helpful to\n * call from another [BaseComposeRenderer] to embed a child renderer.\n */\npublic fun <T : BaseModel> RendererFactory.createComposeRenderer(model: T): BaseComposeRenderer<T> =\n  createComposeRenderer(model::class)\n\n/**\n * Convenience function to get [BaseComposeRenderer] for the given [modelType]. This is helpful to\n * call from another [BaseComposeRenderer] to embed a child renderer.\n *\n * [rendererId] allows you to change the cache key and cache multiple renderers for the same\n * [BaseModel] type.\n */\npublic fun <T : BaseModel> RendererFactory.getComposeRenderer(\n  modelType: KClass<out T>,\n  rendererId: Int = 0,\n): BaseComposeRenderer<T> = getRenderer(modelType, rendererId).asBaseComposeRenderer()\n\n/**\n * Convenience function to get [BaseComposeRenderer] for the given [model]. This is helpful to call\n * from another [BaseComposeRenderer] to embed a child renderer.\n *\n * [rendererId] allows you to change the cache key and cache multiple renderers for the same\n * [BaseModel] type.\n */\npublic fun <T : BaseModel> RendererFactory.getComposeRenderer(\n  model: T,\n  rendererId: Int = 0,\n): BaseComposeRenderer<T> = getComposeRenderer(model::class, rendererId)\n\nprivate fun <T : BaseModel> Renderer<T>.asBaseComposeRenderer(): BaseComposeRenderer<T> {\n  check(this is BaseComposeRenderer<*>) {\n    \"The renderer ${this::class} is not an instance of BaseComposeRenderer. \" +\n      \"For Android View and Compose UI interop use ComposeAndroidRendererFactory.\"\n  }\n\n  @Suppress(\"UNCHECKED_CAST\")\n  return this as BaseComposeRenderer<T>\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/desktopTest/kotlin/software/amazon/app/platform/renderer/ComposeRendererFactoryTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.compose.runtime.Composable\nimport assertk.all\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport assertk.assertions.messageContains\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlin.test.Test\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.buildTestScope\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\nclass ComposeRendererFactoryTest {\n\n  @Test\n  fun `a ComposeRendererFactory can create a ComposeRenderer`() = runTest {\n    val factory = ComposeRendererFactory(rootScopeProvider())\n    assertThat(factory.getRenderer(ComposeModel::class)).isInstanceOf<TestComposeRenderer>()\n  }\n\n  @Test\n  fun `a ComposeRendererFactory throws an error for other renderer types`() = runTest {\n    val factory = ComposeRendererFactory(rootScopeProvider())\n\n    assertFailure { factory.getRenderer(AndroidModel::class) }\n      .isInstanceOf<IllegalStateException>()\n      .messageContains(\"Expected a ComposeRenderer for model type\")\n  }\n\n  @Test\n  fun `getComposeRenderer() can create a ComposeRenderer`() = runTest {\n    val factory = ComposeRendererFactory(rootScopeProvider())\n    assertThat(factory.getComposeRenderer(ComposeModel::class)).isInstanceOf<TestComposeRenderer>()\n  }\n\n  @Test\n  fun `getComposeRenderer() throws an error for other renderer types`() = runTest {\n    val factory = BaseRendererFactory(rootScopeProvider())\n\n    assertFailure { factory.getComposeRenderer(AndroidModel::class) }\n      .all {\n        isInstanceOf<IllegalStateException>()\n\n        messageContains(\n          \"The renderer class software.amazon.app.platform.renderer.\" +\n            \"ComposeRendererFactoryTest\\$AndroidRenderer\"\n        )\n        messageContains(\n          \"For Android View and Compose UI interop use ComposeAndroidRendererFactory.\"\n        )\n      }\n  }\n\n  private fun TestScope.rootScopeProvider(): RootScopeProvider {\n    val scope =\n      Scope.buildTestScope(this) {\n        addMetroDependencyGraph(\n          object : RendererGraph.Factory {\n            override fun createRendererGraph(factory: RendererFactory): RendererGraph {\n              return object : RendererGraph {\n                override val renderers: Map<KClass<out BaseModel>, Provider<Renderer<*>>> =\n                  mapOf(\n                    ComposeModel::class to provider { TestComposeRenderer() },\n                    AndroidModel::class to provider { AndroidRenderer() },\n                  )\n                override val modelToRendererMapping:\n                  Map<KClass<out BaseModel>, KClass<out Renderer<*>>> =\n                  mapOf(\n                    ComposeModel::class to TestComposeRenderer::class,\n                    AndroidModel::class to AndroidRenderer::class,\n                  )\n              }\n            }\n          }\n        )\n      }\n    return object : RootScopeProvider {\n      override val rootScope: Scope = scope\n    }\n  }\n\n  private class ComposeModel : BaseModel\n\n  private class AndroidModel : BaseModel\n\n  private class TestComposeRenderer : ComposeRenderer<ComposeModel>() {\n    @Composable override fun Compose(model: ComposeModel) = Unit\n  }\n\n  private class AndroidRenderer : Renderer<AndroidModel> {\n    override fun render(model: AndroidModel) = Unit\n  }\n}\n"
  },
  {
    "path": "renderer-compose-multiplatform/public/src/desktopTest/kotlin/software/amazon/app/platform/renderer/ComposeRendererTest.kt",
    "content": "package software.amazon.app.platform.renderer\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.material.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.test.ExperimentalTestApi\nimport androidx.compose.ui.test.assertTextEquals\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.runComposeUiTest\nimport kotlin.test.Test\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport software.amazon.app.platform.presenter.BaseModel\n\n@Suppress(\"MagicNumber\")\n@ExperimentalTestApi\nclass ComposeRendererTest {\n\n  @Test\n  fun `a model is rendered with Compose UI elements`() {\n    runComposeUiTest {\n      val renderer = TestRenderer()\n      val models = MutableStateFlow(Model(1))\n\n      setContent {\n        val model by models.collectAsState()\n        renderer.renderCompose(model)\n      }\n\n      onNodeWithTag(\"text\").assertTextEquals(\"Argument 1\")\n\n      models.value = Model(2)\n      onNodeWithTag(\"text\").assertTextEquals(\"Argument 2\")\n\n      models.value = Model(3)\n      onNodeWithTag(\"text\").assertTextEquals(\"Argument 3\")\n    }\n  }\n\n  @Test\n  fun `a ComposeRenderer can nest other ComposeRenderers`() {\n    runComposeUiTest {\n      val renderer = OuterRenderer(TestRenderer())\n      val models = MutableStateFlow(Model(1))\n\n      setContent {\n        val model by models.collectAsState()\n        renderer.renderCompose(model)\n      }\n\n      onNodeWithTag(\"text\").assertTextEquals(\"Argument 1\")\n      onNodeWithTag(\"text-outer\").assertTextEquals(\"Outer 1\")\n\n      models.value = Model(2)\n      onNodeWithTag(\"text\").assertTextEquals(\"Argument 2\")\n      onNodeWithTag(\"text-outer\").assertTextEquals(\"Outer 2\")\n    }\n  }\n\n  private data class Model(val value: Int) : BaseModel\n\n  private class TestRenderer : ComposeRenderer<Model>() {\n    @Composable\n    override fun Compose(model: Model) {\n      Text(text = \"Argument ${model.value}\", modifier = Modifier.testTag(\"text\"))\n    }\n  }\n\n  private class OuterRenderer(private val testRenderer: TestRenderer) : ComposeRenderer<Model>() {\n    @Composable\n    override fun Compose(model: Model) {\n      Column {\n        Text(text = \"Outer ${model.value}\", modifier = Modifier.testTag(\"text-outer\"))\n        testRenderer.renderCompose(model)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "robot/public/api/android/public.api",
    "content": "public abstract class software/amazon/app/platform/robot/AndroidViewRobot : software/amazon/app/platform/robot/Robot {\n\tpublic fun <init> ()V\n\tpublic fun close ()V\n\tprotected fun getRootMatcher ()Lorg/hamcrest/Matcher;\n\tprotected final fun onView (Lorg/hamcrest/Matcher;)Landroidx/test/espresso/ViewInteraction;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/AndroidViewRobot$Component {\n\tpublic abstract fun getRootMatcherProvider ()Lsoftware/amazon/app/platform/robot/RootMatcherProvider;\n}\n\npublic final class software/amazon/app/platform/robot/DefaultRootMatcherProvider : software/amazon/app/platform/robot/RootMatcherProvider {\n\tpublic fun <init> ()V\n\tpublic fun getRootMatcher ()Lorg/hamcrest/Matcher;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/Robot {\n\tpublic fun close ()V\n}\n\npublic final class software/amazon/app/platform/robot/Robot$DefaultImpls {\n\tpublic static fun close (Lsoftware/amazon/app/platform/robot/Robot;)V\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotComponent {\n\tpublic abstract fun getRobots ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotGraph {\n\tpublic abstract fun getRobots ()Ljava/util/Map;\n}\n\npublic abstract class software/amazon/app/platform/robot/RobotGraph$BindsMirror {\n\tpublic abstract fun robots4205200196 ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotGraph$MetroContributionToAppScope : software/amazon/app/platform/robot/RobotGraph {\n}\n\npublic final class software/amazon/app/platform/robot/RobotKt {\n\tpublic static final fun getAllRobots (Lsoftware/amazon/app/platform/scope/Scope;)Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RootMatcherProvider {\n\tpublic abstract fun getRootMatcher ()Lorg/hamcrest/Matcher;\n}\n\npublic final class software/amazon/app/platform/robot/WaiterKt {\n\tpublic static final fun waitFor-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)Ljava/lang/Object;\n\tpublic static synthetic fun waitFor-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun waitUntil-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)V\n\tpublic static synthetic fun waitUntil-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V\n\tpublic static final fun waitUntilCatching-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)V\n\tpublic static synthetic fun waitUntilCatching-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V\n}\n\npublic final class software/amazon/app/platform/robot/internal/RobotInternals {\n\tpublic static final field INSTANCE Lsoftware/amazon/app/platform/robot/internal/RobotInternals;\n\tpublic final fun setRootScopeProvider (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n}\n\n"
  },
  {
    "path": "robot/public/api/desktop/public.api",
    "content": "public abstract interface class software/amazon/app/platform/robot/Robot {\n\tpublic fun close ()V\n}\n\npublic final class software/amazon/app/platform/robot/Robot$DefaultImpls {\n\tpublic static fun close (Lsoftware/amazon/app/platform/robot/Robot;)V\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotComponent {\n\tpublic abstract fun getRobots ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotGraph {\n\tpublic abstract fun getRobots ()Ljava/util/Map;\n}\n\npublic abstract class software/amazon/app/platform/robot/RobotGraph$BindsMirror {\n\tpublic abstract fun robots4205200196 ()Ljava/util/Map;\n}\n\npublic abstract interface class software/amazon/app/platform/robot/RobotGraph$MetroContributionToAppScope : software/amazon/app/platform/robot/RobotGraph {\n}\n\npublic final class software/amazon/app/platform/robot/RobotKt {\n\tpublic static final fun getAllRobots (Lsoftware/amazon/app/platform/scope/Scope;)Ljava/util/Map;\n}\n\npublic final class software/amazon/app/platform/robot/WaiterKt {\n\tpublic static final fun waitFor-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)Ljava/lang/Object;\n\tpublic static synthetic fun waitFor-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Ljava/lang/Object;\n\tpublic static final fun waitUntil-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)V\n\tpublic static synthetic fun waitUntil-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V\n\tpublic static final fun waitUntilCatching-vLdBGDU (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;)V\n\tpublic static synthetic fun waitUntilCatching-vLdBGDU$default (Ljava/lang/String;JJLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V\n}\n\npublic final class software/amazon/app/platform/robot/internal/RobotInternals {\n\tpublic static final field INSTANCE Lsoftware/amazon/app/platform/robot/internal/RobotInternals;\n\tpublic final fun setRootScopeProvider (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n}\n\n"
  },
  {
    "path": "robot/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableKotlinInject true\n    enableMetro true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n    commonMainImplementation project(':robot-internal:public')\n\n    androidMainApi libs.androidx.test.espresso\n\n    commonTestImplementation project(':internal:testing')\n    commonTestImplementation project(':scope:testing')\n}\n"
  },
  {
    "path": "robot/public/src/androidMain/kotlin/software/amazon/app/platform/robot/AndroidViewRobot.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport android.view.View\nimport androidx.test.espresso.Espresso\nimport androidx.test.espresso.Root\nimport androidx.test.espresso.ViewInteraction\nimport org.hamcrest.Matcher\nimport software.amazon.app.platform.robot.internal.rootScope\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/**\n * A [Robot] specific to interacting with the Android View system.\n *\n * [rootMatcher] allows you to change the root for Espresso assertions, e.g. this is needed when\n * interacting with other windows such as dialogs or in a dual screen setup to change the display\n * for particular robots and make assertions on the second display. By default, Espresso's default\n * root matcher is used.\n *\n * Note that the root matcher needs to be set manually using [ViewInteraction.inRoot]. [onView] is a\n * convenience function that sets the root matcher automatically. It's strongly recommended to use\n * this function instead of [Espresso.onView].\n */\npublic abstract class AndroidViewRobot : Robot {\n\n  /**\n   * The root matcher for this particular robot. It allows you to change the root for Espresso\n   * assertions, e.g. this is needed for other windows such as dialogs.\n   */\n  protected open val rootMatcher: Matcher<Root>\n    get() = rootScope.kotlinInjectComponent<Component>().rootMatcherProvider.rootMatcher\n\n  /**\n   * Convenience function that automatically sets [rootMatcher] as root for the assertion. It's\n   * strongly recommended to use this function instead of [Espresso.onView].\n   */\n  protected fun onView(viewMatcher: Matcher<View>): ViewInteraction {\n    return Espresso.onView(viewMatcher).inRoot(rootMatcher)\n  }\n\n  /** Provides objects from the kotlin-inject object graph. */\n  @ContributesTo(AppScope::class)\n  public interface Component {\n    /** The implementation that provides the root matcher. */\n    public val rootMatcherProvider: RootMatcherProvider\n  }\n}\n"
  },
  {
    "path": "robot/public/src/androidMain/kotlin/software/amazon/app/platform/robot/DefaultRootMatcherProvider.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport androidx.test.espresso.Root\nimport androidx.test.espresso.matcher.RootMatchers\nimport me.tatarka.inject.annotations.Inject\nimport org.hamcrest.Matcher\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesBinding\nimport software.amazon.lastmile.kotlin.inject.anvil.SingleIn\n\n/** Implementation of [RootMatcherProvider] that provides Espresso's default root matcher. */\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class)\npublic class DefaultRootMatcherProvider : RootMatcherProvider {\n  override val rootMatcher: Matcher<Root>\n    get() = RootMatchers.DEFAULT\n}\n"
  },
  {
    "path": "robot/public/src/androidMain/kotlin/software/amazon/app/platform/robot/RootMatcherProvider.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport androidx.test.espresso.Root\nimport org.hamcrest.Matcher\n\n/**\n * API that provides the default root matcher for Espresso assertions for all [AndroidViewRobot]s.\n * Individual [AndroidViewRobot]s can override the root matcher for their assertions.\n */\npublic interface RootMatcherProvider {\n  /** The default root matcher for all Espresso assertions. */\n  public val rootMatcher: Matcher<Root>\n}\n"
  },
  {
    "path": "robot/public/src/androidUnitTest/kotlin/software/amazon/app/platform/robot/WaiterTest.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport assertk.all\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.cause\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isGreaterThan\nimport assertk.assertions.isNotNull\nimport assertk.assertions.messageContains\nimport kotlin.test.Test\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlin.time.Duration.Companion.seconds\n\nclass WaiterTest {\n\n  @Test\n  fun `waitUntil blocks until the condition is met`() {\n    val currentTime = System.currentTimeMillis()\n\n    var counter = 0\n    waitUntil(condition = \"Wait for test condition\", delay = 100.milliseconds) {\n      counter++\n      counter == 5\n    }\n\n    assertThat(counter).isEqualTo(5)\n    assertThat(System.currentTimeMillis() - currentTime).isGreaterThan(399L)\n  }\n\n  @Test\n  fun `waitUntil throws an error when the condition is never met`() {\n    assertFailure {\n        waitUntil(\n          condition = \"Wait for test condition\",\n          timeout = 200.milliseconds,\n          delay = 100.milliseconds,\n        ) {\n          false\n        }\n      }\n      .messageContains(\"Waiting until 'Wait for test condition' never returned true.\")\n  }\n\n  @Test\n  fun `throwing an exception in waitUntil bubbles up`() {\n    assertFailure {\n        waitUntil(\n          condition = \"Wait for test condition\",\n          timeout = 200.milliseconds,\n          delay = 100.milliseconds,\n        ) {\n          error(\"Test exception\")\n        }\n      }\n      .messageContains(\"Test exception\")\n  }\n\n  @Test\n  fun `waitUntilCatching blocks until no exception is thrown`() {\n    var counter = 0\n\n    waitUntilCatching(\n      condition = \"Wait for test condition\",\n      timeout = 2.seconds,\n      delay = 20.milliseconds,\n    ) {\n      counter++\n      if (counter < 5) {\n        error(\"Test exception\")\n      }\n    }\n\n    assertThat(counter).isEqualTo(5)\n  }\n\n  @Test\n  fun `waitUntilCatching throws an error when condition is never met`() {\n    assertFailure {\n        waitUntilCatching(\n          condition = \"Wait for test condition\",\n          timeout = 200.milliseconds,\n          delay = 20.milliseconds,\n        ) {\n          error(\"Test exception\")\n        }\n      }\n      .all {\n        messageContains(\"Waiting until 'Wait for test condition' never succeeded.\")\n        cause().isNotNull().messageContains(\"Test exception\")\n      }\n  }\n\n  @Test\n  fun `waitFor returns the result when the value is non-null within the timeout`() {\n    var counter = 0\n\n    val result =\n      waitFor(condition = \"Wait for result\", timeout = 2.seconds, delay = 20.milliseconds) {\n        counter++.takeIf { it == 3 }\n      }\n\n    assertThat(result).isEqualTo(3)\n  }\n\n  @Test\n  fun `waitFor throws an error when the result is null`() {\n    assertFailure {\n        waitFor<Int>(\n          condition = \"Wait for result\",\n          timeout = 100.milliseconds,\n          delay = 20.milliseconds,\n        ) {\n          null\n        }\n      }\n      .messageContains(\"Waiting for 'Wait for result' never succeeded and the value is null.\")\n  }\n}\n"
  },
  {
    "path": "robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/Robot.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport kotlin.reflect.KClass\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.kotlinInjectComponent\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * Test robots are an abstraction between test interactions and the underlying implementation. They\n * hide implementation details of features, mock implementations and the testing framework itself.\n * Instead of a test finding UI elements on screen and making assertions on them using UI test\n * frameworks, a robot would hide these details and do the heavy lifting. The robot can be shared\n * and reused across tests.\n *\n * This is the base class for all instrumentation test robots. Call [robot] to obtain a concrete\n * robot instance and invoke operations on it. Every time a new robot is requested, a new instance\n * is being created.\n *\n * A [Robot] must be annotated with [ContributesRobot]:\n * ```\n * @ContributesRobot(AppScope::class)\n * class ItineraryRobot : Robot {\n *   fun seeItinerary() = ...\n * }\n *\n * robot<ItineraryRobot> { seeItinerary() }\n * ```\n *\n * Using an `@Inject` constructor allows you to inject objects from the kotlin-inject object graph:\n * ```\n * @Inject\n * @ContributesRobot(AppScope::class)\n * class LoginRobot(\n *   private val authService: MockAuthenticationService\n * ) : Robot {\n *   fun login() {\n *     authService.loginMockAccount()\n *   }\n * }\n * ```\n *\n * The [robot] function instantiates a new robot with every call. To make a robot stateful and only\n * ever have a single instance you must wrap your test with the [robot] function:\n * ```\n * @Test\n * fun single_robot_instance() {\n *     robot<SingletonRobot> {\n *         robot<OtherRobot1> { .. }\n *         robot<OtherRobot2> { .. }\n *     }\n * }\n * ```\n *\n * The [close] function can be overridden if the robot manages resources that must be released.\n * [close] is invoked when the [robot] function returns.\n */\npublic interface Robot {\n  /**\n   * Closes this robot.\n   *\n   * This function should be overridden if the robot manages resources that must be released. This\n   * function is invoked when the [robot] function returns and doesn't need to called manually.\n   */\n  // Default no-op implementation.\n  public fun close(): Unit = Unit\n}\n\n/**\n * Creates a [Robot] of type [T] and invokes [block] on the newly created robot:\n * ```\n * @ContributesRobot\n * class AboutScreenRobot : Robot {\n *   fun assertAboutScreenUi() = ...\n * }\n *\n * robot<AboutScreenRobot> {\n *   assertAboutScreenUi()\n * }\n * ```\n *\n * [rootScope] refers to the application scope, which provides [RobotComponent]. Usually, the\n * parameter doesn't need to be changed and the default value can be used.\n */\npublic inline fun <reified T : Robot> robot(\n  rootScope: Scope = software.amazon.app.platform.robot.internal.rootScope,\n  noinline block: T.() -> Unit,\n) {\n  val robot = rootScope.allRobots[T::class]?.invoke() as? T\n\n  checkNotNull(robot) {\n    \"Could not find Robot of type ${T::class}. Did you forget to add the @ContributesRobot \" +\n      \"annotation?\"\n  }\n\n  try {\n    block(robot)\n  } finally {\n    robot.close()\n  }\n}\n\n@PublishedApi\ninternal val Scope.allRobots: Map<KClass<*>, () -> Robot>\n  get() {\n    return kotlinInjectComponentOrNull<RobotComponent>()?.robots.orEmpty() +\n      metroDependencyGraphOrNull<RobotGraph>()?.robots.orEmpty().mapValues { { it.value() } }\n  }\n\nprivate inline fun <reified T : Any> Scope.metroDependencyGraphOrNull(): T? {\n  return try {\n    metroDependencyGraph<T>()\n  } catch (_: NoSuchElementException) {\n    null\n  }\n}\n\nprivate inline fun <reified T : Any> Scope.kotlinInjectComponentOrNull(): T? {\n  return try {\n    kotlinInjectComponent<T>()\n  } catch (_: NoSuchElementException) {\n    null\n  }\n}\n"
  },
  {
    "path": "robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/RobotComponent.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport kotlin.reflect.KClass\nimport software.amazon.lastmile.kotlin.inject.anvil.AppScope\nimport software.amazon.lastmile.kotlin.inject.anvil.ContributesTo\n\n/** Component that provides all contributed [Robot] instances from the dependency graph. */\n@ContributesTo(AppScope::class)\npublic interface RobotComponent {\n  /** All [Robot]s provided in the dependency graph. */\n  public val robots: Map<KClass<out Robot>, () -> Robot>\n}\n"
  },
  {
    "path": "robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/RobotGraph.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.Multibinds\nimport dev.zacsweers.metro.Provider\nimport kotlin.reflect.KClass\n\n/** Graph that provides all contributed [Robot] instances from the Metro dependency graph. */\n@ContributesTo(AppScope::class)\npublic interface RobotGraph {\n  /** All [Robot]s provided in the Metro dependency graph. */\n  @Multibinds(allowEmpty = true) public val robots: Map<KClass<*>, Provider<Robot>>\n}\n"
  },
  {
    "path": "robot/public/src/commonMain/kotlin/software/amazon/app/platform/robot/internal/RobotInternals.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/** Not intended for common usage. */\npublic object RobotInternals {\n\n  /**\n   * Allows you to override the root scope used to initialize test robots. Some platforms, like\n   * Android, try to get the root scope automatically and this function doesn't need to be called.\n   * Other platforms like Desktop must set the [RootScopeProvider] before a test runs.\n   */\n  public fun setRootScopeProvider(rootScopeProvider: RootScopeProvider?) {\n    software.amazon.app.platform.robot.internal.rootScopeProvider = rootScopeProvider\n  }\n}\n"
  },
  {
    "path": "robot/public/src/commonTest/kotlin/software/amazon/app/platform/robot/RobotTest.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.isFalse\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNotSameInstanceAs\nimport assertk.assertions.isTrue\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.addKotlinInjectComponent\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\nclass RobotTest {\n\n  @Test\n  fun `if no robot can be found in the component then a proper error is thrown`() {\n    val exception =\n      assertFailsWith<IllegalStateException> {\n        robot<KiTestRobot>(rootScope(kiRobot = null, metroRobot = null)) {}\n      }\n\n    val message =\n      exception.message?.replace(\"RobotTest\\$KiTestRobot\", \"RobotTest.KiTestRobot\").toString()\n\n    assertThat(message)\n      .contains(\n        \"Could not find Robot of type class software.amazon.app.platform.\" +\n          \"robot.RobotTest.KiTestRobot\"\n      )\n    assertThat(message).contains(\"Did you forget to add the @ContributesRobot annotation?\")\n  }\n\n  @Test\n  fun `the close function is called after the lambda is invoked`() {\n    val rootScope = rootScope(KiTestRobot())\n\n    lateinit var robot: KiTestRobot\n    robot<KiTestRobot>(rootScope) {\n      robot = this\n      assertThat(closeCalled).isFalse()\n    }\n\n    assertThat(robot.closeCalled).isTrue()\n  }\n\n  @Test\n  fun `a new robot is instantiated every time the robot function is invoked`() {\n    val rootScope = Scope.buildRootScope {\n      addKotlinInjectComponent(\n        object : RobotComponent {\n          override val robots: Map<KClass<out Robot>, () -> Robot> =\n            mapOf(KiTestRobot::class to { KiTestRobot() })\n        }\n      )\n    }\n\n    lateinit var robot1: KiTestRobot\n    lateinit var robot2: KiTestRobot\n\n    robot<KiTestRobot>(rootScope) { robot1 = this }\n    robot<KiTestRobot>(rootScope) { robot2 = this }\n\n    assertThat(robot1).isNotSameInstanceAs(robot2)\n\n    robot<KiTestRobot>(rootScope) {\n      val robot1Inner = this\n      robot<KiTestRobot>(rootScope) {\n        val robot2Inner = this\n        assertThat(robot1Inner).isNotSameInstanceAs(robot2Inner)\n      }\n    }\n  }\n\n  @Test\n  fun `a robot is provided for kotlin-inject alone`() {\n    val rootScope = rootScope(kiRobot = KiTestRobot(), metroRobot = null)\n\n    var kiRobot: KiTestRobot? = null\n    robot<KiTestRobot>(rootScope) { kiRobot = this }\n\n    assertFailsWith<Exception> { robot<MetroTestRobot>(rootScope) {} }\n\n    assertThat(kiRobot).isNotNull()\n  }\n\n  @Test\n  fun `a robot is provided for metro alone`() {\n    val rootScope = rootScope(kiRobot = null, metroRobot = MetroTestRobot())\n\n    assertFailsWith<Exception> { robot<KiTestRobot>(rootScope) {} }\n\n    var metroRobot: MetroTestRobot? = null\n    robot<MetroTestRobot>(rootScope) { metroRobot = this }\n\n    assertThat(metroRobot).isNotNull()\n  }\n\n  @Test\n  fun `a robot is provided for kotlin-inject and metro simultaneously`() {\n    val rootScope = rootScope(kiRobot = KiTestRobot(), metroRobot = MetroTestRobot())\n\n    var kiRobot: KiTestRobot? = null\n    robot<KiTestRobot>(rootScope) { kiRobot = this }\n\n    var metroRobot: MetroTestRobot? = null\n    robot<MetroTestRobot>(rootScope) { metroRobot = this }\n\n    assertThat(kiRobot).isNotNull()\n    assertThat(metroRobot).isNotNull()\n  }\n\n  private fun rootScope(\n    kiRobot: Robot? = KiTestRobot(),\n    metroRobot: Robot? = MetroTestRobot(),\n  ): Scope = Scope.buildRootScope {\n    if (kiRobot != null) {\n      addKotlinInjectComponent(Component(kiRobot))\n    }\n    if (metroRobot != null) {\n      addMetroDependencyGraph(Graph(metroRobot))\n    }\n  }\n\n  private class Component(vararg robots: Robot) : RobotComponent {\n    override val robots: Map<KClass<out Robot>, () -> Robot> = robots.associate { robot ->\n      robot::class to { robot }\n    }\n  }\n\n  private class Graph(vararg robots: Robot) : RobotGraph {\n    override val robots: Map<KClass<*>, Provider<Robot>> = robots.associate { robot ->\n      robot::class to provider { robot }\n    }\n  }\n\n  private class KiTestRobot : Robot {\n    var closeCalled = false\n      private set\n\n    override fun close() {\n      closeCalled = true\n    }\n  }\n\n  private class MetroTestRobot : Robot {\n    var closeCalled = false\n      private set\n\n    override fun close() {\n      closeCalled = true\n    }\n  }\n}\n"
  },
  {
    "path": "robot/public/src/noWasmJsMain/kotlin/software/amazon/app/platform/robot/Waiter.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport kotlin.time.Duration\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\n\nprivate val defaultTimeout = 10.seconds\nprivate val defaultDelay = 15.milliseconds\n\n/**\n * Blocks the current thread until the given [block] returns true or the [timeout] occurs. [block]\n * is invoked multiple times with the given [delay] to check the condition. In case of a timeout an\n * [IllegalStateException] is thrown, because the app never transitioned into the expected state.\n * For better error messages [condition] describes what [block] is checking and waiting for.\n *\n * Note that this function should not be called from the main thread. The most common use case is\n * calling it from the instrumentation test thread that is used by default in the test function. The\n * thread this function is invoked in gets blocked and not suspended like a coroutine.\n */\npublic fun waitUntil(\n  condition: String,\n  timeout: Duration = defaultTimeout,\n  delay: Duration = defaultDelay,\n  block: () -> Boolean,\n) {\n  val sleepCycles = (timeout / delay).toInt()\n\n  runBlocking {\n    repeat(sleepCycles) {\n      if (!block()) {\n        delay(delay)\n      } else {\n        return@runBlocking\n      }\n    }\n\n    check(block()) { \"Waiting until '$condition' never returned true.\" }\n  }\n}\n\n/**\n * Similar to [waitUntil], but allows [block] to throw any error when the condition isn't met. This\n * is helpful for example to wait for a UI element, e.g.\n *\n * ```\n * waitUntilCatching(\"text is visible\") {\n *     seeViewWithText(\"Some text\")\n * }\n * ```\n */\n@Suppress(\"TooGenericExceptionCaught\")\npublic fun waitUntilCatching(\n  condition: String,\n  timeout: Duration = defaultTimeout,\n  delay: Duration = defaultDelay,\n  block: () -> Unit,\n) {\n  var lastException: Throwable? = null\n  try {\n    waitUntil(condition = condition, timeout = timeout, delay = delay) {\n      try {\n        lastException = null\n        block()\n        true\n      } catch (t: Throwable) {\n        lastException = t\n        false\n      }\n    }\n  } catch (t: Throwable) {\n    if (lastException != null) {\n      throw IllegalStateException(\"Waiting until '$condition' never succeeded.\", lastException)\n    } else {\n      throw t\n    }\n  }\n}\n\n/**\n * Similar to [waitUntil], but allows [block] to throw any error when the condition isn't met. This\n * is helpful for example to wait for a UI element, e.g.\n *\n * ```\n * waitUntilCatching(\"text is visible\") {\n *     seeViewWithText(\"Some text\")\n * }\n * ```\n */\n@Suppress(\"TooGenericExceptionCaught\")\npublic fun <T : Any> waitFor(\n  condition: String,\n  timeout: Duration = defaultTimeout,\n  delay: Duration = defaultDelay,\n  block: () -> T?,\n): T {\n  var result: T? = null\n\n  try {\n    waitUntil(condition = condition, timeout = timeout, delay = delay) {\n      result = block()\n      result != null\n    }\n  } catch (t: Throwable) {\n    if (result == null) {\n      throw IllegalStateException(\n        \"Waiting for '$condition' never succeeded and the value is null.\",\n        t,\n      )\n    } else {\n      throw t\n    }\n  }\n\n  return checkNotNull(result) { \"Waiting for '$condition' never succeeded and the value is null.\" }\n}\n"
  },
  {
    "path": "robot-compose-multiplatform/public/api/android/public.api",
    "content": "public abstract interface class software/amazon/app/platform/robot/ComposeInteractionsProvider {\n\tpublic abstract fun getSemanticsNodeInteractionsProvider ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n}\n\npublic abstract class software/amazon/app/platform/robot/ComposeRobot : software/amazon/app/platform/robot/Robot {\n\tpublic static final field $stable I\n\tpublic field interactionsProvider Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic fun <init> ()V\n\tpublic fun close ()V\n\tprotected final fun getCompose ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic final fun getInteractionsProvider ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic final fun setInteractionsProvider (Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;)V\n}\n\n"
  },
  {
    "path": "robot-compose-multiplatform/public/api/desktop/public.api",
    "content": "public abstract interface class software/amazon/app/platform/robot/ComposeInteractionsProvider {\n\tpublic abstract fun getSemanticsNodeInteractionsProvider ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n}\n\npublic abstract class software/amazon/app/platform/robot/ComposeRobot : software/amazon/app/platform/robot/Robot {\n\tpublic static final field $stable I\n\tpublic field interactionsProvider Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic fun <init> ()V\n\tpublic fun close ()V\n\tprotected final fun getCompose ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic final fun getInteractionsProvider ()Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;\n\tpublic final fun setInteractionsProvider (Landroidx/compose/ui/test/SemanticsNodeInteractionsProvider;)V\n}\n\n"
  },
  {
    "path": "robot-compose-multiplatform/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enableCompose true\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':robot:public')\n    commonMainApi project(':scope:public')\n    commonMainApi compose.uiTest\n\n    commonMainImplementation project(':robot-internal:public')\n\n    commonTestImplementation project(':metro:public')\n    commonTestImplementation project(':scope:testing')\n    commonTestImplementation libs.metro.runtime\n\n    desktopTestImplementation compose.material\n    iosTestImplementation compose.material\n}\n"
  },
  {
    "path": "robot-compose-multiplatform/public/src/appleAndDesktopTest/kotlin/software/amazon/app/platform/robot/ComposeRobotTest.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.test.ComposeUiTest\nimport androidx.compose.ui.test.ExperimentalTestApi\nimport androidx.compose.ui.test.SemanticsNodeInteractionsProvider\nimport androidx.compose.ui.test.assertTextEquals\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.runComposeUiTest\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport assertk.assertions.messageContains\nimport dev.zacsweers.metro.Provider\nimport dev.zacsweers.metro.provider\nimport kotlin.reflect.KClass\nimport kotlin.test.Test\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\n// Note that this class has to be duplicated and cannot be moved into commonTest, because Android\n// unit tests don't have access to `runComposeUiTest`.\n@OptIn(ExperimentalTestApi::class)\nclass ComposeRobotTest {\n\n  @Test\n  fun `the close function is called after the lambda is invoked`() {\n    val rootScope = rootScope(TestRobot())\n\n    lateinit var robot: TestRobot\n\n    runComposeUiTest {\n      with(interactionProvider()) {\n        composeRobot<TestRobot>(rootScope) {\n          robot = this\n          assertThat(closeCalled).isFalse()\n        }\n      }\n    }\n    assertThat(robot.closeCalled).isTrue()\n  }\n\n  @Test\n  fun `the SemanticsNodeInteractionsProvider is applied within the composeRobot function`() {\n    val rootScope = rootScope(TestRobot())\n\n    runComposeUiTest {\n      setContent { BasicText(\"Hello world!\", Modifier.testTag(\"text\")) }\n\n      with(interactionProvider()) { composeRobot<TestRobot>(rootScope) { textIsShown() } }\n    }\n  }\n\n  @Test\n  fun `calling robot instead of composeRobot will crash`() {\n    val rootScope = rootScope(TestRobot())\n\n    assertFailure { robot<TestRobot>(rootScope) { textIsShown() } }\n      .messageContains(\"lateinit property interactionsProvider has not been initialized\")\n  }\n\n  private fun rootScope(vararg robots: Robot): Scope = Scope.buildRootScope {\n    addMetroDependencyGraph(Component(*robots))\n  }\n\n  private fun ComposeUiTest.interactionProvider(): ComposeInteractionsProvider {\n    val interactionsProvider = this\n    return object : ComposeInteractionsProvider {\n      override val semanticsNodeInteractionsProvider: SemanticsNodeInteractionsProvider =\n        interactionsProvider\n    }\n  }\n\n  private class Component(vararg robots: Robot) : RobotGraph {\n    override val robots: Map<KClass<*>, Provider<Robot>> = robots.associate { robot ->\n      robot::class to provider { robot }\n    }\n  }\n\n  private class TestRobot : ComposeRobot() {\n    var closeCalled = false\n      private set\n\n    fun textIsShown() {\n      compose.onNodeWithTag(\"text\").assertTextEquals(\"Hello world!\")\n    }\n\n    override fun close() {\n      closeCalled = true\n    }\n  }\n}\n"
  },
  {
    "path": "robot-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/robot/ComposeInteractionsProvider.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport androidx.compose.ui.test.SemanticsNodeInteractionsProvider\n\n/**\n * Provides the [SemanticsNodeInteractionsProvider] that is forwarded to [ComposeRobot]s. This\n * interface is usually implemented by the test class and forwards the Compose test rule.\n */\npublic interface ComposeInteractionsProvider {\n  /** Provides access to Compose UI interactions. */\n  public val semanticsNodeInteractionsProvider: SemanticsNodeInteractionsProvider\n}\n"
  },
  {
    "path": "robot-compose-multiplatform/public/src/commonMain/kotlin/software/amazon/app/platform/robot/ComposeRobot.kt",
    "content": "package software.amazon.app.platform.robot\n\nimport androidx.compose.ui.test.SemanticsNodeInteractionsProvider\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * A [Robot] that has access to a [SemanticsNodeInteractionsProvider] and allows you to make\n * assertions and invoke actions on Compose UI elements.\n */\npublic abstract class ComposeRobot : Robot {\n\n  @PublishedApi internal lateinit var interactionsProvider: SemanticsNodeInteractionsProvider\n\n  /** The [SemanticsNodeInteractionsProvider] to use and interact with Compose UI elements. */\n  protected val compose: SemanticsNodeInteractionsProvider\n    get() = interactionsProvider\n}\n\n/** Creates a [ComposeRobot] of type [T] and invokes the lambda on the newly created robot. */\npublic inline fun <reified T : Robot> ComposeInteractionsProvider.composeRobot(\n  rootScope: Scope = software.amazon.app.platform.robot.internal.rootScope,\n  noinline block: T.() -> Unit,\n): Unit = semanticsNodeInteractionsProvider.composeRobot<T>(rootScope, block)\n\n/** Creates a [ComposeRobot] of type [T] and invokes the lambda on the newly created robot. */\npublic inline fun <reified T : Robot> SemanticsNodeInteractionsProvider.composeRobot(\n  rootScope: Scope = software.amazon.app.platform.robot.internal.rootScope,\n  noinline block: T.() -> Unit,\n) {\n  robot<T>(rootScope) {\n    if (this is ComposeRobot) {\n      this.interactionsProvider = this@composeRobot\n    }\n\n    block(this)\n  }\n}\n"
  },
  {
    "path": "robot-internal/public/api/android/public.api",
    "content": "public final class software/amazon/app/platform/robot/internal/DefaultRootScopeKt {\n\tpublic static final fun getDefaultRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/robot/internal/RootScopeKt {\n\tpublic static final fun getRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/robot/internal/RootScopeProviderKt {\n\tpublic static final fun getRootScopeProvider ()Lsoftware/amazon/app/platform/scope/RootScopeProvider;\n\tpublic static final fun setRootScopeProvider (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n}\n\n"
  },
  {
    "path": "robot-internal/public/api/desktop/public.api",
    "content": "public final class software/amazon/app/platform/robot/internal/DefaultRootScopeKt {\n\tpublic static final fun getDefaultRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/robot/internal/RootScopeKt {\n\tpublic static final fun getRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/robot/internal/RootScopeProviderKt {\n\tpublic static final fun getRootScopeProvider ()Lsoftware/amazon/app/platform/scope/RootScopeProvider;\n\tpublic static final fun setRootScopeProvider (Lsoftware/amazon/app/platform/scope/RootScopeProvider;)V\n}\n\n"
  },
  {
    "path": "robot-internal/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ndependencies {\n    commonMainApi project(':scope:public')\n\n    androidMainImplementation libs.androidx.test.monitor\n\n    commonTestImplementation project(':scope:testing')\n}\n"
  },
  {
    "path": "robot-internal/public/src/androidMain/kotlin/software/amazon/app/platform/robot/internal/DefaultRootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\n\n/** A default instance that can be statically obtained. */\npublic actual val defaultRootScope: Scope?\n  get() {\n    val applicationContext =\n      InstrumentationRegistry.getInstrumentation().targetContext.applicationContext\n\n    return (applicationContext as? RootScopeProvider)?.rootScope\n  }\n"
  },
  {
    "path": "robot-internal/public/src/commonMain/kotlin/software/amazon/app/platform/robot/internal/DefaultRootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.Scope\n\n/** A default instance that can be statically obtained. */\ninternal expect val defaultRootScope: Scope?\n"
  },
  {
    "path": "robot-internal/public/src/commonMain/kotlin/software/amazon/app/platform/robot/internal/RootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * A static instance of the root scope. Robots use this as default instance to initialize\n * themselves.\n */\npublic val rootScope: Scope\n  get() =\n    checkNotNull(rootScopeProvider?.rootScope ?: defaultRootScope) {\n      \"The root scope could not be found. Consider overriding the RootScopeProvider \" +\n        \"through RobotInternals.\"\n    }\n"
  },
  {
    "path": "robot-internal/public/src/commonMain/kotlin/software/amazon/app/platform/robot/internal/RootScopeProvider.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * A global value that can be set before a test runs. The returned root scope will be used to\n * initialize robots.\n */\npublic var rootScopeProvider: RootScopeProvider? = null\n"
  },
  {
    "path": "robot-internal/public/src/commonTest/kotlin/software/amazon/app/platform/robot/internal/RootScopeTest.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.isEqualTo\nimport kotlin.test.AfterTest\nimport kotlin.test.BeforeTest\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\n\nclass RootScopeTest {\n\n  @BeforeTest\n  fun before() {\n    rootScopeProvider = null\n  }\n\n  @AfterTest\n  fun after() {\n    rootScopeProvider = null\n  }\n\n  @Test\n  fun `if no root scope can be found then an error is thrown`() {\n    val throwable = assertFailsWith<Throwable> { rootScope }\n\n    if (throwable.stackTraceToString().contains(\"No instrumentation registered!\")) {\n      // This is for Android, but we're not running on a real Android device, so stop\n      // the test.\n      return\n    }\n\n    assertThat(throwable.message.toString())\n      .contains(\n        \"The root scope could not be found. Consider overriding the \" +\n          \"RootScopeProvider through RobotInternals.\"\n      )\n  }\n\n  @Test\n  fun `the root scope provider can be changed`() {\n    rootScopeProvider =\n      object : RootScopeProvider {\n        override val rootScope: Scope = Scope.buildRootScope(\"Test Scope\")\n      }\n\n    assertThat(rootScope.name).isEqualTo(\"Test Scope\")\n  }\n}\n"
  },
  {
    "path": "robot-internal/public/src/desktopMain/kotlin/software/amazon/app/platform/robot/internal/DefaultRootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.Scope\n\n/** A default instance that can be statically obtained. */\npublic actual val defaultRootScope: Scope? = null\n"
  },
  {
    "path": "robot-internal/public/src/nativeMain/kotlin/software/amazon/app/platform/robot/internal/DefaultRootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.Scope\n\n/** A default instance that can be statically obtained. */\npublic actual val defaultRootScope: Scope? = null\n"
  },
  {
    "path": "robot-internal/public/src/wasmJsMain/kotlin/software/amazon/app/platform/robot/internal/DefaultRootScope.kt",
    "content": "package software.amazon.app.platform.robot.internal\n\nimport software.amazon.app.platform.scope.Scope\n\n/** A default instance that can be statically obtained. */\ninternal actual val defaultRootScope: Scope? = null\n"
  },
  {
    "path": "sample/app/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.app'\n}\n\n// This extension comes from our published plugin.\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n    addImplModuleDependencies true\n}\n\nmetro {\n    // Suppress for this sample. The input is used to demonstrate how platform specific\n    // types can be provided in the graph.\n    //noinspection UnnecessaryQualifiedReference\n    unusedGraphInputsSeverity.set(dev.zacsweers.metro.gradle.DiagnosticSeverity.NONE)\n}\n\n// This extension comes from buildSrc.\nappPlatformBuildSrc {\n    enableInstrumentedTests true\n}\n\nandroid {\n    defaultConfig {\n        testInstrumentationRunner 'software.amazon.app.platform.sample.TestRunner'\n    }\n}\n\ndependencies {\n    commonMainImplementation project(':sample:login:impl')\n    commonMainImplementation project(':sample:navigation:impl')\n    commonMainImplementation project(':sample:templates:impl')\n    commonMainImplementation project(':sample:user:impl')\n\n    androidMainImplementation libs.androidx.activity.compose\n\n    desktopTestImplementation project(':robot-compose-multiplatform:public')\n    desktopTestImplementation project(':sample:login:impl-robots')\n    desktopTestImplementation project(':sample:user:impl-robots')\n\n    androidInstrumentedTestImplementation project(':robot-compose-multiplatform:public')\n    androidInstrumentedTestImplementation project(':sample:login:impl-robots')\n    androidInstrumentedTestImplementation project(':sample:user:impl-robots')\n    androidInstrumentedTestImplementation libs.compose.ui.test.junit4\n    androidInstrumentedTestImplementation libs.compose.ui.test.junit4.android\n    androidInstrumentedTestImplementation libs.compose.ui.test.manifest\n}\n"
  },
  {
    "path": "sample/app/lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"MonochromeLauncherIcon\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/AndroidLoginUiTest.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.provider.Settings.Global.ANIMATOR_DURATION_SCALE\nimport android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE\nimport android.provider.Settings.Global.WINDOW_ANIMATION_SCALE\nimport androidx.activity.ComponentActivity\nimport androidx.compose.ui.test.SemanticsNodeInteractionsProvider\nimport androidx.compose.ui.test.junit4.AndroidComposeTestRule\nimport androidx.compose.ui.test.junit4.ComposeTestRule\nimport androidx.test.espresso.Espresso\nimport androidx.test.ext.junit.rules.ActivityScenarioRule\nimport androidx.test.platform.app.InstrumentationRegistry\nimport kotlin.time.Duration.Companion.seconds\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport software.amazon.app.platform.robot.ComposeInteractionsProvider\nimport software.amazon.app.platform.robot.composeRobot\nimport software.amazon.app.platform.robot.waitUntilCatching\nimport software.amazon.app.platform.sample.login.LoginRobot\nimport software.amazon.app.platform.sample.user.UserPageRobot\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/** This class implements [ComposeInteractionsProvider] to make it easier to call [composeRobot]. */\nclass AndroidLoginUiTest : ComposeInteractionsProvider {\n\n  @get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java)\n\n  @get:Rule\n  val composeTestRule: ComposeTestRule =\n    AndroidComposeTestRule(activityRule, ::getActivityFromTestRule)\n\n  override val semanticsNodeInteractionsProvider: SemanticsNodeInteractionsProvider\n    get() = composeTestRule\n\n  @Before\n  fun before() {\n    setAnimations(enabled = false)\n  }\n\n  @After\n  fun after() {\n    val rootScopeProvider =\n      InstrumentationRegistry.getInstrumentation().targetContext.applicationContext\n        as RootScopeProvider\n\n    // Good hygiene to clean everything up.\n    rootScopeProvider.rootScope.destroy()\n    setAnimations(enabled = true)\n  }\n\n  @Test\n  fun a_user_logs_in_and_opens_the_profile_picture() {\n    composeRobot<LoginRobot> {\n      seeLoginButton()\n      clickLoginButton()\n    }\n\n    waitUntilCatching(\"login finished\", timeout = 4.seconds) {\n      composeRobot<UserPageRobot> {\n        seeUserId()\n        seeProfilePicture(fullScreen = false)\n      }\n    }\n\n    // Note that this code doesn't run within the `waitUntilCatching` on purpose. The code\n    // above waits until we're logged in and retries the operation until the UI displayed. The\n    // operations below should not be retried.\n    composeRobot<UserPageRobot> {\n      clickProfilePicture()\n      seeProfilePicture(fullScreen = true)\n\n      clickProfilePicture()\n      seeProfilePicture(fullScreen = false)\n    }\n  }\n\n  @Test\n  fun a_back_press_logs_out_the_user_early() {\n    composeRobot<LoginRobot> {\n      seeLoginButton()\n      clickLoginButton()\n    }\n\n    waitUntilCatching(\"login finished\", timeout = 4.seconds) {\n      composeRobot<UserPageRobot> {\n        seeUserId()\n        seeProfilePicture(fullScreen = false)\n      }\n    }\n\n    Espresso.pressBack()\n\n    waitUntilCatching(\"logout finished\", timeout = 1.seconds) {\n      composeRobot<LoginRobot> { seeLoginButton() }\n    }\n  }\n\n  // Borrowed from AndroidComposeTestRule.\n  private fun <A : ComponentActivity> getActivityFromTestRule(rule: ActivityScenarioRule<A>): A {\n    var activity: A? = null\n    rule.scenario.onActivity { activity = it }\n\n    return with(activity) {\n      checkNotNull(this) { \"Activity was not set in the ActivityScenarioRule!\" }\n    }\n  }\n\n  private fun setAnimations(enabled: Boolean) {\n    val value = if (enabled) \"1.0\" else \"0.0\"\n    InstrumentationRegistry.getInstrumentation().uiAutomation.run {\n      executeShellCommand(\"settings put global $WINDOW_ANIMATION_SCALE $value\")\n      executeShellCommand(\"settings put global $TRANSITION_ANIMATION_SCALE $value\")\n      executeShellCommand(\"settings put global $ANIMATOR_DURATION_SCALE $value\")\n    }\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.app.Application\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/** Metro graph that is used in instrumented tests. */\n@DependencyGraph(AppScope::class)\ninterface TestAndroidAppGraph {\n  /** The factory to create a new instance of [AndroidAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [AndroidAppGraph] instance. [application] and [rootScopeProvider] are provided\n     * in the [AndroidAppGraph] and can be injected.\n     */\n    fun create(\n      @Provides application: Application,\n      @Provides rootScopeProvider: RootScopeProvider,\n    ): TestAndroidAppGraph\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestAndroidApplication.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.createGraphFactory\n\n/**\n * Application class that is used in instrumented tests. Note that it provides a\n * [TestAndroidApplication] instead of [AndroidApplication].\n */\nclass TestAndroidApplication : AndroidApplication() {\n  override fun metroGraph(demoApplication: DemoApplication): AppGraph {\n    return createGraphFactory<TestAndroidAppGraph.Factory>().create(this, demoApplication)\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidInstrumentedTest/kotlin/software/amazon/app/platform/sample/TestRunner.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.test.runner.AndroidJUnitRunner\n\n/** The test runner overrides the application class in favor of the test version. */\n@Suppress(\"unused\")\nclass TestRunner : AndroidJUnitRunner() {\n  override fun newApplication(cl: ClassLoader, className: String, context: Context): Application =\n    newApplication(TestAndroidApplication::class.java, context)\n}\n"
  },
  {
    "path": "sample/app/src/androidMain/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:name=\"software.amazon.app.platform.sample.AndroidApplication\"\n        android:allowBackup=\"false\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@android:style/Theme.Material.Light.NoActionBar\"\n        tools:ignore=\"DataExtractionRules\">\n        <activity\n            android:exported=\"true\"\n            android:configChanges=\"orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode\"\n            android:name=\"software.amazon.app.platform.sample.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.app.Application\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Android app graph.\n *\n * Note that [Application] is an Android specific type and classes living in the Android source\n * folder can therefore inject [Application].\n */\n@DependencyGraph(AppScope::class)\ninterface AndroidAppGraph {\n  /** The factory to create a new instance of [AndroidAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [AndroidAppGraph] instance. [application] and [rootScopeProvider] are provided\n     * in the [AndroidAppGraph] and can be injected.\n     */\n    fun create(\n      @Provides application: Application,\n      @Provides rootScopeProvider: RootScopeProvider,\n    ): AndroidAppGraph\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/AndroidApplication.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.app.Application\nimport dev.zacsweers.metro.createGraphFactory\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * The [Application] class of our sample app. Note that this class implements [RootScopeProvider].\n * This is helpful to get access to the root scope from Android components such as activities.\n */\nopen class AndroidApplication : Application(), RootScopeProvider {\n\n  private val demoApplication = DemoApplication()\n\n  override val rootScope: Scope\n    get() = demoApplication.rootScope\n\n  override fun onCreate() {\n    demoApplication.create(metroGraph(demoApplication))\n    super.onCreate()\n  }\n\n  /** Create the [AppGraph]. In UI tests we use a different instance. */\n  protected open fun metroGraph(demoApplication: DemoApplication): AppGraph {\n    return createGraphFactory<AndroidAppGraph.Factory>().create(this, demoApplication)\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivity.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport software.amazon.app.platform.renderer.ComposeAndroidRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The only `Activity` of our sample app. This class is just an entry point to start rendering\n * templates.\n */\nclass MainActivity : ComponentActivity() {\n\n  private val rootScopeProvider\n    get() = application as RootScopeProvider\n\n  private val viewModel by viewModels<MainActivityViewModel>()\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n    enableEdgeToEdge()\n\n    val rendererFactory =\n      ComposeAndroidRendererFactory.createForComposeUi(rootScopeProvider = rootScopeProvider)\n\n    setContent {\n      val template by viewModel.templates.collectAsState()\n\n      val renderer = rendererFactory.getComposeRenderer(template)\n      renderer.renderCompose(template)\n    }\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidMain/kotlin/software/amazon/app/platform/sample/MainActivityViewModel.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.sample.template.SampleAppTemplate\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * `ViewModel` that hosts the stream of templates and survives configuration changes. Note that we\n * use [application] to get access to the root scope.\n */\nclass MainActivityViewModel(application: Application) : AndroidViewModel(application) {\n\n  private val graph = (application as RootScopeProvider).rootScope.metroDependencyGraph<Graph>()\n  private val templateProvider = graph.templateProviderFactory.createTemplateProvider()\n\n  /** The stream of templates that are rendered by [MainActivity]. */\n  val templates: StateFlow<SampleAppTemplate> = templateProvider.templates\n\n  override fun onCleared() {\n    templateProvider.cancel()\n  }\n\n  /** Graph interface to give us access to objects from the app graph. */\n  @ContributesTo(AppScope::class)\n  interface Graph {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "sample/app/src/androidMain/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "sample/app/src/androidMain/res/drawable/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "sample/app/src/androidMain/res/mipmap-anydpi/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "sample/app/src/androidMain/res/mipmap-anydpi/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "sample/app/src/androidMain/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">App Platform Demo</string>\n</resources>\n"
  },
  {
    "path": "sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/AppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.Multibinds\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\n\n/**\n * Shared interface for the app graph. The final graphs live in the platform specific source folders\n * in order to have access to platform specific code.\n */\n@ContributesTo(AppScope::class)\ninterface AppGraph {\n  /** All [Scoped] instances part of the app scope. */\n  @ForScope(AppScope::class) @Multibinds(allowEmpty = true) val appScopedInstances: Set<Scoped>\n\n  /** The coroutine scope that runs as long as the app scope is alive. */\n  @ForScope(AppScope::class) val appScopeCoroutineScopeScoped: CoroutineScopeScoped\n}\n"
  },
  {
    "path": "sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/DemoApplication.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\nimport software.amazon.app.platform.scope.register\n\n/**\n * Shared class between the platform to manage the root scope. It itself implements the\n * [RootScopeProvider] interface.\n */\nclass DemoApplication : RootScopeProvider {\n\n  private var _rootScope: Scope? = null\n\n  override val rootScope: Scope\n    get() = checkNotNull(_rootScope) { \"Must call create() first.\" }\n\n  /** Creates the root scope and remembers the instance. */\n  fun create(appGraph: AppGraph) {\n    check(_rootScope == null) { \"create() should be called only once.\" }\n\n    _rootScope = Scope.buildRootScope {\n      addMetroDependencyGraph(appGraph)\n\n      addCoroutineScopeScoped(appGraph.appScopeCoroutineScopeScoped)\n    }\n\n    // Register instances after the rootScope has been set to avoid race conditions for Scoped\n    // instances that may use the rootScope.\n    rootScope.register(appGraph.appScopedInstances)\n  }\n\n  /** Destroys the root scope. */\n  fun destroy() {\n    rootScope.destroy()\n    _rootScope = null\n  }\n}\n"
  },
  {
    "path": "sample/app/src/commonMain/kotlin/software/amazon/app/platform/sample/TemplateProvider.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.Assisted\nimport dev.zacsweers.metro.AssistedFactory\nimport dev.zacsweers.metro.AssistedInject\nimport dev.zacsweers.metro.Inject\nimport kotlinx.coroutines.flow.StateFlow\nimport software.amazon.app.platform.presenter.molecule.MoleculeScope\nimport software.amazon.app.platform.presenter.molecule.MoleculeScopeFactory\nimport software.amazon.app.platform.presenter.molecule.launchMoleculePresenter\nimport software.amazon.app.platform.sample.navigation.NavigationPresenter\nimport software.amazon.app.platform.sample.template.SampleAppTemplate\nimport software.amazon.app.platform.sample.template.SampleAppTemplatePresenter\n\n/**\n * Shared class between all platforms to start collecting [SampleAppTemplate] in a [StateFlow].\n * Inject [Factory] to create a new instance. Once the instance is no longer needed, call [cancel]\n * to clean up any resources.\n *\n * [NavigationPresenter] serves as the root presenter and gets wrapped in a\n * [SampleAppTemplatePresenter].\n */\n@AssistedInject\nclass TemplateProvider(\n  presenter: NavigationPresenter,\n  templatePresenterFactory: SampleAppTemplatePresenter.Factory,\n  @Assisted private val moleculeScope: MoleculeScope,\n) {\n\n  /** The templates that should be rendered in the UI. */\n  val templates: StateFlow<SampleAppTemplate> by lazy {\n    moleculeScope\n      .launchMoleculePresenter(\n        presenter = templatePresenterFactory.createSampleAppTemplatePresenter(presenter),\n        input = Unit,\n      )\n      .model\n  }\n\n  /** Releases all resources and stops [templates] from updating further. */\n  fun cancel() {\n    moleculeScope.cancel()\n  }\n\n  /**\n   * The assisted factory for Metro to create a new [TemplateProvider]. This factory is wrapped by\n   * [Factory], which should be used instead.\n   */\n  @AssistedFactory\n  fun interface InternalFactory {\n    /** Create a new instance of [TemplateProvider] with the given [MoleculeScope]. */\n    fun create(moleculeScope: MoleculeScope): TemplateProvider\n  }\n\n  /** Factory class to create a new instance of [TemplateProvider]. */\n  // Note that the Factory class technically is not required. But since TemplateProvider\n  // contains a MoleculeScope that needs to be canceled explicitly, this Factory helps to\n  // highlight that the created instance contains resources that must be cleaned up.\n  @Inject\n  class Factory(\n    private val moleculeScopeFactory: MoleculeScopeFactory,\n    private val templateProviderFactory: InternalFactory,\n  ) {\n    /**\n     * Creates a new instance of [TemplateProvider]. Call [TemplateProvider.cancel] when the\n     * instance not needed anymore to avoid leaking resources.\n     */\n    fun createTemplateProvider(): TemplateProvider {\n      return templateProviderFactory.create(moleculeScopeFactory.createMoleculeScope())\n    }\n  }\n}\n"
  },
  {
    "path": "sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopApp.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * Responsible for creating the ap [graph] and producing templates. Call [destroy] to clean up any\n * resources.\n *\n * This class is reused in UI tests, but the tests use a different test specific [AppGraph].\n */\nclass DesktopApp(private val graph: (RootScopeProvider) -> AppGraph) : RootScopeProvider {\n\n  override val rootScope: Scope\n    get() = demoApplication.rootScope\n\n  private val demoApplication = DemoApplication().apply { create(graph(this)) }\n\n  private val templateProvider =\n    rootScope.metroDependencyGraph<Graph>().templateProviderFactory.createTemplateProvider()\n\n  /** Call this composable function to start rendering templates on the screen. */\n  @Composable\n  fun renderTemplates() {\n    val template by templateProvider.templates.collectAsState()\n\n    val factory = remember { ComposeRendererFactory(demoApplication) }\n\n    val renderer = factory.getComposeRenderer(template)\n    renderer.renderCompose(template)\n  }\n\n  /** Cancels and releases all resources. */\n  fun destroy() {\n    templateProvider.cancel()\n    demoApplication.destroy()\n  }\n\n  /** Graph interface to give us access to objects from the app graph. */\n  @ContributesTo(AppScope::class)\n  interface Graph {\n    /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n    val templateProviderFactory: TemplateProvider.Factory\n  }\n}\n"
  },
  {
    "path": "sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/DesktopAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Desktop app graph. Unlike the Android and iOS specific counterpart, this class doesn't\n * have any platform specific types.\n */\n@DependencyGraph(AppScope::class)\ninterface DesktopAppGraph {\n  /** The factory to create a new instance of [DesktopAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [DesktopAppGraph] instance. [rootScopeProvider] is provided in the\n     * [DesktopAppGraph] and can be injected.\n     */\n    fun create(@Provides rootScopeProvider: RootScopeProvider): DesktopAppGraph\n  }\n}\n"
  },
  {
    "path": "sample/app/src/desktopMain/kotlin/software/amazon/app/platform/sample/Main.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport androidx.compose.ui.window.Window\nimport androidx.compose.ui.window.application\nimport dev.zacsweers.metro.createGraphFactory\n\n/** The main function to launch the Desktop app. */\nfun main() {\n  val desktopApp = DesktopApp { createGraphFactory<DesktopAppGraph.Factory>().create(it) }\n\n  application {\n    Window(\n      onCloseRequest = {\n        desktopApp.destroy()\n        exitApplication()\n      },\n      // alwaysOnTop helps during development to see the application in foreground.\n      alwaysOnTop = true,\n      title = \"App Platform\",\n    ) {\n      desktopApp.renderTemplates()\n    }\n  }\n}\n"
  },
  {
    "path": "sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/LoginUiTest.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport androidx.compose.ui.test.ComposeUiTest\nimport androidx.compose.ui.test.ExperimentalTestApi\nimport androidx.compose.ui.test.runComposeUiTest\nimport dev.zacsweers.metro.createGraphFactory\nimport kotlin.test.AfterTest\nimport kotlin.test.BeforeTest\nimport kotlin.test.Test\nimport kotlin.time.Duration.Companion.seconds\nimport software.amazon.app.platform.robot.composeRobot\nimport software.amazon.app.platform.robot.internal.RobotInternals\nimport software.amazon.app.platform.robot.waitUntilCatching\nimport software.amazon.app.platform.sample.login.LoginRobot\nimport software.amazon.app.platform.sample.user.UserPageRobot\n\n@OptIn(ExperimentalTestApi::class)\nclass LoginUiTest {\n\n  private lateinit var desktopApp: DesktopApp\n\n  @BeforeTest\n  fun before() {\n    desktopApp = DesktopApp {\n      // Note that we use a different test specific graph in UI tests.\n      createGraphFactory<TestDesktopAppGraph.Factory>().create(it)\n    }\n\n    // This is required for Desktop and iOS. On Android it's expected that the Application\n    // class implements the RootScopeProvider interface and the test environment has static\n    // access to the Application.\n    RobotInternals.setRootScopeProvider(desktopApp)\n  }\n\n  @AfterTest\n  fun after() {\n    RobotInternals.setRootScopeProvider(null)\n\n    // Good hygiene to clean everything up.\n    desktopApp.destroy()\n  }\n\n  @Test\n  fun `a user logs in and opens the profile picture`(): Unit = runRobotTest {\n    composeRobot<LoginRobot> {\n      seeLoginButton()\n      clickLoginButton()\n    }\n\n    waitUntilCatching(\"login finished\", timeout = 2.seconds) {\n      composeRobot<UserPageRobot> {\n        seeUserId()\n        seeProfilePicture(fullScreen = false)\n      }\n    }\n\n    // Note that this code doesn't run within the `waitUntilCatching` on purpose. The code\n    // above waits until we're logged in and retries the operation until the UI displayed. The\n    // operations below should not be retried.\n    composeRobot<UserPageRobot> {\n      clickProfilePicture()\n      seeProfilePicture(fullScreen = true)\n\n      clickProfilePicture()\n      seeProfilePicture(fullScreen = false)\n    }\n  }\n\n  /** Convenience function to start rendering templates. */\n  private fun runRobotTest(block: ComposeUiTest.() -> Unit) {\n    runComposeUiTest {\n      setContent { desktopApp.renderTemplates() }\n\n      block()\n    }\n  }\n}\n"
  },
  {
    "path": "sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestAnimationHelper.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.sample.user.AnimationHelper\nimport software.amazon.app.platform.sample.user.DefaultAnimationsHelper\n\n/**\n * This implementation replaces [DefaultAnimationsHelper] in UI tests to disable animations and make\n * tests more stable.\n */\n@Inject\n@ContributesBinding(AppScope::class, replaces = [DefaultAnimationsHelper::class])\nclass TestAnimationHelper : AnimationHelper {\n  override fun isAnimationsEnabled(): Boolean = false\n}\n"
  },
  {
    "path": "sample/app/src/desktopTest/kotlin/software/amazon/app/platform/sample/TestDesktopAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/** Metro graph that is used in UI tests. */\n@DependencyGraph(AppScope::class)\ninterface TestDesktopAppGraph : DesktopApp.Graph {\n  @DependencyGraph.Factory\n  fun interface Factory {\n    fun create(@Provides rootScopeProvider: RootScopeProvider): TestDesktopAppGraph\n  }\n}\n"
  },
  {
    "path": "sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/IosAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.createGraphFactory\nimport platform.UIKit.UIApplication\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final iOS app graph.\n *\n * Note that [UIApplication] is an iOS specific type and classes living in the iOS source folder can\n * therefore inject [UIApplication].\n */\n@DependencyGraph(AppScope::class)\ninterface IosAppGraph {\n  /** The factory to create a new instance of [IosAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [IosAppGraph] instance. [uiApplication] and [rootScopeProvider] are provided in\n     * the [IosAppGraph] and can be injected.\n     */\n    fun create(\n      @Provides uiApplication: UIApplication,\n      @Provides rootScopeProvider: RootScopeProvider,\n    ): IosAppGraph\n  }\n\n  /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n  val templateProviderFactory: TemplateProvider.Factory\n}\n\n/** This function is called from Swift to create a new graph instance. */\n@Suppress(\"unused\")\nfun createIosAppGraph(application: UIApplication, rootScopeProvider: RootScopeProvider): AppGraph {\n  return createGraphFactory<IosAppGraph.Factory>().create(application, rootScopeProvider)\n}\n"
  },
  {
    "path": "sample/app/src/iosMain/kotlin/software/amazon/app/platform/sample/MainViewController.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.window.ComposeUIViewController\nimport platform.UIKit.UIViewController\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * This function is called from Swift to hook up the Compose Multiplatform UI.\n *\n * This is our entry point to start producing templates and hooking up our [Renderer] runtime. Other\n * platforms extract this code into classes that are effectively singletons. But this approach is\n * good enough for the iOS sample.\n */\n@Suppress(\"unused\")\nfun mainViewController(rootScopeProvider: RootScopeProvider): UIViewController =\n  ComposeUIViewController {\n    // Create a single instance.\n    val templateProvider = remember {\n      rootScopeProvider.rootScope\n        .metroDependencyGraph<IosAppGraph>()\n        .templateProviderFactory\n        .createTemplateProvider()\n    }\n\n    DisposableEffect(Unit) {\n      onDispose {\n        // Cancel the provider when it's no longer needed.\n        templateProvider.cancel()\n      }\n    }\n\n    // Only a single factory is needed.\n    val factory = remember { ComposeRendererFactory(rootScopeProvider) }\n\n    // Render templates using our Renderer runtime.\n    val template by templateProvider.templates.collectAsState()\n\n    val renderer = factory.getRenderer(template::class)\n    renderer.renderCompose(template)\n  }\n"
  },
  {
    "path": "sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/Main.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.ExperimentalComposeUiApi\nimport androidx.compose.ui.window.ComposeViewport\nimport dev.zacsweers.metro.createGraphFactory\nimport kotlinx.browser.document\nimport software.amazon.app.platform.renderer.ComposeRendererFactory\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/** The entry point of our sample app. */\n@OptIn(ExperimentalComposeUiApi::class)\nfun main() {\n  ComposeViewport(checkNotNull(document.body)) { AppPlatform() }\n}\n\n@Composable\nprivate fun AppPlatform() {\n  val application = remember {\n    DemoApplication().apply { create(createGraphFactory<WasmJsAppGraph.Factory>().create(this)) }\n  }\n\n  // Create a single instance.\n  val templateProvider = remember {\n    application.rootScope\n      .metroDependencyGraph<WasmJsAppGraph>()\n      .templateProviderFactory\n      .createTemplateProvider()\n  }\n\n  DisposableEffect(Unit) {\n    onDispose {\n      // Cancel the provider when it's no longer needed.\n      templateProvider.cancel()\n    }\n  }\n\n  // Only a single factory is needed.\n  val factory = remember { ComposeRendererFactory(application) }\n\n  // Render templates using our Renderer runtime.\n  val template by templateProvider.templates.collectAsState()\n\n  val renderer = factory.getRenderer(template::class)\n  renderer.renderCompose(template)\n}\n"
  },
  {
    "path": "sample/app/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/WasmJsAppGraph.kt",
    "content": "package software.amazon.app.platform.sample\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.DependencyGraph\nimport dev.zacsweers.metro.Provides\nimport software.amazon.app.platform.scope.RootScopeProvider\n\n/**\n * The final Wasm app graph.\n *\n * Unlike the Android and iOS specific counterpart, this class doesn't have any platform specific\n * types.\n */\n@DependencyGraph(AppScope::class)\ninterface WasmJsAppGraph {\n  /** The factory to create a new instance of [WasmJsAppGraph]. */\n  @DependencyGraph.Factory\n  fun interface Factory {\n    /**\n     * Creates a new [WasmJsAppGraph] instance. [[rootScopeProvider] is provided in the\n     * [WasmJsAppGraph] and can be injected.\n     */\n    fun create(@Provides rootScopeProvider: RootScopeProvider): WasmJsAppGraph\n  }\n\n  /** Gives access to the [TemplateProvider.Factory] from the object graph. */\n  val templateProviderFactory: TemplateProvider.Factory\n}\n"
  },
  {
    "path": "sample/app/src/wasmJsMain/resources/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>App Platform</title>\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"styles.css\">\n    <script type=\"application/javascript\" src=\"sample-app.js\"></script>\n</head>\n<body>\n</body>\n</html>\n"
  },
  {
    "path": "sample/app/src/wasmJsMain/resources/styles.css",
    "content": "html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n}"
  },
  {
    "path": "sample/iosApp/Configuration/Config.xcconfig",
    "content": "TEAM_ID=\nBUNDLE_ID=software.amazon.app.platform.sample.AppPlatform\nAPP_NAME=AppPlatform\n"
  },
  {
    "path": "sample/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}"
  },
  {
    "path": "sample/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"app-icon-1024.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "sample/iosApp/iosApp/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}"
  },
  {
    "path": "sample/iosApp/iosApp/ComposeContentView.swift",
    "content": "import UIKit\nimport SwiftUI\nimport SampleApp\n\nstruct ComposeView: UIViewControllerRepresentable {\n    private var rootScopeProvider: RootScopeProvider\n\n    init(rootScopeProvider: RootScopeProvider) {\n        self.rootScopeProvider = rootScopeProvider\n    }\n\n    func makeUIViewController(context: Context) -> UIViewController {\n        MainViewControllerKt.mainViewController(rootScopeProvider: rootScopeProvider)\n    }\n\n    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}\n}\n\nstruct ComposeContentView: View {\n    var rootScopeProvider: RootScopeProvider\n\n    init(rootScopeProvider: RootScopeProvider) {\n        self.rootScopeProvider = rootScopeProvider\n    }\n\n    var body: some View {\n        ComposeView(rootScopeProvider: rootScopeProvider).ignoresSafeArea(.keyboard) // Compose has own keyboard handler\n    }\n}\n"
  },
  {
    "path": "sample/iosApp/iosApp/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>CADisableMinimumFrameDurationOnPhone</key>\n\t<true/>\n\t<key>UIApplicationSceneManifest</key>\n\t<dict>\n\t\t<key>UIApplicationSupportsMultipleScenes</key>\n\t\t<false/>\n\t</dict>\n\t<key>UILaunchScreen</key>\n\t<dict/>\n\t<key>UIRequiredDeviceCapabilities</key>\n\t<array>\n\t\t<string>armv7</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "sample/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}"
  },
  {
    "path": "sample/iosApp/iosApp/iOSApp.swift",
    "content": "import SampleApp\nimport SwiftUI\n\nclass AppDelegate: NSObject, UIApplicationDelegate, RootScopeProvider {\n\n    private let demoApplication: DemoApplication = DemoApplication()\n\n    var rootScope: Scope {\n        get {\n            demoApplication.rootScope\n        }\n    }\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {\n        demoApplication.create(appGraph: IosAppGraphKt.createIosAppGraph(application: application, rootScopeProvider: demoApplication))\n        return true\n    }\n}\n\n@main\nstruct iOSApp: App {\n\n    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n\n    var body: some Scene {\n        WindowGroup {\n            ComposeContentView(rootScopeProvider: appDelegate)\n        }\n    }\n}\n"
  },
  {
    "path": "sample/iosApp/iosApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 50;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };\n\t\t058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };\n\t\t1BACA3B135BB44908CC94158 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */; };\n\t\t1BACAC1D12A9468E4FB4B657 /* ComposeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\t1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposeContentView.swift; sourceTree = \"<group>\"; };\n\t\t1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = \"<group>\"; };\n\t\t7555FF7B242A565900829871 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tAB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXGroup section */\n\t\t058557D7273AAEEB004C7B11 /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA0F817E367AF6D6D5F7F /* iosApp.xcodeproj */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tpath = iosApp.xcodeproj;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA1D950CDDAF5CA074B34 /* rwo.xcuserdatad */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACA238344459027BE460EF /* xcschemes */,\n\t\t\t);\n\t\t\tpath = rwo.xcuserdatad;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA238344459027BE460EF /* xcschemes */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tpath = xcschemes;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA2D2F3054320AD39037F /* swiftpm */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACA384AE86938E6114C0CB /* configuration */,\n\t\t\t);\n\t\t\tpath = swiftpm;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA2DC766A57FBC214E6DB /* project.xcworkspace */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACAC2308A09D9A30F78117 /* xcshareddata */,\n\t\t\t);\n\t\t\tname = project.xcworkspace;\n\t\t\tpath = iosApp.xcodeproj/project.xcworkspace;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACA384AE86938E6114C0CB /* configuration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tpath = configuration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACAC2308A09D9A30F78117 /* xcshareddata */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACA2D2F3054320AD39037F /* swiftpm */,\n\t\t\t);\n\t\t\tpath = xcshareddata;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1BACAC4B5E700FD8C89EB3BF /* xcuserdata */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BACA1D950CDDAF5CA074B34 /* rwo.xcuserdatad */,\n\t\t\t);\n\t\t\tname = xcuserdata;\n\t\t\tpath = iosApp.xcodeproj/xcuserdata;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t42799AB246E5F90AF97AA0EF /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7555FF72242A565900829871 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAB1DB47929225F7C00F7AF9C /* Configuration */,\n\t\t\t\t7555FF7D242A565900829871 /* iosApp */,\n\t\t\t\t7555FF7C242A565900829871 /* Products */,\n\t\t\t\t42799AB246E5F90AF97AA0EF /* Frameworks */,\n\t\t\t\t1BACAC4B5E700FD8C89EB3BF /* xcuserdata */,\n\t\t\t\t1BACA2DC766A57FBC214E6DB /* project.xcworkspace */,\n\t\t\t\t1BACA0F817E367AF6D6D5F7F /* iosApp.xcodeproj */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7555FF7C242A565900829871 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t7555FF7B242A565900829871 /* .app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7555FF7D242A565900829871 /* iosApp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t058557BA273AAA24004C7B11 /* Assets.xcassets */,\n\t\t\t\t7555FF8C242A565B00829871 /* Info.plist */,\n\t\t\t\t058557D7273AAEEB004C7B11 /* Preview Content */,\n\t\t\t\t1BACA6BD71FC32091FE23841 /* ComposeContentView.swift */,\n\t\t\t\t1BACA8873D2AF36B9E0FC788 /* iOSApp.swift */,\n\t\t\t);\n\t\t\tpath = iosApp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tAB1DB47929225F7C00F7AF9C /* Configuration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tAB3632DC29227652001CCB65 /* Config.xcconfig */,\n\t\t\t);\n\t\t\tpath = Configuration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t7555FF7A242A565900829871 /* iosApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget \"iosApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tF36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,\n\t\t\t\t7555FF77242A565900829871 /* Sources */,\n\t\t\t\t7555FF79242A565900829871 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = iosApp;\n\t\t\tproductName = iosApp;\n\t\t\tproductReference = 7555FF7B242A565900829871 /* .app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t7555FF73242A565900829871 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 1130;\n\t\t\t\tLastUpgradeCheck = 1130;\n\t\t\t\tORGANIZATIONNAME = orgName;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t7555FF7A242A565900829871 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 11.3.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject \"iosApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 7555FF72242A565900829871;\n\t\t\tproductRefGroup = 7555FF7C242A565900829871 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t7555FF7A242A565900829871 /* iosApp */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t7555FF79242A565900829871 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,\n\t\t\t\t058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tF36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Compile Kotlin Framework\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"if [ \\\"YES\\\" = \\\"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\\\" ]; then\\n  echo \\\"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\\\\\"YES\\\\\\\"\\\"\\n  exit 0\\nfi\\ncd \\\"$SRCROOT/..\\\"\\n../gradlew :sample:app:embedAndSignAppleFrameworkForXcode\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t7555FF77242A565900829871 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t1BACAC1D12A9468E4FB4B657 /* ComposeContentView.swift in Sources */,\n\t\t\t\t1BACA3B135BB44908CC94158 /* iOSApp.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t7555FFA3242A565B00829871 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t7555FFA4242A565B00829871 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t7555FFA6242A565B00829871 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"iosApp/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"${TEAM_ID}\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(SRCROOT)/../app/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\\n$(SRCROOT)/../app/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = iosApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-framework\",\n\t\t\t\t\tSampleApp,\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"${BUNDLE_ID}${TEAM_ID}\";\n\t\t\t\tPRODUCT_NAME = \"${APP_NAME}\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t7555FFA7242A565B00829871 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"iosApp/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"${TEAM_ID}\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(SRCROOT)/../app/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\\n$(SRCROOT)/../ap/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = iosApp/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 14.1;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"-framework\",\n\t\t\t\t\tSampleApp,\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"${BUNDLE_ID}${TEAM_ID}\";\n\t\t\t\tPRODUCT_NAME = \"${APP_NAME}\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t7555FF76242A565900829871 /* Build configuration list for PBXProject \"iosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t7555FFA3242A565B00829871 /* Debug */,\n\t\t\t\t7555FFA4242A565B00829871 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget \"iosApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t7555FFA6242A565B00829871 /* Debug */,\n\t\t\t\t7555FFA7242A565B00829871 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 7555FF73242A565900829871 /* Project object */;\n}\n"
  },
  {
    "path": "sample/login/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n\ndependencies {\n    commonMainApi project(':sample:user:public')\n\n    commonMainImplementation compose.components.resources\n    commonMainImplementation compose.material\n\n    commonTestImplementation project(':sample:user:testing')\n\n    appleAndDesktopTestImplementation compose.uiTest\n}\n"
  },
  {
    "path": "sample/login/impl/src/appleAndDesktopTest/kotlin/software/amazon/app/platform/sample/login/LoginRendererTest.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport androidx.compose.ui.test.ExperimentalTestApi\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.runComposeUiTest\nimport kotlin.test.Test\n\n/**\n * Executes the [LoginRenderer] in unit tests for iOS and Desktop. Note that this test cannot run on\n * Android, because there the test would need to run on an emulator.\n *\n * This test highlights that `ComposeRenderers` are testable in isolation.\n */\n@ExperimentalTestApi\nclass LoginRendererTest {\n\n  @Test\n  fun `the login button is rendered when not logging in`() {\n    runComposeUiTest {\n      setContent {\n        val renderer = LoginRenderer()\n        renderer.renderCompose(LoginPresenter.Model(loginInProgress = false) {})\n      }\n\n      onNodeWithTag(\"loginProgress\").assertDoesNotExist()\n      onNodeWithTag(\"loginButton\").assertIsDisplayed()\n    }\n  }\n\n  @Test\n  fun `the progress is shown while logging in`() {\n    runComposeUiTest {\n      setContent {\n        val renderer = LoginRenderer()\n        renderer.renderCompose(LoginPresenter.Model(loginInProgress = true) {})\n      }\n\n      onNodeWithTag(\"loginProgress\").assertIsDisplayed()\n      onNodeWithTag(\"loginButton\").assertDoesNotExist()\n    }\n  }\n}\n"
  },
  {
    "path": "sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImpl.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport kotlin.random.Random\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport software.amazon.app.platform.sample.login.LoginPresenter.Model\nimport software.amazon.app.platform.sample.user.UserManager\n\n/** Production implementation for [LoginPresenter]. */\n@Inject\n@ContributesBinding(AppScope::class)\nclass LoginPresenterImpl(private val userManager: UserManager) : LoginPresenter {\n  @Composable\n  @Suppress(\"MagicNumber\")\n  override fun present(input: Unit): Model {\n    var loginInProgress by remember { mutableStateOf(false) }\n\n    if (loginInProgress) {\n      LaunchedEffect(loginInProgress) {\n        delay(1.seconds)\n        userManager.login(Random.nextLong(10_000))\n        loginInProgress = false\n      }\n    }\n\n    return Model(loginInProgress = loginInProgress) {\n      when (it) {\n        is LoginPresenter.Event.Login -> {\n          loginInProgress = true\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/login/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRenderer.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.Button\nimport androidx.compose.material.CircularProgressIndicator\nimport androidx.compose.material.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.unit.dp\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.sample.login.LoginPresenter.Model\n\n/** Renders the content for [LoginPresenter] on screen using Compose Multiplatform. */\n@ContributesRenderer\nclass LoginRenderer : ComposeRenderer<Model>() {\n  @Composable\n  override fun Compose(model: Model) {\n    Column(\n      modifier = Modifier.fillMaxSize(),\n      verticalArrangement = Arrangement.Center,\n      horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n      if (model.loginInProgress) {\n        CircularProgressIndicator(modifier = Modifier.width(64.dp).testTag(\"loginProgress\"))\n      } else {\n        Button(\n          onClick = { model.onEvent(LoginPresenter.Event.Login(\"Imagine a text field\")) },\n          modifier = Modifier.testTag(\"loginButton\"),\n        ) {\n          Text(\"Login\")\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/login/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/login/LoginPresenterImplTest.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport assertk.assertThat\nimport assertk.assertions.isFalse\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport assertk.assertions.isTrue\nimport kotlin.test.Test\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.molecule.test\nimport software.amazon.app.platform.sample.user.FakeUserManager\n\nclass LoginPresenterImplTest {\n\n  @Test\n  fun `after 1 second the user is logged in after pressing the login button`() = runTest {\n    val userManager = FakeUserManager()\n    LoginPresenterImpl(userManager).test(this) {\n      awaitItem().let { model ->\n        assertThat(model.loginInProgress).isFalse()\n        model.onEvent(LoginPresenter.Event.Login(\"userName\"))\n      }\n\n      assertThat(awaitItem().loginInProgress).isTrue()\n      assertThat(userManager.user.value).isNull()\n\n      advanceTimeBy(1.seconds + 1.milliseconds)\n\n      assertThat(awaitItem().loginInProgress).isFalse()\n      assertThat(userManager.user.value).isNotNull()\n    }\n  }\n}\n"
  },
  {
    "path": "sample/login/impl-robots/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n}\n"
  },
  {
    "path": "sample/login/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginRobot.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.performClick\nimport dev.zacsweers.metro.AppScope\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.ComposeRobot\n\n/** A test robot to verify interactions with the login screen written with Compose Multiplatform. */\n@ContributesRobot(AppScope::class)\nclass LoginRobot : ComposeRobot() {\n\n  private val loginButtonNode\n    get() = compose.onNodeWithTag(\"loginButton\")\n\n  /** Verify that login button is displayed. */\n  fun seeLoginButton() {\n    loginButtonNode.assertIsDisplayed()\n  }\n\n  /** Clicks the login button and starts the login process. */\n  fun clickLoginButton() {\n    loginButtonNode.performClick()\n  }\n}\n"
  },
  {
    "path": "sample/login/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n"
  },
  {
    "path": "sample/login/public/src/commonMain/kotlin/software/amazon/app/platform/sample/login/LoginPresenter.kt",
    "content": "package software.amazon.app.platform.sample.login\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/** A presenter to render the login screen. */\ninterface LoginPresenter : MoleculePresenter<Unit, LoginPresenter.Model> {\n  /** The state of the login screen. */\n  data class Model(\n    /** Whether login is currently in progress. */\n    val loginInProgress: Boolean,\n\n    /** Callback to send events back to the presenter. */\n    val onEvent: (Event) -> Unit,\n  ) : BaseModel\n\n  /** All events that [LoginPresenter] can process. */\n  sealed interface Event {\n    /** Sent when the user presses the login button with the entered [userName]. */\n    data class Login(val userName: String) : Event\n  }\n}\n"
  },
  {
    "path": "sample/navigation/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n\ndependencies {\n    commonMainApi project(':sample:login:public')\n    commonMainApi project(':sample:user:public')\n\n    commonTestImplementation project(':sample:user:testing')\n}\n"
  },
  {
    "path": "sample/navigation/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImpl.kt",
    "content": "package software.amazon.app.platform.sample.navigation\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.Provider\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.sample.login.LoginPresenter\nimport software.amazon.app.platform.sample.user.UserManager\nimport software.amazon.app.platform.sample.user.UserPagePresenter\nimport software.amazon.app.platform.sample.user.UserScope\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\n/**\n * Production implementation of [NavigationPresenter].\n *\n * [loginPresenter] is injected lazily to delay initialization until it's actually needed. See\n * [MoleculePresenter] for more details.\n */\n@Inject\n@ContributesBinding(AppScope::class)\nclass NavigationPresenterImpl(\n  private val userManager: UserManager,\n  private val loginPresenter: Provider<LoginPresenter>,\n) : NavigationPresenter {\n\n  @Composable\n  override fun present(input: Unit): BaseModel {\n    val scope = getUserScope()\n    if (scope == null) {\n      // If no user is logged in, then show the logged in screen.\n      val presenter = remember { loginPresenter() }\n      return presenter.present(Unit)\n    }\n\n    // A user is logged in. Use the user graph to get an instance of UserPagePresenter, which is\n    // only\n    // part of the user scope.\n    val userPresenter = remember(scope) { scope.metroDependencyGraph<UserGraph>().userPresenter }\n    return userPresenter.present(Unit)\n  }\n\n  @Composable\n  private fun getUserScope(): Scope? {\n    val user by userManager.user.collectAsState()\n    return if (user?.scope?.isDestroyed() == true) null else user?.scope\n  }\n\n  /**\n   * This graph interface gives us access to objects from the user scope. We cannot inject\n   * `UserPresenter` in the constructor, because it's part of the user scope.\n   */\n  @ContributesTo(UserScope::class)\n  interface UserGraph {\n    /** The [UserPagePresenter] provided by the user scope. */\n    val userPresenter: UserPagePresenter\n  }\n}\n"
  },
  {
    "path": "sample/navigation/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenterImplTest.kt",
    "content": "package software.amazon.app.platform.sample.navigation\n\nimport androidx.compose.runtime.Composable\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport kotlin.test.Test\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.test\nimport software.amazon.app.platform.sample.login.LoginPresenter\nimport software.amazon.app.platform.sample.user.FakeUserManager\nimport software.amazon.app.platform.sample.user.UserPagePresenter\n\nclass NavigationPresenterImplTest {\n\n  @Test\n  fun `after login the presenter navigates from the login screen to the user page screen`() =\n    runTest {\n      val userManager = FakeUserManager()\n\n      val presenter =\n        NavigationPresenterImpl(\n          userManager = userManager,\n          loginPresenter = { FakeLoginPresenter() },\n        )\n\n      presenter.test(this) {\n        assertThat(awaitItem()).isInstanceOf<LoginPresenter.Model>()\n\n        userManager.login(\n          userId = 1L,\n          scope = this@runTest,\n          graph =\n            object : NavigationPresenterImpl.UserGraph {\n              override val userPresenter: UserPagePresenter\n                get() = FakeUserPagePresenter()\n            },\n        )\n\n        assertThat(awaitItem()).isInstanceOf<UserPagePresenter.Model>()\n      }\n    }\n\n  private class FakeLoginPresenter : LoginPresenter {\n    @Composable\n    override fun present(input: Unit): LoginPresenter.Model =\n      LoginPresenter.Model(loginInProgress = false) {}\n  }\n\n  private class FakeUserPagePresenter : UserPagePresenter {\n    @Composable\n    override fun present(input: Unit): UserPagePresenter.Model =\n      object : UserPagePresenter.Model {\n        override val listModel: BaseModel = object : BaseModel {}\n        override val detailModel: BaseModel = object : BaseModel {}\n      }\n  }\n}\n"
  },
  {
    "path": "sample/navigation/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n"
  },
  {
    "path": "sample/navigation/public/src/commonMain/kotlin/software/amazon/app/platform/sample/navigation/NavigationPresenter.kt",
    "content": "package software.amazon.app.platform.sample.navigation\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\n\n/**\n * A presenter that hosts other presenters and returns their models. For that reason this presenter\n * doesn't have its own [BaseModel] type and returns [BaseModel].\n */\ninterface NavigationPresenter : MoleculePresenter<Unit, BaseModel>\n"
  },
  {
    "path": "sample/templates/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n\ndependencies {\n    androidMainImplementation libs.androidx.constraintlayout\n}\n"
  },
  {
    "path": "sample/templates/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/template/AndroidSampleAppTemplateRenderer.kt",
    "content": "package software.amazon.app.platform.sample.template\n\nimport android.app.Activity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.template.AndroidTemplateRenderer\nimport software.amazon.app.platform.sample.templates.impl.R\n\n/** An Android renderer implementation for templates used in the sample application. */\n// This implementation is disabled and we always use ComposeSampleAppTemplateRenderer.\n// The Android sample app could use this renderer if desired.\n// @Inject\n// @ContributesRenderer\nclass AndroidSampleAppTemplateRenderer(rendererFactory: RendererFactory) :\n  AndroidTemplateRenderer<SampleAppTemplate>(rendererFactory) {\n\n  companion object {\n    private const val ELEVATION = 24f\n  }\n\n  private lateinit var fullScreenContainer: Container\n  private lateinit var listContainer: Container\n  private lateinit var detailContainer: Container\n\n  private lateinit var rootView: ConstraintLayout\n\n  override fun inflate(\n    activity: Activity,\n    parent: ViewGroup,\n    layoutInflater: LayoutInflater,\n    initialModel: SampleAppTemplate,\n  ): View {\n    return layoutInflater.inflate(R.layout.sample_app_template_root, parent, false).also {\n      rootView = it as ConstraintLayout\n\n      fullScreenContainer = Container(activity, it.findViewById(R.id.full_screen_container), null)\n\n      it.findViewById<ViewGroup>(R.id.list).apply {\n        this.clipToOutline = true\n        this.elevation = ELEVATION\n        listContainer = Container(activity, this, null)\n      }\n\n      detailContainer = Container(activity, it.findViewById(R.id.detail), null)\n    }\n  }\n\n  override fun renderModel(model: SampleAppTemplate) {\n    when (model) {\n      is SampleAppTemplate.FullScreenTemplate -> {\n        renderFullScreenTemplate(model)\n      }\n\n      is SampleAppTemplate.ListDetailTemplate -> {\n        renderListDetailTemplate(model)\n      }\n    }\n  }\n\n  private fun renderFullScreenTemplate(template: SampleAppTemplate.FullScreenTemplate) {\n    listContainer.reset()\n    detailContainer.reset()\n\n    fullScreenContainer.renderModel(template.model)\n  }\n\n  private fun renderListDetailTemplate(template: SampleAppTemplate.ListDetailTemplate) {\n    fullScreenContainer.reset()\n\n    listContainer.renderModel(template.list)\n    detailContainer.renderModel(template.detail)\n  }\n}\n"
  },
  {
    "path": "sample/templates/impl/src/androidMain/res/layout/sample_app_template_root.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/full_screen_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\" />\n\n    <FrameLayout\n        android:id=\"@+id/list\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"@id/list_end_guideline\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        />\n\n    <FrameLayout\n        android:id=\"@+id/detail\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintStart_toEndOf=\"@id/list_end_guideline\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"/>\n\n    <androidx.constraintlayout.widget.Guideline\n        android:id=\"@+id/list_end_guideline\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintGuide_percent=\"0.3\"\n        tools:viewBindingType=\"android.view.View\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "sample/templates/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/template/ComposeSampleAppTemplateRenderer.kt",
    "content": "package software.amazon.app.platform.sample.template\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.animation.ExperimentalSharedTransitionApi\nimport androidx.compose.animation.SharedTransitionLayout\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.safeDrawing\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.ui.Modifier\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.ForwardBackPressEventsToPresenters\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.renderer.RendererFactory\nimport software.amazon.app.platform.renderer.getComposeRenderer\nimport software.amazon.app.platform.sample.template.animation.LocalAnimatedVisibilityScope\nimport software.amazon.app.platform.sample.template.animation.LocalSharedTransitionScope\n\n/**\n * A Compose renderer implementation for templates used in the sample application.\n *\n * [rendererFactory] is used to get the [Renderer] for the [BaseModel] wrapped in the template.\n */\n@OptIn(ExperimentalSharedTransitionApi::class)\n@Inject\n@ContributesRenderer\nclass ComposeSampleAppTemplateRenderer(\n  private val rendererFactory: RendererFactory,\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n) : ComposeRenderer<SampleAppTemplate>() {\n\n  @Composable\n  override fun Compose(model: SampleAppTemplate) {\n    backGestureDispatcherPresenter.ForwardBackPressEventsToPresenters()\n\n    Box(Modifier.windowInsetsPadding(WindowInsets.safeDrawing)) {\n      // Wrap all the the UI in a SharedTransitionLayout and AnimatedContent to support\n      // shared element transitions across template updates. The scopes are exposed through\n      // composition locals as suggested here:\n      // https://developer.android.com/develop/ui/compose/animation/shared-elements#understand-scopes\n      SharedTransitionLayout {\n        CompositionLocalProvider(LocalSharedTransitionScope provides this) {\n          AnimatedContent(\n            targetState = model,\n            label = \"Top level AnimatedContent\",\n            contentKey = { template ->\n              // Use the key from AnimationContentKey as indicator when content has changed\n              // that needs to be animated. If this key is doesn't change (the default behavior),\n              // then no animation occurs.\n              template.contentKey\n            },\n          ) { template ->\n            CompositionLocalProvider(LocalAnimatedVisibilityScope provides this) {\n              when (template) {\n                is SampleAppTemplate.FullScreenTemplate -> FullScreen(template)\n                is SampleAppTemplate.ListDetailTemplate -> ListDetail(template)\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  @Composable\n  private fun FullScreen(template: SampleAppTemplate.FullScreenTemplate) {\n    val renderer = rendererFactory.getComposeRenderer(template.model)\n    renderer.renderCompose(template.model)\n  }\n\n  @Composable\n  private fun ListDetail(template: SampleAppTemplate.ListDetailTemplate) {\n    Row {\n      Column(Modifier.weight(1f)) {\n        rendererFactory.getComposeRenderer(template.list).renderCompose(template.list)\n      }\n      Column(Modifier.weight(2f)) {\n        rendererFactory.getComposeRenderer(template.detail).renderCompose(template.detail)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/templates/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n"
  },
  {
    "path": "sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplate.kt",
    "content": "package software.amazon.app.platform.sample.template\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.template.Template\nimport software.amazon.app.platform.sample.template.animation.AnimationContentKey\nimport software.amazon.app.platform.sample.template.animation.AnimationContentKey.Companion.contentKey\n\n/** All [Template]s implemented in the sample application. */\nsealed interface SampleAppTemplate : Template, AnimationContentKey {\n  /** A template that hosts a single model, which should rendered as full-screen element. */\n  data class FullScreenTemplate(\n    /** The model to be rendered fullscreen. */\n    val model: BaseModel\n  ) : SampleAppTemplate {\n    override val contentKey: Int\n      get() = model.contentKey\n  }\n\n  /**\n   * A template that hosts two models, these can be rendered in different configurations, at the\n   * discretion of the [Template]'s `Renderer`. These two models are meant to be related to each\n   * other through the list model's selection state, which influences the data in the detail model.\n   */\n  data class ListDetailTemplate(\n    /**\n     * The list model. Typically rendered on less screen real estate and is meant to be used to show\n     * a high level overview of some data.\n     */\n    val list: BaseModel,\n\n    /**\n     * The detail model. Typically rendered on more screen real estate than the list model and is\n     * meant to be used to show more detailed information.\n     */\n    val detail: BaseModel,\n  ) : SampleAppTemplate {\n    override val contentKey: Int\n      // Multiply by 31 to avoid collisions in the sum, e.g. when list changes from 0 to 1 and\n      // detail changes from 1 to 0 at teh same time.\n      get() = list.contentKey * 31 + detail.contentKey\n  }\n}\n"
  },
  {
    "path": "sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenter.kt",
    "content": "package software.amazon.app.platform.sample.template\n\nimport androidx.compose.runtime.Composable\nimport dev.zacsweers.metro.Assisted\nimport dev.zacsweers.metro.AssistedFactory\nimport dev.zacsweers.metro.AssistedInject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.LocalBackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.returningCompositionLocalProvider\nimport software.amazon.app.platform.presenter.template.ModelDelegate\nimport software.amazon.app.platform.presenter.template.toTemplate\n\n/**\n * A presenter that wraps any other presenter and turns the emitted models from the other presenter\n * into [SampleAppTemplate]s.\n *\n * Inject [Factory] to create a new instance of [SampleAppTemplatePresenter].\n */\n@AssistedInject\nclass SampleAppTemplatePresenter(\n  private val backGestureDispatcherPresenter: BackGestureDispatcherPresenter,\n  @Assisted private val rootPresenter: MoleculePresenter<Unit, *>,\n) : MoleculePresenter<Unit, SampleAppTemplate> {\n  @Composable\n  override fun present(input: Unit): SampleAppTemplate {\n    return returningCompositionLocalProvider(\n      LocalBackGestureDispatcherPresenter provides backGestureDispatcherPresenter\n    ) {\n      rootPresenter.present(Unit).toTemplate<SampleAppTemplate> {\n        SampleAppTemplate.FullScreenTemplate(it)\n      }\n    }\n  }\n\n  /** A factory to instantiate a new [SampleAppTemplatePresenter] instance. */\n  @AssistedFactory\n  fun interface Factory {\n    /**\n     * Create a new [SampleAppTemplatePresenter]. The given [presenter] will be wrapped and its\n     * models are transformed into a [SampleAppTemplate] with [SampleAppTemplate.FullScreenTemplate]\n     * as default. The given [presenter] can override the template by either returning\n     * [SampleAppTemplate] directly or making its [BaseModel] type implement [ModelDelegate].\n     */\n    fun createSampleAppTemplatePresenter(\n      rootPresenter: MoleculePresenter<Unit, *>\n    ): SampleAppTemplatePresenter\n  }\n}\n"
  },
  {
    "path": "sample/templates/public/src/commonMain/kotlin/software/amazon/app/platform/sample/template/animation/AnimationContentKey.kt",
    "content": "package software.amazon.app.platform.sample.template.animation\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.animation.AnimatedVisibilityScope\nimport androidx.compose.animation.ExperimentalSharedTransitionApi\nimport androidx.compose.animation.SharedTransitionLayout\nimport androidx.compose.animation.SharedTransitionScope\nimport androidx.compose.runtime.compositionLocalOf\nimport software.amazon.app.platform.presenter.BaseModel\n\n/**\n * The sample application supports animations between models and templates. [BaseModel] classes can\n * implement this interface to indicate when a change occurred that should be animated. [contentKey]\n * represents a unique value for an animation state. If the value doesn't change between new models,\n * then no animation will be started.\n *\n * An example may look like this:\n * ```\n * data class Model(\n *     val showPictureFullscreen: Boolean,\n *     ...\n * ) : BaseModel, AnimationContentKey {\n *     override val contentKey: Int =\n *         if (showPictureFullscreen) 1 else AnimationContentKey.DEFAULT_CONTENT_KEY\n * }\n * ```\n *\n * In this sample when `showPictureFullscreen` changes from `true` to `false` and vice versa then an\n * animation will be started using [AnimatedContent]. Use [LocalAnimatedVisibilityScope] and\n * [LocalSharedTransitionScope] to get access to the right scopes.\n */\ninterface AnimationContentKey {\n  /**\n   * [contentKey] represents a unique value for an animation state. See [AnimatedContent] for more\n   * details.\n   */\n  val contentKey: Int\n\n  companion object {\n    /**\n     * The default value for [AnimationContentKey.contentKey], highlighting that no animation should\n     * occur.\n     */\n    const val DEFAULT_CONTENT_KEY = 0\n\n    /**\n     * Return [AnimationContentKey.contentKey] for any [BaseModel] instance no matter whether the\n     * [AnimationContentKey] was implemented.\n     */\n    val BaseModel.contentKey: Int\n      get() = (this as? AnimationContentKey)?.contentKey ?: DEFAULT_CONTENT_KEY\n  }\n}\n\n/**\n * All UI composable functions for renderers in the sample application are wrapped within a\n * [AnimatedContent]. This composition local gives access to this wrapper instance to run a shared\n * element transition. For more information see the the\n * [shared element transition documentation](https://developer.android.com/develop/ui/compose/animation/shared-elements#shared-bounds).\n *\n * The [BaseModel] must implement [AnimationContentKey] to indicate that an animation should be\n * played. See [AnimationContentKey] for more details.\n */\nval LocalAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }\n\n/**\n * All UI composable functions for renderers in the sample application are wrapped within a\n * [SharedTransitionLayout]. This composition local gives access to this wrapper instance to run a\n * shared element transition. For more information see the the\n * [shared element transition documentation](https://developer.android.com/develop/ui/compose/animation/shared-elements#shared-bounds).\n *\n * The [BaseModel] must implement [AnimationContentKey] to indicate that an animation should be\n * played. See [AnimationContentKey] for more details.\n */\n@OptIn(ExperimentalSharedTransitionApi::class)\nval LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }\n"
  },
  {
    "path": "sample/templates/public/src/commonTest/kotlin/software/amazon/app/platform/sample/template/SampleAppTemplatePresenterTest.kt",
    "content": "package software.amazon.app.platform.sample.template\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport assertk.assertThat\nimport assertk.assertions.isInstanceOf\nimport assertk.assertions.isSameInstanceAs\nimport kotlin.test.Test\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackGestureDispatcherPresenter\nimport software.amazon.app.platform.presenter.molecule.test\nimport software.amazon.app.platform.presenter.template.ModelDelegate\nimport software.amazon.app.platform.presenter.template.Template\nimport software.amazon.app.platform.sample.template.SampleAppTemplate.FullScreenTemplate\nimport software.amazon.app.platform.sample.template.SampleAppTemplatePresenterTest.TestPresenter.Model\n\nclass SampleAppTemplatePresenterTest {\n\n  @Test\n  fun `the template provided by the injected presenter is returned`() = runTest {\n    val trigger = MutableStateFlow<Template?>(null)\n    val testPresenter = TestPresenter(trigger)\n    val expectedTemplate = FullScreenTemplate(object : BaseModel {})\n\n    SampleAppTemplatePresenter(BackGestureDispatcherPresenter.createNewInstance(), testPresenter)\n      .test(this) {\n        val defaultFullScreenTemplate = awaitItem() as FullScreenTemplate\n        assertThat(defaultFullScreenTemplate.model).isInstanceOf<Model>()\n\n        trigger.value = expectedTemplate\n        assertThat(awaitItem()).isSameInstanceAs(expectedTemplate)\n      }\n  }\n\n  private class TestPresenter(private val trigger: StateFlow<Template?>) :\n    MoleculePresenter<Unit, Model> {\n    @Composable\n    override fun present(input: Unit): Model {\n      return Model(trigger.collectAsState().value)\n    }\n\n    data class Model(private val delegate: Template?) : BaseModel, ModelDelegate {\n      override fun delegate(): BaseModel {\n        return delegate ?: this\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n\ndependencies {\n    commonMainApi project(':sample:templates:public')\n\n    commonMainImplementation compose.components.resources\n    commonMainImplementation compose.material\n\n    commonTestImplementation project(':sample:user:testing')\n}\n"
  },
  {
    "path": "sample/user/impl/src/androidMain/kotlin/software/amazon/app/platform/sample/user/AndroidAnimationsHelper.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport android.app.Application\nimport android.provider.Settings\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\n\n/**\n * Android implementation of [AnimationHelper] which queries the device state to determine whether\n * animations are enabled.\n */\n@Inject\n@ContributesBinding(AppScope::class)\nclass AndroidAnimationsHelper(private val application: Application) : AnimationHelper {\n  override fun isAnimationsEnabled(): Boolean {\n    val duration =\n      Settings.Global.getFloat(\n        application.contentResolver,\n        Settings.Global.ANIMATOR_DURATION_SCALE,\n        1f,\n      )\n\n    return duration > 0f\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/appleAndDesktopMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\n\n/**\n * Default implementation of [AnimationHelper] that always keeps animations enabled. This\n * implementation is used for iOS and Desktop.\n */\n@Inject\n@ContributesBinding(AppScope::class)\nclass DefaultAnimationsHelper : AnimationHelper {\n  override fun isAnimationsEnabled(): Boolean = true\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/AnimationHelper.kt",
    "content": "package software.amazon.app.platform.sample.user\n\n/**\n * Helper class to determine whether animations are enabled. This helps with flaky UI tests.\n *\n * The interesting part is that this interface is part of the `:impl` module, because it doesn't\n * need to be shared with other modules. The implementations live in the platform specific folders\n * like `androidMain`. Metro will use the right implementation on each platform automatically.\n */\ninterface AnimationHelper {\n\n  /** Whether animations are enabled, e.g. on Android they're turned off during UI tests. */\n  fun isAnimationsEnabled(): Boolean\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/SessionTimeout.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.SingleIn\nimport kotlin.time.Duration\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.update\nimport software.amazon.app.platform.inject.metro.ContributesScoped\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.launch\n\n/**\n * This class logs out the user after a certain delay.\n *\n * The important part of this class is that it implements [Scoped] and it's part of the [UserScope].\n * That means when the user logs in, this class gets automatically instantiated and [onEnterScope]\n * will be called. [onExitScope] will be called on the `CoroutineScope` will be destroyed when the\n * user logs out.\n */\n@Inject\n@SingleIn(UserScope::class)\n@ContributesScoped(UserScope::class)\nclass SessionTimeout(private val userManager: UserManager, animationHelper: AnimationHelper) :\n  Scoped {\n\n  private val _sessionTimeout = MutableStateFlow(initialTimeout)\n\n  /** The remaining time until the user is logged out. */\n  @Suppress(\"MemberNameEqualsClassName\") val sessionTimeout: StateFlow<Duration> = _sessionTimeout\n\n  private val updateDelay =\n    if (animationHelper.isAnimationsEnabled()) {\n      10.milliseconds\n    } else {\n      1.seconds\n    }\n\n  override fun onEnterScope(scope: Scope) {\n    // This job will be automatically canceled when the user logs out and the user scope is\n    // destroyed.\n    scope.launch {\n      while (userManager.user.value != null) {\n        delay(updateDelay)\n\n        _sessionTimeout.update { (it - updateDelay).coerceAtLeast(Duration.ZERO) }\n      }\n    }\n\n    scope.launch {\n      sessionTimeout.first { it == Duration.ZERO }\n      userManager.logout()\n    }\n  }\n\n  /** Reset the session timeout to the initial value. */\n  fun resetTimeout() {\n    _sessionTimeout.value = initialTimeout\n  }\n\n  companion object {\n    /** The timeout after which the user will be logged out automatically. */\n    val initialTimeout = 10.seconds\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserGraph.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesTo\nimport dev.zacsweers.metro.ForScope\nimport dev.zacsweers.metro.GraphExtension\nimport dev.zacsweers.metro.Multibinds\nimport dev.zacsweers.metro.Provides\nimport dev.zacsweers.metro.SingleIn\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.SupervisorJob\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.IoCoroutineDispatcher\n\n/** The Metro graph for the user scope. This is a graph extension of the AppScope graph. */\n@GraphExtension(UserScope::class)\ninterface UserGraph {\n  /**\n   * The factory instantiates a new instance of [UserGraph]. This interface will be implemented by\n   * the AppScope graph.\n   */\n  @GraphExtension.Factory\n  @ContributesTo(AppScope::class)\n  interface Factory {\n    /**\n     * Creates a new instance of [UserGraph]. The provided [user] argument will be added to the\n     * graph and the [User] can be injected in the classes part of the [UserScope].\n     */\n    fun createUserGraph(@Provides user: User): UserGraph\n  }\n\n  /** All [Scoped] instances part of the user scope. */\n  @ForScope(UserScope::class) @Multibinds(allowEmpty = true) val userScopedInstances: Set<Scoped>\n\n  /** The coroutine scope that runs as long as the user scope is alive. */\n  @ForScope(UserScope::class) val userScopeCoroutineScopeScoped: CoroutineScopeScoped\n\n  /**\n   * Provides the [CoroutineScopeScoped] for the user scope. This is a single instance for the user\n   * scope.\n   */\n  @Provides\n  @SingleIn(UserScope::class)\n  @ForScope(UserScope::class)\n  fun provideUserScopeCoroutineScopeScoped(\n    @IoCoroutineDispatcher dispatcher: CoroutineDispatcher\n  ): CoroutineScopeScoped {\n    return CoroutineScopeScoped(dispatcher + SupervisorJob() + CoroutineName(\"UserScope\"))\n  }\n\n  /**\n   * Provides the [CoroutineScope] for the user scope. A new child scope is created every time an\n   * instance is injected so that the parent cannot be canceled accidentally.\n   */\n  @Provides\n  @ForScope(UserScope::class)\n  fun provideUserCoroutineScope(\n    @ForScope(UserScope::class) userScopeCoroutineScopeScoped: CoroutineScopeScoped\n  ): CoroutineScope {\n    return userScopeCoroutineScopeScoped.createChild()\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserImpl.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport software.amazon.app.platform.scope.Scope\n\n/** Production implementation of [User]. This is a data class for equals() and hashcode(). */\ninternal data class UserImpl(\n  override val userId: Long,\n  override val attributes: List<User.Attribute>,\n) : User {\n  @Suppress(\"DataClassShouldBeImmutable\")\n  override lateinit var scope: Scope\n    internal set\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManagerImpl.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport app_platform.sample.user.impl.generated.resources.Res\nimport app_platform.sample.user.impl.generated.resources.allDrawableResources\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport dev.zacsweers.metro.SingleIn\nimport kotlin.random.Random\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport org.jetbrains.compose.resources.ExperimentalResourceApi\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\nimport software.amazon.app.platform.scope.register\n\n/**\n * Production implementation of [UserManager].\n *\n * This class is responsible for creating the [UserScope] and [UserGraph].\n */\n@Inject\n@SingleIn(AppScope::class)\n@ContributesBinding(AppScope::class)\nclass UserManagerImpl(\n  private val rootScopeProvider: RootScopeProvider,\n  private val userGraphFactory: UserGraph.Factory,\n) : UserManager {\n\n  private val _user = MutableStateFlow<User?>(null)\n  override val user: StateFlow<User?> = _user\n\n  override fun login(userId: Long) {\n    logout()\n\n    val user =\n      UserImpl(\n        userId = userId,\n        attributes =\n          listOf(\n            User.Attribute(\"First Name\", firstName()),\n            User.Attribute(\"Last Name\", lastName()),\n            User.Attribute(\"Age\", age()),\n            User.Attribute(User.Attribute.PICTURE_KEY, profilePicture(), metadata = true),\n          ),\n      )\n\n    val userGraph = userGraphFactory.createUserGraph(user)\n\n    val userScope =\n      rootScopeProvider.rootScope.buildChild(\"user-$userId\") {\n        addMetroDependencyGraph(userGraph)\n\n        addCoroutineScopeScoped(userGraph.userScopeCoroutineScopeScoped)\n      }\n\n    user.scope = userScope\n\n    _user.value = user\n\n    // Register instances after the userScope has been set to avoid race conditions for Scoped\n    // instances that may use the userScope.\n    userScope.register(userGraph.userScopedInstances)\n  }\n\n  override fun logout() {\n    val currentUserScope = user.value?.scope\n\n    _user.value = null\n\n    currentUserScope?.destroy()\n  }\n\n  private fun firstName(): String {\n    return listOf(\"Jim\", \"Andrea\", \"Alan\", \"Alice\").random()\n  }\n\n  private fun lastName(): String {\n    return listOf(\"Lee\", \"Smith\", \"Anderson\", \"Miller\").random()\n  }\n\n  @Suppress(\"MagicNumber\")\n  private fun age(): String {\n    return Random.nextInt(100).toString()\n  }\n\n  @OptIn(ExperimentalResourceApi::class)\n  private fun profilePicture(): String {\n    val keys = Res.allDrawableResources.keys.toList()\n    return if (Random.nextBoolean()) keys[0] else keys[1]\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailPresenter.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.State\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.produceState\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport dev.zacsweers.metro.Inject\nimport kotlin.time.Duration\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.sample.template.animation.AnimationContentKey\nimport software.amazon.app.platform.sample.user.UserPageDetailPresenter.Input\nimport software.amazon.app.platform.sample.user.UserPageDetailPresenter.Model\n\n/** Presenter to manage the detail content of the list-detail layout. */\n@Inject\nclass UserPageDetailPresenter(private val sessionTimeout: SessionTimeout) :\n  MoleculePresenter<Input, Model> {\n\n  @Composable\n  override fun present(input: Input): Model {\n    var showPictureFullscreen by remember { mutableStateOf(false) }\n\n    val timeoutProgress =\n      produceState(getSessionTimeoutProgress(sessionTimeout.sessionTimeout.value)) {\n        sessionTimeout.sessionTimeout.collect { timeout ->\n          value = getSessionTimeoutProgress(timeout)\n        }\n      }\n\n    return Model(\n      text = input.user.attributes[input.selectedAttribute].value,\n      pictureKey = input.user.attributes.single { it.key == User.Attribute.PICTURE_KEY }.value,\n      timeoutProgress = timeoutProgress,\n      showPictureFullscreen = showPictureFullscreen,\n    ) {\n      when (it) {\n        Event.ProfilePictureClick -> {\n          showPictureFullscreen = !showPictureFullscreen\n        }\n      }\n    }\n  }\n\n  private fun getSessionTimeoutProgress(timeout: Duration): Float {\n    return (timeout / SessionTimeout.initialTimeout).toFloat()\n  }\n\n  /** The state of the detail pane. */\n  data class Model(\n    /** The text rendered on screen. Usually, refers to the selected user attribute. */\n    val text: String,\n    /** The profile picture ID loaded from the resources. */\n    val pictureKey: String,\n    /**\n     * The progress until when current user is logged out. The value is between [0, 1].\n     *\n     * Note that this property is a composable [State]. Updates of this value are not propagated\n     * through a new [Model] and consumers must observe the value directly instead. In a Compose UI\n     * layer this is trivial, because the Compose runtime does this automatically.\n     *\n     * Using this approach for the timeout is much more efficient, because the value changes every\n     * 10 milliseconds and going through the whole presenter tree to compute a new model and\n     * updating all renderers adds a lot of load. With this approach only the necessary Composable\n     * UI element is updated.\n     */\n    val timeoutProgress: State<Float>,\n    /** If this value is true, then the profile picture is shown in full screen. */\n    val showPictureFullscreen: Boolean,\n    /** Callback to send events back to the presenter. */\n    val onEvent: (Event) -> Unit,\n  ) : BaseModel, AnimationContentKey {\n    override val contentKey: Int =\n      if (showPictureFullscreen) 1 else AnimationContentKey.DEFAULT_CONTENT_KEY\n  }\n\n  /** All events that [UserPageDetailPresenter] can process. */\n  sealed interface Event {\n    /** Sent when the user taps on the profile picture. */\n    data object ProfilePictureClick : Event\n  }\n\n  /**\n   * The input type of the presenter. [user] is the currently logged in user. [selectedAttribute] is\n   * the index of the selected attribute in the UI.\n   */\n  data class Input(val user: User, val selectedAttribute: Int)\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageDetailRenderer.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.animation.ExperimentalSharedTransitionApi\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.LinearProgressIndicator\nimport androidx.compose.material.MaterialTheme\nimport androidx.compose.material.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport app_platform.sample.user.impl.generated.resources.Res\nimport app_platform.sample.user.impl.generated.resources.allDrawableResources\nimport org.jetbrains.compose.resources.painterResource\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.sample.template.animation.LocalAnimatedVisibilityScope\nimport software.amazon.app.platform.sample.template.animation.LocalSharedTransitionScope\nimport software.amazon.app.platform.sample.user.UserPageDetailPresenter.Model\n\n/** Renders the content for [UserPageDetailPresenter] on screen using Compose Multiplatform. */\n@OptIn(ExperimentalSharedTransitionApi::class)\n@ContributesRenderer\nclass UserPageDetailRenderer : ComposeRenderer<Model>() {\n\n  @Composable\n  override fun Compose(model: Model) {\n    if (model.showPictureFullscreen) {\n      ProfilePicture(model)\n    } else {\n      ProfileDetails(model)\n    }\n  }\n\n  @Composable\n  private fun ProfileDetails(model: Model) {\n    Column(modifier = Modifier.fillMaxWidth().fillMaxHeight()) {\n      LinearProgressIndicator(\n        progress = model.timeoutProgress.value,\n        modifier = Modifier.fillMaxWidth(),\n      )\n\n      with(checkNotNull(LocalSharedTransitionScope.current)) {\n        Image(\n          painter = painterResource(Res.allDrawableResources.getValue(model.pictureKey)),\n          contentDescription = \"Profile picture\",\n          modifier =\n            Modifier.padding(start = 64.dp, top = 16.dp, end = 64.dp)\n              .testTag(\"profilePicture\")\n              .sharedElement(\n                rememberSharedContentState(key = PROFILE_PICTURE_KEY),\n                animatedVisibilityScope = checkNotNull(LocalAnimatedVisibilityScope.current),\n                clipInOverlayDuringTransition = OverlayClip(CircleShape),\n              )\n              .shadow(\n                elevation = 16.dp,\n                shape = CircleShape,\n                clip = false,\n                ambientColor = MaterialTheme.colors.primary,\n                spotColor = MaterialTheme.colors.primary,\n              )\n              .clip(CircleShape) // clip to the circle shape\n              .clickable { model.onEvent(UserPageDetailPresenter.Event.ProfilePictureClick) }\n              .border(2.dp, MaterialTheme.colors.primary, shape = CircleShape),\n        )\n\n        AnimatedContent(targetState = model.text) { text ->\n          Text(\n            text = text,\n            style = MaterialTheme.typography.h6,\n            textAlign = TextAlign.Center,\n            modifier = Modifier.fillMaxWidth().padding(16.dp),\n          )\n        }\n      }\n    }\n  }\n\n  @Composable\n  private fun ProfilePicture(model: Model) {\n    with(checkNotNull(LocalSharedTransitionScope.current)) {\n      Row(Modifier.background(Color.Black).fillMaxSize()) {\n        Image(\n          painter = painterResource(Res.allDrawableResources.getValue(model.pictureKey)),\n          contentDescription = \"Profile picture\",\n          modifier =\n            Modifier.clickable { model.onEvent(UserPageDetailPresenter.Event.ProfilePictureClick) }\n              .testTag(\"profilePicture\")\n              .align(Alignment.CenterVertically)\n              .sharedElement(\n                rememberSharedContentState(key = PROFILE_PICTURE_KEY),\n                animatedVisibilityScope = checkNotNull(LocalAnimatedVisibilityScope.current),\n                clipInOverlayDuringTransition = OverlayClip(CircleShape),\n              )\n              .clip(CircleShape),\n        )\n      }\n    }\n  }\n\n  private companion object {\n    const val PROFILE_PICTURE_KEY = \"profile-picture\"\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListPresenter.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.sample.user.UserPageListPresenter.Input\nimport software.amazon.app.platform.sample.user.UserPageListPresenter.Model\n\n/** Presenter to manage the list content of the list-detail layout. */\n@Inject\nclass UserPageListPresenter(private val sessionTimeout: SessionTimeout) :\n  MoleculePresenter<Input, Model> {\n\n  @Composable\n  override fun present(input: Input): Model {\n    val user = input.user\n    var selectedIndex by remember { mutableIntStateOf(0) }\n\n    return Model(\n      userId = user.userId,\n      attributeKeys = user.attributes.filterNot { it.metadata }.map { it.key },\n      selectedIndex = selectedIndex,\n    ) {\n      when (it) {\n        is Event.ItemSelected -> {\n          sessionTimeout.resetTimeout()\n          selectedIndex = it.index\n        }\n      }\n    }\n  }\n\n  /** The state of the list pane. */\n  data class Model(\n    /** The ID of the currently logged in user. */\n    val userId: Long,\n    /** The attributes that can be selected in the in UI. */\n    val attributeKeys: List<String>,\n    /** The currently selected attribute. */\n    val selectedIndex: Int,\n    /** Callback to send events back to the presenter. */\n    val onEvent: (Event) -> Unit,\n  ) : BaseModel\n\n  /** All events that [UserPageListPresenter] can process. */\n  sealed interface Event {\n    /** Sent when the user selects item with [index] in the list. */\n    data class ItemSelected(val index: Int) : Event\n  }\n\n  /**\n   * The input type of the presenter. [user] is the currently logged in user. More parameters can be\n   * added to this class when needed.\n   */\n  data class Input(val user: User)\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageListRenderer.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.material.MaterialTheme\nimport androidx.compose.material.Text\nimport androidx.compose.material.ripple\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.testTag\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport software.amazon.app.platform.inject.ContributesRenderer\nimport software.amazon.app.platform.renderer.ComposeRenderer\nimport software.amazon.app.platform.sample.user.UserPageListPresenter.Model\n\n/** Renders the content for [UserPageListPresenter] on screen using Compose Multiplatform. */\n@ContributesRenderer\nclass UserPageListRenderer : ComposeRenderer<Model>() {\n\n  @Composable\n  override fun Compose(model: Model) {\n    Column(modifier = Modifier.fillMaxWidth().fillMaxHeight()) {\n      Text(\n        text = \"User: ${model.userId}\",\n        textAlign = TextAlign.Center,\n        style = MaterialTheme.typography.h5,\n        modifier = Modifier.padding(8.dp).testTag(\"userIdText\"),\n      )\n\n      LazyColumn {\n        itemsIndexed(model.attributeKeys) { index, attribute ->\n          Text(\n            text = attribute,\n            modifier =\n              Modifier.fillMaxWidth()\n                .clickable(\n                  onClick = { model.onEvent(UserPageListPresenter.Event.ItemSelected(index)) },\n                  interactionSource = remember { MutableInteractionSource() },\n                  indication = ripple(),\n                )\n                .let { modifier ->\n                  if (model.selectedIndex == index) {\n                    @Suppress(\"MagicNumber\") modifier.background(Color.DarkGray.copy(0.1f))\n                  } else {\n                    modifier\n                  }\n                }\n                .padding(8.dp),\n          )\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImpl.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.runtime.Composable\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.backgesture.BackHandlerPresenter\nimport software.amazon.app.platform.presenter.template.ModelDelegate\nimport software.amazon.app.platform.renderer.Renderer\nimport software.amazon.app.platform.sample.template.SampleAppTemplate\nimport software.amazon.app.platform.sample.user.UserPagePresenter.Model\n\n/**\n * Production implementation of [UserPagePresenter].\n *\n * This class injects two child presenters to compute models. The child presenters have inputs for\n * bi-directional communication between presenters.\n *\n * Note that this class itself doesn't have a [Renderer], because it only combines models from child\n * presenters, which come with a [Renderer].\n */\n@Inject\n@ContributesBinding(UserScope::class)\nclass UserPagePresenterImpl(\n  private val user: User,\n  private val userManager: UserManager,\n  private val userPageListPresenter: UserPageListPresenter,\n  private val userPageDetailPresenter: UserPageDetailPresenter,\n) : UserPagePresenter {\n\n  @Composable\n  override fun present(input: Unit): Model {\n    BackHandlerPresenter { userManager.logout() }\n\n    // Note that listModel provides further input for the detail presenter.\n    val listModel = userPageListPresenter.present(UserPageListPresenter.Input(user))\n    val detailModel =\n      userPageDetailPresenter.present(\n        UserPageDetailPresenter.Input(user, selectedAttribute = listModel.selectedIndex)\n      )\n\n    return ModelImpl(listModel = listModel, detailModel = detailModel)\n  }\n\n  /**\n   * This class implements [ModelDelegate] to override which [SampleAppTemplate] to use. This Model\n   * hosts to other models [listModel] and [detailModel], which will be produced by child\n   * presenters.\n   */\n  private data class ModelImpl(\n    override val listModel: UserPageListPresenter.Model,\n    override val detailModel: UserPageDetailPresenter.Model,\n  ) : Model, ModelDelegate {\n    override fun delegate(): BaseModel {\n      return if (detailModel.showPictureFullscreen) {\n        SampleAppTemplate.FullScreenTemplate(detailModel)\n      } else {\n        SampleAppTemplate.ListDetailTemplate(listModel, detailModel)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/FakeAnimationHelper.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nobject FakeAnimationHelper : AnimationHelper {\n  override fun isAnimationsEnabled(): Boolean = true\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/SessionTimeoutTest.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isGreaterThan\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport kotlin.test.Test\nimport kotlin.time.Duration\nimport kotlin.time.Duration.Companion.milliseconds\nimport kotlin.time.Duration.Companion.seconds\nimport kotlinx.coroutines.test.advanceTimeBy\nimport software.amazon.app.platform.scope.runTestWithScope\n\nclass SessionTimeoutTest {\n\n  @Test\n  fun `the timeout reaches zero`() = runTestWithScope { scope ->\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n    assertThat(sessionTimeout.sessionTimeout.value).isEqualTo(SessionTimeout.initialTimeout)\n\n    scope.register(sessionTimeout)\n\n    advanceTimeBy(2.seconds + 1.milliseconds)\n    assertThat(sessionTimeout.sessionTimeout.value)\n      .isEqualTo(SessionTimeout.initialTimeout - 2.seconds)\n\n    // Note that it doesn't go negative.\n    advanceTimeBy(SessionTimeout.initialTimeout)\n    assertThat(sessionTimeout.sessionTimeout.value).isEqualTo(Duration.ZERO)\n  }\n\n  @Test\n  fun `on timeout the user is logged out`() = runTestWithScope { scope ->\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n    scope.register(sessionTimeout)\n\n    assertThat(userManager.user.value).isNotNull()\n\n    advanceTimeBy(SessionTimeout.initialTimeout + 1.milliseconds)\n    assertThat(userManager.user.value).isNull()\n  }\n\n  @Test\n  fun `the timeout can be reset`() = runTestWithScope { scope ->\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n    scope.register(sessionTimeout)\n\n    advanceTimeBy(2.seconds + 1.milliseconds)\n    assertThat(sessionTimeout.sessionTimeout.value)\n      .isEqualTo(SessionTimeout.initialTimeout - 2.seconds)\n\n    sessionTimeout.resetTimeout()\n    assertThat(sessionTimeout.sessionTimeout.value).isEqualTo(SessionTimeout.initialTimeout)\n  }\n\n  @Test\n  fun `the timeout stops on early logout`() = runTestWithScope { scope ->\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n    scope.register(sessionTimeout)\n\n    advanceTimeBy(2.seconds + 1.milliseconds)\n    assertThat(sessionTimeout.sessionTimeout.value)\n      .isEqualTo(SessionTimeout.initialTimeout - 2.seconds)\n\n    userManager.logout()\n\n    // The timeout doesn't change.\n    advanceTimeBy(SessionTimeout.initialTimeout)\n    assertThat(sessionTimeout.sessionTimeout.value).isGreaterThan(Duration.ZERO)\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserManagerImplTest.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport assertk.assertions.isTrue\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.scope.RootScopeProvider\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.Scoped\nimport software.amazon.app.platform.scope.buildTestScope\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\nimport software.amazon.app.platform.scope.di.metro.metroDependencyGraph\n\nclass UserManagerImplTest {\n\n  @Test\n  fun `a user can login and logout`() = runTest {\n    val rootScopeProvider = rootScopeProvider()\n    val userManager = userManager(rootScopeProvider)\n\n    assertThat(userManager.user.value).isNull()\n\n    userManager.login(1L)\n    assertThat(userManager.user.value).isNotNull()\n\n    userManager.logout()\n    assertThat(userManager.user.value).isNull()\n  }\n\n  @Test\n  fun `logging out destroys the user scope`() = runTest {\n    val rootScopeProvider = rootScopeProvider()\n    val userManager = userManager(rootScopeProvider)\n\n    userManager.login(1L)\n    val userScope = checkNotNull(userManager.user.value?.scope)\n    assertThat(userScope.isDestroyed()).isFalse()\n\n    userManager.logout()\n    assertThat(userScope.isDestroyed()).isTrue()\n  }\n\n  @Test\n  fun `logging in while another user is logged in logs out the user`() = runTest {\n    val rootScopeProvider = rootScopeProvider()\n    val userManager = userManager(rootScopeProvider)\n\n    userManager.login(1L)\n    assertThat(userManager.user.value?.userId).isEqualTo(1L)\n    val userScope = checkNotNull(userManager.user.value?.scope)\n    assertThat(userScope.isDestroyed()).isFalse()\n\n    userManager.login(2L)\n    assertThat(userManager.user.value?.userId).isEqualTo(2L)\n    assertThat(userScope.isDestroyed()).isTrue()\n  }\n\n  private fun userManager(rootScopeProvider: RootScopeProvider): UserManagerImpl =\n    UserManagerImpl(\n      rootScopeProvider,\n      rootScopeProvider.rootScope.metroDependencyGraph<UserGraph.Factory>(),\n    )\n\n  private fun TestScope.appGraph(): Any =\n    object : UserGraph.Factory {\n      override fun createUserGraph(user: User): UserGraph {\n        return object : UserGraph {\n          override val userScopedInstances: Set<Scoped> = emptySet()\n          override val userScopeCoroutineScopeScoped: CoroutineScopeScoped =\n            CoroutineScopeScoped(coroutineContext + Job() + CoroutineName(\"TestUserScope\"))\n        }\n      }\n    }\n\n  private fun TestScope.rootScopeProvider(appGraph: Any = appGraph()): RootScopeProvider {\n    return object : RootScopeProvider {\n      override val rootScope: Scope =\n        Scope.buildTestScope(this@rootScopeProvider) { addMetroDependencyGraph(appGraph) }\n    }\n  }\n}\n"
  },
  {
    "path": "sample/user/impl/src/commonTest/kotlin/software/amazon/app/platform/sample/user/UserPagePresenterImplTest.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isNotNull\nimport assertk.assertions.isNull\nimport kotlin.test.Test\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.advanceTimeBy\nimport kotlinx.coroutines.test.runCurrent\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.presenter.molecule.backgesture.withBackGestureDispatcher\nimport software.amazon.app.platform.presenter.molecule.test\nimport software.amazon.app.platform.scope.runTestWithScope\n\nclass UserPagePresenterImplTest {\n\n  @Test\n  fun `the selected item can be changed`() = runTest {\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n    val user = checkNotNull(userManager.user.value)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n\n    val presenter =\n      UserPagePresenterImpl(\n        user,\n        FakeUserManager(),\n        UserPageListPresenter(sessionTimeout),\n        UserPageDetailPresenter(sessionTimeout),\n      )\n\n    presenter.withBackGestureDispatcher().test(this) {\n      awaitItem().let { model ->\n        assertThat((model.listModel as UserPageListPresenter.Model).selectedIndex).isEqualTo(0)\n        assertThat((model.detailModel as UserPageDetailPresenter.Model).text)\n          .isEqualTo(FakeUser.fakeAttribute1.value)\n\n        (model.listModel as UserPageListPresenter.Model).onEvent(\n          UserPageListPresenter.Event.ItemSelected(1)\n        )\n      }\n\n      awaitItem().let { model ->\n        assertThat((model.listModel as UserPageListPresenter.Model).selectedIndex).isEqualTo(1)\n        assertThat((model.detailModel as UserPageDetailPresenter.Model).text)\n          .isEqualTo(FakeUser.fakeAttribute2.value)\n      }\n    }\n  }\n\n  @Test\n  fun `the session timeout progress is updated`() = runTestWithScope { scope ->\n    val userManager = FakeUserManager()\n    userManager.login(1L)\n    val user = checkNotNull(userManager.user.value)\n\n    val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n    scope.register(sessionTimeout)\n\n    val presenter =\n      UserPagePresenterImpl(\n        user,\n        userManager,\n        UserPageListPresenter(sessionTimeout),\n        UserPageDetailPresenter(sessionTimeout),\n      )\n\n    presenter.withBackGestureDispatcher().test(this) {\n      val model = awaitItem().detailModel as UserPageDetailPresenter.Model\n      assertThat(model.timeoutProgress.value).isEqualTo(1f)\n\n      advanceTimeBy(SessionTimeout.initialTimeout / 2)\n      runCurrent()\n\n      // This is important. This test only modifies the value for timeoutProgress.\n      // This property is a composable `State` and therefore changes aren't propagated\n      // through Model updates, but instead the property must be observed.\n      expectNoEvents()\n\n      assertThat(model.timeoutProgress.value).isEqualTo(0.5f)\n    }\n  }\n\n  @Test\n  fun `the user logs out on back press`() =\n    runTestWithScope(UnconfinedTestDispatcher()) { scope ->\n      val userManager = FakeUserManager()\n      userManager.login(1L)\n      val user = checkNotNull(userManager.user.value)\n\n      val sessionTimeout = SessionTimeout(userManager, FakeAnimationHelper)\n      scope.register(sessionTimeout)\n\n      val presenter =\n        UserPagePresenterImpl(\n          user,\n          userManager,\n          UserPageListPresenter(sessionTimeout),\n          UserPageDetailPresenter(sessionTimeout),\n        )\n\n      val backEvent = MutableSharedFlow<Unit>()\n      presenter.withBackGestureDispatcher(backEvent).test(this) {\n        assertThat(awaitItem()).isNotNull()\n\n        backEvent.emit(Unit)\n\n        expectNoEvents()\n        assertThat(userManager.user.value).isNull()\n      }\n    }\n}\n"
  },
  {
    "path": "sample/user/impl/src/wasmJsMain/kotlin/software/amazon/app/platform/sample/user/DefaultAnimationsHelper.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.ContributesBinding\nimport dev.zacsweers.metro.Inject\n\n/**\n * Default implementation of [AnimationHelper] that always keeps animations enabled. This\n * implementation is used for Wasm.\n */\n@Inject\n@ContributesBinding(AppScope::class)\nclass DefaultAnimationsHelper : AnimationHelper {\n  override fun isAnimationsEnabled(): Boolean = true\n}\n"
  },
  {
    "path": "sample/user/impl-robots/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableComposeUi true\n    enableMetro true\n    enableModuleStructure true\n}\n"
  },
  {
    "path": "sample/user/impl-robots/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPageRobot.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport androidx.compose.ui.test.assertIsDisplayed\nimport androidx.compose.ui.test.assertTextEquals\nimport androidx.compose.ui.test.onNodeWithTag\nimport androidx.compose.ui.test.performClick\nimport dev.zacsweers.metro.AppScope\nimport dev.zacsweers.metro.Inject\nimport software.amazon.app.platform.inject.robot.ContributesRobot\nimport software.amazon.app.platform.robot.ComposeRobot\n\n/**\n * A test robot to verify interactions with the user page screen written with Compose Multiplatform.\n *\n * This robot injects other dependencies from the object graph, e.g. this is helpful to query\n * further data or change behavior of classes. Robots are not exclusive to verifying UI and UI\n * interactions.\n */\n@Inject\n@ContributesRobot(AppScope::class)\nclass UserPageRobot(private val userManager: UserManager) : ComposeRobot() {\n\n  private val userIdTextNode\n    get() = compose.onNodeWithTag(\"userIdText\")\n\n  private val profilePictureNode\n    get() = compose.onNodeWithTag(\"profilePicture\")\n\n  /**\n   * Verify that the user ID is displayed. The [userId] can be changed, but uses by default the ID\n   * of the logged in user if present.\n   */\n  fun seeUserId(userId: Long = userManager.user.value?.userId ?: -1L) {\n    userIdTextNode.assertIsDisplayed()\n    userIdTextNode.assertTextEquals(\"User: $userId\")\n  }\n\n  /**\n   * Verify that the profile picture is displayed. If [fullScreen] is `true`, then only the picture\n   * should be shown and other elements like the user ID [seeUserId] are gone.\n   */\n  fun seeProfilePicture(fullScreen: Boolean = false) {\n    profilePictureNode.assertIsDisplayed()\n\n    if (fullScreen) {\n      userIdTextNode.assertDoesNotExist()\n    } else {\n      userIdTextNode.assertIsDisplayed()\n    }\n  }\n\n  /** Click on the profile picture. This works in the detail page and fullscreen mode. */\n  fun clickProfilePicture() {\n    profilePictureNode.performClick()\n  }\n}\n"
  },
  {
    "path": "sample/user/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n"
  },
  {
    "path": "sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/User.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport software.amazon.app.platform.scope.Scope\n\n/** A [User] represents an account to implement login and logout in our sample app. */\ninterface User {\n  /** A unique ID of this user. */\n  val userId: Long\n\n  /** User specific attributes just to show some data in the sample app. */\n  val attributes: List<Attribute>\n\n  /**\n   * The scope is tied to the lifecycle of the user. It hosts a user specific `CoroutineScope` and\n   * Metro graph. The scope is destroyed on logout.\n   */\n  val scope: Scope\n\n  /**\n   * A [key] [value] pair of user specific data. [metadata] is true when the element should not be\n   * shown on screen.\n   */\n  data class Attribute(val key: String, val value: String, val metadata: Boolean = false) {\n    companion object {\n      /** Key for the picture attribute. */\n      const val PICTURE_KEY = \"picture\"\n    }\n  }\n}\n"
  },
  {
    "path": "sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserManager.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport kotlinx.coroutines.flow.StateFlow\n\n/**\n * Handles login and logout of user and manages the logged in state. Only a single [User] can login\n * at a time.\n */\ninterface UserManager {\n\n  /** The currently logged in user or `null`. */\n  val user: StateFlow<User?>\n\n  /**\n   * Logs in a new user with the given [userId]. Any existing logged in user is logged out first.\n   * [user] will be updated before the function returns.\n   */\n  fun login(userId: Long)\n\n  /** Logs out the current user and resets [user] to `null`. */\n  fun logout()\n}\n"
  },
  {
    "path": "sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserPagePresenter.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport software.amazon.app.platform.presenter.BaseModel\nimport software.amazon.app.platform.presenter.molecule.MoleculePresenter\nimport software.amazon.app.platform.presenter.template.ModelDelegate\nimport software.amazon.app.platform.sample.user.UserPagePresenter.Model\n\n/** Presenter to render user details on screen. */\ninterface UserPagePresenter : MoleculePresenter<Unit, Model> {\n\n  /**\n   * The state of the user page. Note that the actual implementation class implements\n   * [ModelDelegate] to override which `SampleAppTemplate` to use. This Model hosts to other models\n   * [listModel] and [detailModel], which will be produced by child presenters.\n   *\n   * This class is an interface and not the final `data class`, because the actual implementation\n   * contains more logic, which was moved therefore into the :impl module.\n   */\n  interface Model : BaseModel {\n    val listModel: BaseModel\n    val detailModel: BaseModel\n  }\n}\n"
  },
  {
    "path": "sample/user/public/src/commonMain/kotlin/software/amazon/app/platform/sample/user/UserScope.kt",
    "content": "package software.amazon.app.platform.sample.user\n\n/**\n * Marker class for the user scope, which can be used with `@SingleIn(UserScope::class)` or\n * `@ContributesBinding(UserScope::class)`.\n *\n * This is an abstract class with a private constructor so that no instance is allocated at runtime.\n */\nabstract class UserScope private constructor()\n"
  },
  {
    "path": "sample/user/testing/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform'\n\n    // This plugin lives in the buildSrc directory and is not published.\n    // It's used to manage certain configurations and dependencies for all\n    // :sample:* modules, otherwise we'd need to repeat them several times.\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatform {\n    enableMetro true\n    enableModuleStructure true\n    enableMoleculePresenters true\n}\n"
  },
  {
    "path": "sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUser.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport software.amazon.app.platform.scope.Scope\n\n/**\n * Fake implementation of [User], which is useful in unit tests.\n *\n * This class is part of the `:testing` module and shared with other modules.\n */\nclass FakeUser(\n  override val userId: Long = 1L,\n  override val attributes: List<User.Attribute> =\n    listOf(fakeAttribute1, fakeAttribute2, fakePicture),\n  override val scope: Scope = Scope.buildRootScope(),\n) : User {\n  companion object {\n    /** Fake attribute in tests that is added by default to a [FakeUser] unless overridden. */\n    val fakeAttribute1 = User.Attribute(\"Key1\", \"Value1\")\n\n    /** Fake attribute in tests that is added by default to a [FakeUser] unless overridden. */\n    val fakeAttribute2 = User.Attribute(\"Key2\", \"Value2\")\n\n    /** Fake attribute in tests that is added by default to a [FakeUser] unless overridden. */\n    val fakePicture = User.Attribute(User.Attribute.PICTURE_KEY, \"picture\", metadata = true)\n  }\n}\n"
  },
  {
    "path": "sample/user/testing/src/commonMain/kotlin/software/amazon/app/platform/sample/user/FakeUserManager.kt",
    "content": "package software.amazon.app.platform.sample.user\n\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.test.TestScope\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.buildTestScope\nimport software.amazon.app.platform.scope.di.metro.addMetroDependencyGraph\n\n/**\n * Fake implementation of [UserManager], which is useful in unit tests.\n *\n * This class is part of the `:testing` module and shared with other modules.\n */\nclass FakeUserManager(override val user: MutableStateFlow<User?> = MutableStateFlow(null)) :\n  UserManager {\n\n  override fun login(userId: Long) {\n    user.value = FakeUser(userId = userId)\n  }\n\n  /** Overloaded function to change the coroutine scope and Metro graph for the [FakeUser]. */\n  fun login(userId: Long, scope: TestScope, graph: Any) {\n    user.value =\n      FakeUser(\n        userId = userId,\n        scope = Scope.buildTestScope(scope) { addMetroDependencyGraph(graph) },\n      )\n  }\n\n  override fun logout() {\n    user.value?.scope?.destroy()\n    user.value = null\n  }\n}\n"
  },
  {
    "path": "scope/public/api/android/public.api",
    "content": "public abstract interface class software/amazon/app/platform/scope/RootScopeProvider {\n\tpublic abstract fun getRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/Scope {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/Scope$Companion;\n\tpublic abstract fun buildChild (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildChild$default (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic abstract fun children ()Ljava/util/Set;\n\tpublic abstract fun destroy ()V\n\tpublic abstract fun getName ()Ljava/lang/String;\n\tpublic abstract fun getParent ()Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic abstract fun getService (Ljava/lang/String;)Ljava/lang/Object;\n\tpublic abstract fun isDestroyed ()Z\n\tpublic abstract fun register (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/Scope$Builder {\n\tpublic final fun addService (Ljava/lang/String;Ljava/lang/Object;)V\n\tpublic final fun register (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/Scope$Companion {\n\tpublic final fun buildRootScope (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildRootScope$default (Lsoftware/amazon/app/platform/scope/Scope$Companion;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/scope/Scope$DefaultImpls {\n\tpublic static synthetic fun buildChild$default (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/scope/ScopeKt {\n\tpublic static final fun onExit (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/jvm/functions/Function0;)V\n\tpublic static final fun parents (Lsoftware/amazon/app/platform/scope/Scope;Z)Lkotlin/sequences/Sequence;\n\tpublic static synthetic fun parents$default (Lsoftware/amazon/app/platform/scope/Scope;ZILjava/lang/Object;)Lkotlin/sequences/Sequence;\n\tpublic static final fun register (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Iterable;)V\n\tpublic static final fun register (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/Iterable;)V\n}\n\npublic abstract interface class software/amazon/app/platform/scope/Scoped {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/Scoped$Companion;\n\tpublic fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic fun onExitScope ()V\n}\n\npublic final class software/amazon/app/platform/scope/Scoped$Companion {\n\tpublic final fun getNO_OP ()Lsoftware/amazon/app/platform/scope/Scoped;\n}\n\npublic final class software/amazon/app/platform/scope/Scoped$DefaultImpls {\n\tpublic static fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scoped;Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic static fun onExitScope (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineScopeScoped : kotlinx/coroutines/CoroutineScope, software/amazon/app/platform/scope/Scoped {\n\tpublic fun <init> (Lkotlin/coroutines/CoroutineContext;)V\n\tpublic final fun createChild (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static synthetic fun createChild$default (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;\n\tpublic fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic fun onExitScope ()V\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineScopeServiceKt {\n\tpublic static final fun addCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/Scope$Builder;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)V\n\tpublic static final fun coroutineScope (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static synthetic fun coroutineScope$default (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static final fun launch (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;\n\tpublic static synthetic fun launch$default (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;\n}\n\n"
  },
  {
    "path": "scope/public/api/desktop/public.api",
    "content": "public abstract interface class software/amazon/app/platform/scope/RootScopeProvider {\n\tpublic abstract fun getRootScope ()Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic abstract interface class software/amazon/app/platform/scope/Scope {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/Scope$Companion;\n\tpublic abstract fun buildChild (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildChild$default (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic abstract fun children ()Ljava/util/Set;\n\tpublic abstract fun destroy ()V\n\tpublic abstract fun getName ()Ljava/lang/String;\n\tpublic abstract fun getParent ()Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic abstract fun getService (Ljava/lang/String;)Ljava/lang/Object;\n\tpublic abstract fun isDestroyed ()Z\n\tpublic abstract fun register (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/Scope$Builder {\n\tpublic final fun addService (Ljava/lang/String;Ljava/lang/Object;)V\n\tpublic final fun register (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/Scope$Companion {\n\tpublic final fun buildRootScope (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildRootScope$default (Lsoftware/amazon/app/platform/scope/Scope$Companion;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/scope/Scope$DefaultImpls {\n\tpublic static synthetic fun buildChild$default (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\npublic final class software/amazon/app/platform/scope/ScopeKt {\n\tpublic static final fun onExit (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/jvm/functions/Function0;)V\n\tpublic static final fun parents (Lsoftware/amazon/app/platform/scope/Scope;Z)Lkotlin/sequences/Sequence;\n\tpublic static synthetic fun parents$default (Lsoftware/amazon/app/platform/scope/Scope;ZILjava/lang/Object;)Lkotlin/sequences/Sequence;\n\tpublic static final fun register (Lsoftware/amazon/app/platform/scope/Scope$Builder;Ljava/lang/Iterable;)V\n\tpublic static final fun register (Lsoftware/amazon/app/platform/scope/Scope;Ljava/lang/Iterable;)V\n}\n\npublic abstract interface class software/amazon/app/platform/scope/Scoped {\n\tpublic static final field Companion Lsoftware/amazon/app/platform/scope/Scoped$Companion;\n\tpublic fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic fun onExitScope ()V\n}\n\npublic final class software/amazon/app/platform/scope/Scoped$Companion {\n\tpublic final fun getNO_OP ()Lsoftware/amazon/app/platform/scope/Scoped;\n}\n\npublic final class software/amazon/app/platform/scope/Scoped$DefaultImpls {\n\tpublic static fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scoped;Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic static fun onExitScope (Lsoftware/amazon/app/platform/scope/Scoped;)V\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineScopeScoped : kotlinx/coroutines/CoroutineScope, software/amazon/app/platform/scope/Scoped {\n\tpublic fun <init> (Lkotlin/coroutines/CoroutineContext;)V\n\tpublic final fun createChild (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static synthetic fun createChild$default (Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;\n\tpublic fun onEnterScope (Lsoftware/amazon/app/platform/scope/Scope;)V\n\tpublic fun onExitScope ()V\n}\n\npublic final class software/amazon/app/platform/scope/coroutine/CoroutineScopeServiceKt {\n\tpublic static final fun addCoroutineScopeScoped (Lsoftware/amazon/app/platform/scope/Scope$Builder;Lsoftware/amazon/app/platform/scope/coroutine/CoroutineScopeScoped;)V\n\tpublic static final fun coroutineScope (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static synthetic fun coroutineScope$default (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/CoroutineScope;\n\tpublic static final fun launch (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;\n\tpublic static synthetic fun launch$default (Lsoftware/amazon/app/platform/scope/Scope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;\n}\n\n"
  },
  {
    "path": "scope/public/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/RootScopeProvider.kt",
    "content": "package software.amazon.app.platform.scope\n\n/**\n * Provides the root scope of the application, which usually refers to `AppScope`. This interface is\n * implemented by the `Application` class of the app.\n */\npublic interface RootScopeProvider {\n  /**\n   * The root scope of the application, which usually refers to `AppScope`. Usually, the scope is\n   * alive until the application terminates.\n   */\n  public val rootScope: Scope\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/Scope.kt",
    "content": "package software.amazon.app.platform.scope\n\n/**\n * Scopes define the boundary our software components operate in. A scope is a space with a\n * well-defined lifecycle that can be created and torn down. Scopes host other service objects and\n * serve as container for them, e.g. DI components. To receive a callback when a scope is created or\n * destroyed the [Scoped] interface can be used.\n *\n * Scopes can have 0-N children. Child scopes have the same or a shorter lifecycle as their parent\n * scope. If the parent scope gets destroyed, then all child scopes are destroyed as well.\n *\n * After a scope has been destroyed it should no longer be used. All methods will throw an exception\n * instead.\n */\npublic interface Scope {\n\n  /** The name to identify this scope. */\n  public val name: String\n\n  /**\n   * Returns the parent scope if this is a child scope and was created with [buildChild], or returns\n   * `null` for the root scope that was created with [buildRootScope].\n   */\n  public val parent: Scope?\n\n  /** Creates a new child scope with this scope as parent. */\n  public fun buildChild(name: String, builder: (Builder.() -> Unit)? = null): Scope\n\n  /** All child scopes of this scope. */\n  public fun children(): Set<Scope>\n\n  /**\n   * Registers [scoped] to be notified when this scope is destroyed. Since this scope has been\n   * already created at this point in time, [Scoped.onEnterScope] will be called immediately.\n   */\n  public fun register(scoped: Scoped)\n\n  /**\n   * Returns whether this scope has been destroyed. If `true`, then no other methods should be\n   * called anymore.\n   */\n  public fun isDestroyed(): Boolean\n\n  /**\n   * Destroy this scope and all its children. Calling this function will invoke [Scoped.onExitScope]\n   * for all registered [Scoped] instances.\n   */\n  public fun destroy()\n\n  /**\n   * Returns a registered service for the given [key] or null if no service is registered with this\n   * key.\n   */\n  public fun <T : Any> getService(key: String): T?\n\n  /** Builder type to construct a new [Scope] instance. */\n  public class Builder internal constructor(private val name: String, private val parent: Scope?) {\n    private val services = mutableMapOf<String, Any>()\n    private val scopedInstances = mutableSetOf<Scoped>()\n\n    /** Adds the given [service] to the scope that is about to be constructed. */\n    public fun addService(key: String, service: Any) {\n      services[key] = service\n    }\n\n    /** Registers [scoped] to be notified when this scope is created and destroyed. */\n    public fun register(scoped: Scoped) {\n      scopedInstances += scoped\n    }\n\n    internal fun build(): ScopeImpl {\n      return ScopeImpl(name, parent, services).apply { register(scopedInstances) }\n    }\n  }\n\n  public companion object {\n    /** Builds a scope without any parent. */\n    public fun buildRootScope(name: String = \"root\", builder: (Builder.() -> Unit)? = null): Scope =\n      Builder(name, null)\n        .apply {\n          if (builder != null) {\n            builder()\n          }\n        }\n        .build()\n  }\n}\n\n/**\n * Returns a chain of scopes beginning with this scope if [includeSelf] is `true` or the\n * [Scope.parent] if [includeSelf] is false followed by each parent of the previous scope until the\n * root scope is reached. The returned sequence is empty for root scope and [includeSelf] being\n * `false`.\n */\npublic fun Scope.parents(includeSelf: Boolean = false): Sequence<Scope> =\n  generateSequence(this) { it.parent }.drop(if (includeSelf) 0 else 1)\n\n/**\n * Registers [scopedInstances] to be notified when this scope is destroyed. Since this scope has\n * been already created at this point in time, [Scoped.onEnterScope] will be called immediately for\n * all instance.\n */\npublic fun Scope.register(scopedInstances: Iterable<Scoped>) {\n  scopedInstances.forEach { register(it) }\n}\n\n/** Registers [scopedInstances] to be notified when this scope is created and destroyed. */\npublic fun Scope.Builder.register(scopedInstances: Iterable<Scoped>) {\n  scopedInstances.forEach { register(it) }\n}\n\n/**\n * Invokes the given lambda when the scope is destroyed. This is a convenience function when\n * [Scoped.onExitScope] cannot be overridden or fields are tedious to manage, e.g.\n *\n * ```\n * override fun onEnterScope(scope: Scope) {\n *     val receiver = BroadCastReceiver()\n *\n *     application.registerReceiver(receiver)\n *\n *     scope.onExit {\n *         application.unregisterReceiver(receiver)\n *     }\n * }\n * ```\n */\npublic fun Scope.onExit(block: () -> Unit) {\n  register(\n    object : Scoped {\n      override fun onExitScope() {\n        block()\n      }\n    }\n  )\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/ScopeImpl.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\n\ninternal class ScopeImpl(\n  override val name: String,\n  private val parentScope: Scope?,\n  private val services: Map<String, Any>,\n) : Scope {\n\n  private val scopedInstances = mutableSetOf<Scoped>()\n  private var isDestroyed = false\n  private var isDestroying = false\n\n  private val children = mutableSetOf<ScopeImpl>()\n\n  override val parent: Scope?\n    get() {\n      checkIsNotDestroyed()\n      return parentScope\n    }\n\n  override fun buildChild(name: String, builder: (Scope.Builder.() -> Unit)?): Scope {\n    checkIsNotDestroyed()\n    return Scope.Builder(name, this)\n      .apply {\n        if (builder != null) {\n          builder()\n        }\n      }\n      .build()\n      .also { children += it }\n  }\n\n  override fun children(): Set<Scope> {\n    checkIsNotDestroyed()\n    return children.toSet()\n  }\n\n  override fun register(scoped: Scoped) {\n    checkIsNotDestroyed()\n\n    if (scoped !in scopedInstances) {\n      scopedInstances += scoped\n      scoped.onEnterScope(this)\n    }\n  }\n\n  override fun isDestroyed(): Boolean = isDestroyed\n\n  @Suppress(\"UNCHECKED_CAST\")\n  override fun <T : Any> getService(key: String): T? {\n    checkIsNotDestroyed()\n    return services[key] as T?\n  }\n\n  override fun destroy() {\n    if (isDestroyed || isDestroying) return\n    isDestroying = true\n\n    // Heads up that destroying the child will modify the `children` set. By using a\n    // sequence and computing the next value lazily this operation safe.\n    generateSequence { children.firstOrNull { !it.isDestroying } }\n      .forEach { childScope -> childScope.destroy() }\n\n    // Cancel coroutines first to make the destruction of a scope more deterministic. A common\n    // pattern is to use the CoroutineScope in onEnterScope() to launch several jobs. These\n    // jobs often run in a loop. onExitScope() is used to release resources, to which the jobs\n    // may react and run code to recreate these resources. This is problematic and requires\n    // you to check in the jobs whether this scope (not the CoroutineScope) is destroyed.\n    //\n    // Instead of always checking whether the scope is destroyed, make sure to cancel all\n    // coroutines first and then call onExitScope() on all other Scoped instances.\n    scopedInstances.filterIsInstance<CoroutineScopeScoped>().forEach { it.onExitScope() }\n    scopedInstances.filter { it !is CoroutineScopeScoped }.forEach { it.onExitScope() }\n\n    // Release all references.\n    scopedInstances.clear()\n\n    (parent as? ScopeImpl)?.children?.remove(this)\n    isDestroyed = true\n    isDestroying = false\n  }\n\n  private fun checkIsNotDestroyed() {\n    check(!isDestroyed) { \"The scope $name is already destroyed.\" }\n  }\n\n  override fun toString(): String {\n    return \"Scope(name='$name', isDestroyed=$isDestroyed, children=${children.size}, \" +\n      \"parentScope=$parentScope)\"\n  }\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/Scoped.kt",
    "content": "package software.amazon.app.platform.scope\n\n/**\n * Register this interface with a [Scope] to receive lifecycle callbacks of this scope.\n *\n * This interface is commonly used with service objects to receive a notification when a [Scope] is\n * created and when the [Scope] is destroyed to clean up resources, e.g.\n *\n * ```\n * interface HudManager {\n *     ...\n * }\n *\n * @Inject\n * @SingleIn(AppScope::class)\n * class HudManagerImpl(\n *     ...\n * ) : HudManager, Scoped {\n *     ...\n *\n *     override fun onEnterScope(scope: Scope) {\n *         // Initialize code and start background work\n *     }\n *\n *     override fun onExitScope() {\n *         // Free up resources\n *     }\n *\n *     interface Component {\n *         @Provides\n *         fun provideHudManager(impl: HudManagerImpl): HudManager = impl\n *\n *         @Provides\n *         @IntoSet\n *         fun provideHudManagerScoped(impl: HudManagerImpl): AppScopeScoped = impl\n *   }\n * }\n * ```\n *\n * In this particular example `HudManagerImpl` gets automatically created by our DI framework, when\n * the app scope is created. The same mechanism works for other scopes. Other consumers can safely\n * inject `HudManager` and would receive an instance of `HudManagerImpl`.\n *\n * It's an anti-pattern to make interfaces like `HudManager` in this example extend the [Scoped]\n * interface. Implementing [Scoped] is a pure implementation detail.\n *\n * If the application crashes, then [onExitScope] will not be called for any scope. Upon relaunch\n * it's up to the application to restore any scope and invoke [onEnterScope] again.\n */\npublic interface Scoped {\n  /**\n   * This function is called when the given [scope] is created or was already created by the time\n   * this [Scoped] is being registered.\n   *\n   * This method provides no guarantee on which thread it is invoked. It can be the main thread or a\n   * background thread. E.g. for the `AppScope` it's usually invoked on the main thread, when the\n   * application starts, but other service classes hosting more fine grained scopes may use a\n   * background thread.\n   */\n  public fun onEnterScope(scope: Scope): Unit = Unit\n\n  /**\n   * Called when the scope is destroyed. This callback should be used to clean up resources and stop\n   * ongoing work. This function is a blocking / non-suspending call and should not start any\n   * background work, which may lead to race conditions. The `CoroutineScope` attached to this scope\n   * is already canceled by the time this function runs.\n   *\n   * This method provides no guarantee on which thread it is invoked. It can be the main thread or a\n   * background thread. E.g. for the `AppScope` it's usually invoked on the main thread, when the\n   * application is destroyed, but other service classes hosting more fine grained scopes may use a\n   * background thread.\n   */\n  public fun onExitScope(): Unit = Unit\n\n  public companion object {\n    /** A [Scoped] implementation that does nothing in the lifecycle methods. */\n    public val NO_OP: Scoped = object : Scoped {}\n  }\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/CoroutineScopeScoped.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.job\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.Scoped\n\n/**\n * A [CoroutineScope] that can be registered with a [Scope] to be automatically canceled when the\n * [Scope] gets destroyed.\n */\npublic class CoroutineScopeScoped(override val coroutineContext: CoroutineContext) :\n  CoroutineScope, Scoped {\n\n  private val parentName =\n    requireNotNull(coroutineContext[CoroutineName]) {\n      \"Expected the coroutine context to have a name.\"\n    }\n\n  override fun onExitScope() {\n    coroutineContext.cancel()\n  }\n\n  /** Creates a child [CoroutineScope] with this scope as parent. */\n  public fun createChild(\n    coroutineContext: CoroutineContext = EmptyCoroutineContext\n  ): CoroutineScope {\n    val name = coroutineContext[CoroutineName] ?: CoroutineName(parentName.name + \"-child\")\n\n    return CoroutineScope(\n      this.coroutineContext + coroutineContext + Job(this.coroutineContext.job) + name\n    )\n  }\n}\n"
  },
  {
    "path": "scope/public/src/commonMain/kotlin/software/amazon/app/platform/scope/coroutine/CoroutineScopeService.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport software.amazon.app.platform.scope.Scope\n\nprivate const val COROUTINE_SCOPE_KEY = \"coroutineScope\"\n\nprivate val Scope.coroutineScopeScoped: CoroutineScopeScoped\n  get() {\n    val result =\n      checkNotNull(getService<CoroutineScopeScoped>(COROUTINE_SCOPE_KEY)) {\n        \"Couldn't find CoroutineScopeScoped within scope $name.\"\n      }\n    check(result.isActive) {\n      \"Expected the coroutine scope ${result.coroutineContext[CoroutineName]?.name} still \" +\n        \"to be active.\"\n    }\n    return result\n  }\n\n/**\n * Returns a coroutine scope bound to the lifecycle of this [Scope]. It's not necessary nor\n * recommended to cancel the returned scope as this automatically happens when the [Scope] is being\n * destroyed.\n *\n * The [CoroutineScope] uses IO dispatcher by default and launched jobs run on a background thread.\n *\n * Jobs created by this scope don't need to be canceled.\n */\npublic fun Scope.coroutineScope(context: CoroutineContext = EmptyCoroutineContext): CoroutineScope {\n  return coroutineScopeScoped.createChild(context)\n}\n\n/**\n * Adds the given [coroutineScope] to the [Scope] that will be built. A child scope can be retrieved\n * with `coroutineScope()`.\n */\npublic fun Scope.Builder.addCoroutineScopeScoped(coroutineScope: CoroutineScopeScoped) {\n  addService(COROUTINE_SCOPE_KEY, coroutineScope)\n  register(coroutineScope)\n}\n\n/**\n * Launches a new job in the [CoroutineScope] created by [coroutineScope]. The job run on the IO\n * dispatcher by default. The lifecycle of the job is bound to the lifecycle of the [Scope] and\n * therefore doesn't need to be canceled. However, it's generally good practice to stop and cancel\n * ongoing background work eargerly.\n *\n * This is a short version of `coroutineScope().launch { }`.\n *\n * See [coroutineScope] for more details.\n */\npublic fun Scope.launch(\n  context: CoroutineContext = EmptyCoroutineContext,\n  block: suspend CoroutineScope.() -> Unit,\n): Job {\n  return coroutineScope(context).launch(block = block)\n}\n"
  },
  {
    "path": "scope/public/src/commonTest/kotlin/software/amazon/app/platform/scope/ScopeTest.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsExactly\nimport assertk.assertions.containsExactlyInAnyOrder\nimport assertk.assertions.hasSize\nimport assertk.assertions.isEmpty\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isNull\nimport assertk.assertions.isTrue\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\n\nclass ScopeTest {\n\n  @Test\n  fun `child scopes can be created`() {\n    val root = Scope.buildRootScope(\"root\")\n    assertThat(root.name).isEqualTo(\"root\")\n    assertThat(root.parent).isNull()\n\n    val child = root.buildChild(\"child\")\n    assertThat(child.name).isEqualTo(\"child\")\n    assertThat(child.parent).isEqualTo(root)\n  }\n\n  @Test\n  fun `a scope can have multiple children`() {\n    val root = Scope.buildRootScope()\n    root.buildChild(\"child1\")\n    root.buildChild(\"child2\")\n    root.buildChild(\"child3\")\n\n    assertThat(root.children()).hasSize(3)\n  }\n\n  @Test\n  fun `after a scope is destroyed methods throw`() {\n    val root = Scope.buildRootScope()\n    assertThat(root.isDestroyed()).isFalse()\n\n    root.destroy()\n    assertThat(root.isDestroyed()).isTrue()\n\n    assertFailsWith<IllegalStateException> { root.buildChild(\"child\") }\n    assertFailsWith<IllegalStateException> { root.register(Scoped.NO_OP) }\n    assertFailsWith<IllegalStateException> { root.getService<Any>(\"\") }\n\n    // Function is idempotent.\n    root.destroy()\n  }\n\n  @Test\n  fun `destroying the parent scope destroys all children`() {\n    val root = Scope.buildRootScope()\n    val children =\n      setOf(root.buildChild(\"child1\"), root.buildChild(\"child2\"), root.buildChild(\"child3\"))\n\n    children.forEach { assertThat(it.isDestroyed()).isFalse() }\n\n    root.destroy()\n    children.forEach { assertThat(it.isDestroyed()).isTrue() }\n  }\n\n  @Test\n  fun `destroying a child removes it from the parent`() {\n    val root = Scope.buildRootScope()\n    val child = root.buildChild(\"child\")\n\n    assertThat(root.children().single()).isEqualTo(child)\n\n    child.destroy()\n    assertThat(root.children()).isEmpty()\n  }\n\n  @Test\n  fun `a Scoped onEnterScope function is called immediately`() {\n    val root = Scope.buildRootScope()\n    val scoped = FakeScoped()\n    root.register(scoped)\n\n    assertThat(scoped.onEnterCalled).isEqualTo(1)\n  }\n\n  @Test\n  fun `registering a Scoped multiple times is idempotent`() {\n    val root = Scope.buildRootScope()\n    val scoped = FakeScoped()\n\n    repeat(10) { root.register(scoped) }\n    assertThat(scoped.onEnterCalled).isEqualTo(1)\n\n    repeat(10) { root.destroy() }\n    assertThat(scoped.onExitCalled).isEqualTo(1)\n  }\n\n  @Test\n  fun `destroying a parent scope calls onExitScope for children`() {\n    val root = Scope.buildRootScope()\n    val child = root.buildChild(\"child\")\n\n    val scoped = FakeScoped()\n    child.register(scoped)\n\n    root.destroy()\n    assertThat(scoped.onExitCalled).isEqualTo(1)\n  }\n\n  @Test\n  fun `toString contains meaningful information`() {\n    val root = Scope.buildRootScope()\n    val child = root.buildChild(\"child\")\n    val grandChild = child.buildChild(\"grand-child\")\n\n    assertThat(grandChild.toString()).contains(\"grand-child\")\n  }\n\n  @Test\n  fun `a service can be registered`() {\n    val root = Scope.buildRootScope { addService(\"key\", \"value\") }\n\n    assertThat(root.getService<String>(\"key\")).isEqualTo(\"value\")\n    assertThat(root.getService<Any>(\"key2\")).isNull()\n  }\n\n  @Test\n  fun `parents returns the chain of parent scopes`() {\n    val root = Scope.buildRootScope()\n    val child1 = root.buildChild(\"child1\")\n    val child2 = root.buildChild(\"child2\")\n    val grandchild = child1.buildChild(\"grandchild\")\n\n    assertThat(grandchild.parents(includeSelf = true).toList())\n      .containsExactly(grandchild, child1, root)\n    assertThat(grandchild.parents(includeSelf = false).toList()).containsExactly(child1, root)\n\n    assertThat(child2.parents(includeSelf = true).toList()).containsExactly(child2, root)\n    assertThat(child2.parents(includeSelf = false).toList()).containsExactly(root)\n\n    assertThat(root.parents(includeSelf = true).toList()).containsExactlyInAnyOrder(root)\n    assertThat(root.parents(includeSelf = false).toList()).isEmpty()\n  }\n\n  @Test\n  fun `a Scoped can be registered in the builder`() {\n    val scoped = FakeScoped()\n    Scope.buildRootScope {\n      register(scoped)\n      assertThat(scoped.onEnterCalled).isEqualTo(0)\n    }\n    assertThat(scoped.onEnterCalled).isEqualTo(1)\n  }\n\n  @Test\n  fun `onExit is invoked when the scope is destroyed`() {\n    val root = Scope.buildRootScope()\n    val child = root.buildChild(\"child\")\n\n    var onExitCalled = 0\n\n    child.onExit { onExitCalled++ }\n\n    root.destroy()\n    assertThat(onExitCalled).isEqualTo(1)\n  }\n\n  @Test\n  fun `destroy can be called on a scope that is currently being destroyed`() {\n    val root = Scope.buildRootScope()\n    repeat(3) {\n      val child = root.buildChild(\"child-$it\")\n      child.onExit {\n        // This makes the child destroy the parent while it's being destroyed and that\n        // triggered a bug previously.\n        root.destroy()\n      }\n    }\n\n    val children = root.children()\n\n    // Start the chain reaction.\n    children.first().destroy()\n\n    assertThat(root.isDestroyed()).isTrue()\n    children.forEach { assertThat(it.isDestroyed()).isTrue() }\n  }\n\n  private class FakeScoped : Scoped {\n    var onEnterCalled = 0\n      private set\n\n    var onExitCalled = 0\n      private set\n\n    override fun onEnterScope(scope: Scope) {\n      onEnterCalled++\n    }\n\n    override fun onExitScope() {\n      onExitCalled++\n    }\n  }\n}\n"
  },
  {
    "path": "scope/public/src/commonTest/kotlin/software/amazon/app/platform/scope/coroutine/CoroutineScopeScopedTest.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport assertk.assertThat\nimport assertk.assertions.contains\nimport assertk.assertions.containsExactly\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.test.Test\nimport kotlin.test.assertFailsWith\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.scope.Scope\nimport software.amazon.app.platform.scope.Scoped\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass CoroutineScopeScopedTest {\n\n  @Test\n  fun `the provided context must have a name for easier debugging`() {\n    assertFailsWith<IllegalArgumentException> { CoroutineScopeScoped(EmptyCoroutineContext) }\n\n    CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n  }\n\n  @Test\n  fun `onExitScope cancels the CoroutineContext`() {\n    val scope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n    scope.onExitScope()\n\n    assertThat(scope.coroutineContext.isActive).isFalse()\n  }\n\n  @Test\n  fun `a child scope has a meaningful default name`() {\n    val scope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n    val child = scope.createChild()\n    val name = child.coroutineContext[CoroutineName]?.name\n\n    assertThat(name).isEqualTo(\"abc-child\")\n  }\n\n  @Test\n  fun `a child scope uses the provided name`() {\n    val scope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n    val child = scope.createChild(CoroutineName(\"def\"))\n    val name = child.coroutineContext[CoroutineName]?.name\n\n    assertThat(name).isEqualTo(\"def\")\n  }\n\n  @OptIn(ExperimentalStdlibApi::class)\n  @Test\n  fun `a child scope can use a different dispatcher`() {\n    val scope = CoroutineScopeScoped(Job() + StandardTestDispatcher() + CoroutineName(\"abc\"))\n    val child = scope.createChild(UnconfinedTestDispatcher())\n\n    assertThat(scope.coroutineContext[CoroutineDispatcher.Key].toString())\n      .contains(\"StandardTestDispatcher\")\n    assertThat(child.coroutineContext[CoroutineDispatcher.Key].toString())\n      .contains(\"UnconfinedTestDispatcher\")\n  }\n\n  @Test\n  fun `the child scope is canceled when the parent is canceled`() {\n    val scope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n    val child = scope.createChild()\n\n    assertThat(scope.isActive).isTrue()\n    assertThat(child.isActive).isTrue()\n\n    scope.cancel()\n    assertThat(scope.isActive).isFalse()\n    assertThat(child.isActive).isFalse()\n  }\n\n  @Test\n  fun `the parent scope is not canceled when the child is canceled`() {\n    val scope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n    val child = scope.createChild()\n\n    assertThat(scope.isActive).isTrue()\n    assertThat(child.isActive).isTrue()\n\n    child.cancel()\n    assertThat(scope.isActive).isTrue()\n    assertThat(child.isActive).isFalse()\n  }\n\n  @Test\n  fun `the CoroutineScope is canceled before any other Scoped instance`() = runTest {\n    val scope = Scope.buildRootScope()\n    val exitScopeOrder = mutableListOf<String>()\n\n    val coroutineScope =\n      CoroutineScopeScoped(Job() + CoroutineName(\"abc\") + UnconfinedTestDispatcher())\n\n    coroutineScope.launch {\n      try {\n        delay(10)\n      } catch (ignored: CancellationException) {\n        exitScopeOrder += \"coroutine\"\n      }\n    }\n\n    scope.register(\n      object : Scoped {\n        override fun onExitScope() {\n          exitScopeOrder += \"first\"\n        }\n      }\n    )\n\n    scope.register(coroutineScope)\n\n    scope.register(\n      object : Scoped {\n        override fun onExitScope() {\n          exitScopeOrder += \"third\"\n        }\n      }\n    )\n\n    scope.destroy()\n\n    assertThat(exitScopeOrder).containsExactly(\"coroutine\", \"first\", \"third\")\n  }\n}\n"
  },
  {
    "path": "scope/public/src/commonTest/kotlin/software/amazon/app/platform/scope/coroutine/CoroutineScopeServiceTest.kt",
    "content": "package software.amazon.app.platform.scope.coroutine\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.isActive\nimport software.amazon.app.platform.scope.Scope\n\nclass CoroutineScopeServiceTest {\n\n  @Test\n  fun `a coroutine scope can be registered in a scope`() {\n    val coroutineScope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n\n    val scope = Scope.buildRootScope { addCoroutineScopeScoped(coroutineScope) }\n\n    assertThat(scope.coroutineScope().coroutineContext[CoroutineName]?.name).isEqualTo(\"abc-child\")\n  }\n\n  @Test\n  fun `a coroutine scope is canceled when the Scope is destroyed`() {\n    val coroutineScope = CoroutineScopeScoped(Job() + CoroutineName(\"abc\"))\n\n    val scope = Scope.buildRootScope { addCoroutineScopeScoped(coroutineScope) }\n\n    val childCoroutineScope = scope.coroutineScope()\n\n    scope.destroy()\n    assertThat(coroutineScope.isActive).isFalse()\n    assertThat(childCoroutineScope.isActive).isFalse()\n  }\n}\n"
  },
  {
    "path": "scope/testing/api/android/testing.api",
    "content": "public final class software/amazon/app/platform/scope/RunTestWithScopeKt {\n\tpublic static final fun runTestWithScope (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V\n\tpublic static synthetic fun runTestWithScope$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V\n\tpublic static final fun runTestWithScoped (Lsoftware/amazon/app/platform/scope/Scoped;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V\n\tpublic static synthetic fun runTestWithScoped$default (Lsoftware/amazon/app/platform/scope/Scoped;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V\n}\n\npublic final class software/amazon/app/platform/scope/TestScopeKt {\n\tpublic static final fun buildTestScope (Lsoftware/amazon/app/platform/scope/Scope$Companion;Lkotlinx/coroutines/test/TestScope;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildTestScope$default (Lsoftware/amazon/app/platform/scope/Scope$Companion;Lkotlinx/coroutines/test/TestScope;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\n"
  },
  {
    "path": "scope/testing/api/desktop/testing.api",
    "content": "public final class software/amazon/app/platform/scope/RunTestWithScopeKt {\n\tpublic static final fun runTestWithScope (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V\n\tpublic static synthetic fun runTestWithScope$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V\n\tpublic static final fun runTestWithScoped (Lsoftware/amazon/app/platform/scope/Scoped;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V\n\tpublic static synthetic fun runTestWithScoped$default (Lsoftware/amazon/app/platform/scope/Scoped;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)V\n}\n\npublic final class software/amazon/app/platform/scope/TestScopeKt {\n\tpublic static final fun buildTestScope (Lsoftware/amazon/app/platform/scope/Scope$Companion;Lkotlinx/coroutines/test/TestScope;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)Lsoftware/amazon/app/platform/scope/Scope;\n\tpublic static synthetic fun buildTestScope$default (Lsoftware/amazon/app/platform/scope/Scope$Companion;Lkotlinx/coroutines/test/TestScope;Ljava/lang/String;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsoftware/amazon/app/platform/scope/Scope;\n}\n\n"
  },
  {
    "path": "scope/testing/build.gradle",
    "content": "plugins {\n    id 'software.amazon.app.platform.lib'\n}\n\nappPlatformBuildSrc {\n    enablePublishing true\n}\n\ndependencies {\n    commonTestImplementation project(':internal:testing')\n}\n"
  },
  {
    "path": "scope/testing/src/commonMain/kotlin/software/amazon/app/platform/scope/RunTestWithScope.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.TestResult\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.runTest\n\n/**\n * This function is similar to [runTest] and will additionally create a [Scope] using\n * [Scope.Companion.buildTestScope] for you. The [Scope] will be destroyed before the test finishes\n * and clean up all resources. A common pattern to test classes implementing [Scoped] looks like the\n * following\n *\n * ```\n * @Test\n * fun `test lifecycle`() = runTestWithScope { scope ->\n *    val myScoped = MyScoped()\n *\n *    // This calls myScoped.onEnterScope() for you.\n *    scope.register(myScoped)\n *\n *    ...\n *    // myScoped.onExitScope() will be called for you.\n * }\n * ```\n *\n * Similar to [Scope.Companion.buildTestScope], this function will use [StandardTestDispatcher] by\n * default, but this can be overridden using [context] parameter:\n * ```\n * runTestWithScope(UnconfinedTestDispatcher()) { ... }\n * ```\n */\npublic fun runTestWithScope(\n  context: CoroutineContext = EmptyCoroutineContext,\n  builder: (Scope.Builder.() -> Unit)? = null,\n  testBody: suspend TestScope.(scope: Scope) -> Unit,\n): TestResult {\n  return runTest(context) {\n    val rootScope =\n      Scope.buildTestScope(\n        testScope = this,\n        name = \"test-root-scope\",\n        context = backgroundScope.coroutineContext + context,\n        builder = builder,\n      )\n\n    try {\n      testBody.invoke(this, rootScope)\n    } finally {\n      rootScope.destroy()\n    }\n  }\n}\n\n/**\n * This function is similar to [runTest] and will additionally create a [Scope] using\n * [Scope.Companion.buildTestScope] for you. The [Scope] will be destroyed before the test finishes\n * and clean up all resources. The given [scoped] will be registered automatically in created\n * [Scope] and its [Scoped.onEnterScope] function will be called before the block [testBody] is\n * called. If this behavior is not desired, then you can use [runTestWithScope] and register your\n * [scoped] object manually.\n *\n * A common pattern to test classes implementing [Scoped] looks like the following:\n * ```\n * private lateinit var myScoped: MyScoped\n *\n * @Before\n * fun prepare() {\n *    myScoped = MyScoped()\n * }\n *\n * @Test\n * fun `test lifecycle`() = runTestWithScoped(myScoped) {\n *    // myScoped.onEnterScope() was already called at this point.\n *    ...\n *    // myScoped.onExitScope() will be called for you.\n * }\n * ```\n *\n * Similar to [Scope.Companion.buildTestScope], this function will use [StandardTestDispatcher] by\n * default, but this can be overridden using [context] parameter:\n * ```\n * runTestWithScoped(myScoped, UnconfinedTestDispatcher()) { ... }\n * ```\n */\npublic fun runTestWithScoped(\n  scoped: Scoped,\n  context: CoroutineContext = EmptyCoroutineContext,\n  builder: (Scope.Builder.() -> Unit)? = null,\n  testBody: suspend TestScope.(scope: Scope) -> Unit,\n): TestResult =\n  runTestWithScope(context = context, builder = builder) { scope ->\n    scope.register(scoped)\n    testBody(this, scope)\n  }\n"
  },
  {
    "path": "scope/testing/src/commonMain/kotlin/software/amazon/app/platform/scope/TestScope.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.job\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport software.amazon.app.platform.scope.coroutine.CoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.addCoroutineScopeScoped\nimport software.amazon.app.platform.scope.coroutine.coroutineScope\n\n/**\n * It's recommended to use this builder instead of [Scope.buildRootScope] in unit tests.\n *\n * This builder creates a new root scope similar to [Scope.buildRootScope], but also adds a\n * coroutine scope automatically to the returned scope to support the [coroutineScope] extension.\n * The coroutine scope uses [StandardTestDispatcher] by default. Use [context] to override any\n * element of coroutine scope, e.g. to use the unconfined behavior you can pass in\n * [UnconfinedTestDispatcher]:\n * ```\n * @Test\n * fun `my test`() = runTest {\n *   val scope = Scope.buildTestScope(this, context = UnconfinedTestDispatcher())\n * }\n * ```\n *\n * If you don't rely on the coroutine runtime in your test or you don't have a parameter value for\n * [testScope], then you can safely use [Scope.buildRootScope] as well.\n *\n * ## Clean up\n *\n * When the given [testScope] is canceled, which usually happens after the test body returns, then\n * the returned scope is destroyed through [Scope.destroy] automatically if it hasn't been destroyed\n * already. This also means [Scoped.onExitScope] will be called for all registered instances.\n *\n * ## Convenience\n *\n * Consider using [runTestWithScope] as a convenience to avoid creating a [Scope] manually:\n * ```\n * @Test\n * fun `my test`() = runTestWithScope { scope ->\n *   ...\n * }\n * ```\n */\npublic fun Scope.Companion.buildTestScope(\n  testScope: TestScope,\n  name: String = \"test\",\n  context: CoroutineContext = EmptyCoroutineContext,\n  builder: (Scope.Builder.() -> Unit)? = null,\n): Scope {\n  val baseContext = testScope.backgroundScope.coroutineContext\n\n  val coroutineContext =\n    baseContext + SupervisorJob(baseContext.job) + CoroutineName(name) + context\n\n  val scope =\n    buildRootScope(name = name) {\n      addCoroutineScopeScoped(CoroutineScopeScoped(coroutineContext))\n      builder?.invoke(this)\n    }\n\n  baseContext.job.invokeOnCompletion { scope.destroy() }\n\n  return scope\n}\n"
  },
  {
    "path": "scope/testing/src/commonTest/kotlin/software/amazon/app/platform/scope/RunTestWithScopeTest.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport kotlin.test.Test\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runCurrent\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.scope.coroutine.coroutineScope\n\nclass RunTestWithScopeTest {\n\n  @Test\n  fun `a test scope comes with a coroutine scope`() = runTestWithScope { scope ->\n    assertThat(scope.name).isEqualTo(\"test-root-scope\")\n\n    val coroutineScope = scope.coroutineScope()\n    assertThat(coroutineScope.isActive).isTrue()\n  }\n\n  @Test\n  fun `a standard test dispatcher is used by default`() = runTestWithScope {\n    val job1 =\n      it.coroutineScope().launch {\n        // Do nothing\n      }\n\n    val job2 = launch {\n      // No nothing\n    }\n\n    assertThat(job1.isCompleted).isFalse()\n    assertThat(job2.isCompleted).isFalse()\n\n    runCurrent()\n\n    assertThat(job1.isCompleted).isTrue()\n    assertThat(job2.isCompleted).isTrue()\n  }\n\n  @Test\n  fun `the coroutine context can be changed`() =\n    runTestWithScope(UnconfinedTestDispatcher()) {\n      val job1 =\n        it.coroutineScope().launch {\n          // Do nothing\n        }\n      val job2 = launch {\n        // Do nothing\n      }\n\n      assertThat(job1.isCompleted).isTrue()\n      assertThat(job2.isCompleted).isTrue()\n    }\n\n  @Test\n  @IgnoreWasm\n  fun `onEnterScope and onExitScope will be called when registering the scoped`() {\n    val scoped = MyScoped()\n    assertThat(scoped.onEnterScopeCalled).isFalse()\n    assertThat(scoped.onExitScopeCalled).isFalse()\n\n    runTestWithScope { scope ->\n      assertThat(scoped.onEnterScopeCalled).isFalse()\n      assertThat(scoped.onExitScopeCalled).isFalse()\n\n      scope.register(scoped)\n\n      assertThat(scoped.onEnterScopeCalled).isTrue()\n      assertThat(scoped.onExitScopeCalled).isFalse()\n    }\n\n    assertThat(scoped.onEnterScopeCalled).isTrue()\n    assertThat(scoped.onExitScopeCalled).isTrue()\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `onExitScope will not be called when calling onEnterScope manually`() {\n    val scoped = MyScoped()\n    assertThat(scoped.onEnterScopeCalled).isFalse()\n    assertThat(scoped.onExitScopeCalled).isFalse()\n\n    runTestWithScope { scope ->\n      assertThat(scoped.onEnterScopeCalled).isFalse()\n      assertThat(scoped.onExitScopeCalled).isFalse()\n\n      scoped.onEnterScope(scope)\n\n      assertThat(scoped.onEnterScopeCalled).isTrue()\n      assertThat(scoped.onExitScopeCalled).isFalse()\n    }\n\n    assertThat(scoped.onEnterScopeCalled).isTrue()\n    assertThat(scoped.onExitScopeCalled).isFalse()\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `onEnterScope and onExitScope will be called when registering the scoped through runTestWithScoped`() {\n    val scoped = MyScoped()\n    assertThat(scoped.onEnterScopeCalled).isFalse()\n    assertThat(scoped.onExitScopeCalled).isFalse()\n\n    runTestWithScoped(scoped) {\n      assertThat(scoped.onEnterScopeCalled).isTrue()\n      assertThat(scoped.onExitScopeCalled).isFalse()\n    }\n\n    assertThat(scoped.onEnterScopeCalled).isTrue()\n    assertThat(scoped.onExitScopeCalled).isTrue()\n  }\n\n  private class MyScoped : Scoped {\n    var onEnterScopeCalled = false\n      private set\n\n    var onExitScopeCalled = false\n      private set\n\n    override fun onEnterScope(scope: Scope) {\n      onEnterScopeCalled = true\n    }\n\n    override fun onExitScope() {\n      onExitScopeCalled = true\n    }\n  }\n}\n"
  },
  {
    "path": "scope/testing/src/commonTest/kotlin/software/amazon/app/platform/scope/TestScopeTest.kt",
    "content": "package software.amazon.app.platform.scope\n\nimport assertk.assertFailure\nimport assertk.assertThat\nimport assertk.assertions.isEqualTo\nimport assertk.assertions.isFalse\nimport assertk.assertions.isTrue\nimport assertk.assertions.messageContains\nimport assertk.assertions.rootCause\nimport kotlin.test.Test\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.test.UnconfinedTestDispatcher\nimport kotlinx.coroutines.test.runCurrent\nimport kotlinx.coroutines.test.runTest\nimport software.amazon.app.platform.internal.IgnoreWasm\nimport software.amazon.app.platform.scope.coroutine.coroutineScope\nimport software.amazon.app.platform.scope.coroutine.launch\n\nclass TestScopeTest {\n\n  @Test\n  fun `a test scope comes with a coroutine scope`() = runTest {\n    val scope = Scope.buildTestScope(this, name = \"abc\")\n    assertThat(scope.name).isEqualTo(\"abc\")\n\n    val coroutineScope = scope.coroutineScope()\n    assertThat(coroutineScope.isActive).isTrue()\n\n    scope.destroy()\n    assertThat(coroutineScope.isActive).isFalse()\n  }\n\n  @Test\n  fun `a standard test dispatcher is used by default`() = runTest {\n    val job =\n      Scope.buildTestScope(this).coroutineScope().launch {\n        // Do nothing\n      }\n    assertThat(job.isCompleted).isFalse()\n\n    runCurrent()\n\n    assertThat(job.isCompleted).isTrue()\n  }\n\n  @Test\n  fun `an unconfined test dispatcher can be used`() = runTest {\n    val job =\n      Scope.buildTestScope(this, context = UnconfinedTestDispatcher()).launch {\n        // Do nothing\n      }\n    assertThat(job.isCompleted).isTrue()\n  }\n\n  @Test\n  fun `the coroutine context can be changed`() = runTest {\n    val name =\n      Scope.buildTestScope(this, context = CoroutineName(\"Test-abc\"))\n        .coroutineScope()\n        .coroutineContext[CoroutineName.Key]\n        ?.name\n    assertThat(name).isEqualTo(\"Test-abc-child\")\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `the test scope is destroyed automatically`() {\n    lateinit var scope: Scope\n\n    runTest {\n      scope = Scope.buildTestScope(this)\n      assertThat(scope.isDestroyed()).isFalse()\n    }\n\n    assertThat(scope.isDestroyed()).isTrue()\n  }\n\n  @Test\n  @IgnoreWasm\n  fun `a failure in the clean up routine causes the test to fail`() {\n    assertFailure {\n        runTest {\n          val scope = Scope.buildTestScope(this)\n          scope.onExit { error(\"test failure\") }\n        }\n      }\n      .rootCause()\n      .messageContains(\"test failure\")\n  }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "plugins {\n    id \"com.gradle.develocity\" version \"4.0.2\"\n}\n\nincludeBuild('gradle-plugin') {\n    dependencySubstitution {\n        substitute module(\"$GROUP:gradle-plugin\") using project(':')\n    }\n}\n\ndependencyResolutionManagement {\n    repositories {\n        mavenCentral()\n        google()\n        gradlePluginPortal()\n\n        maven {\n            url = \"https://central.sonatype.com/repository/maven-snapshots/\"\n        }\n    }\n}\n\ndevelocity {\n    buildScan {\n        def isCi = providers.environmentVariable(\"CI\").isPresent() ||\n                providers.systemProperty(\"CI\").isPresent() ||\n                providers.gradleProperty(\"CI\").isPresent()\n\n        if (isCi) {\n            publishing.onlyIf { true }\n            uploadInBackground = false\n        }\n\n        termsOfUseUrl = \"https://gradle.com/terms-of-service\"\n        termsOfUseAgree = \"yes\"\n    }\n}\n\nrootProject.name = 'app-platform'\n\ninclude ':di-common:public'\ninclude ':internal:testing'\ninclude ':kotlin-inject:impl'\ninclude ':kotlin-inject:public'\ninclude ':kotlin-inject-extensions:contribute:impl-code-generators'\ninclude ':kotlin-inject-extensions:contribute:public'\ninclude ':ksp-common:public'\ninclude ':ksp-common:testing'\ninclude ':metro:impl'\ninclude ':metro:public'\ninclude ':metro-extensions:contribute:impl-compiler-plugin'\ninclude ':metro-extensions:contribute:impl-code-generators'\ninclude ':presenter:public'\ninclude ':presenter-molecule:impl'\ninclude ':presenter-molecule:public'\ninclude ':presenter-molecule:testing'\ninclude ':recipes:app'\ninclude ':recipes:common:impl'\ninclude ':renderer:public'\ninclude ':renderer-android-view:public'\ninclude ':renderer-compose-multiplatform:public'\ninclude ':robot:public'\ninclude ':robot-compose-multiplatform:public'\ninclude ':robot-internal:public'\ninclude ':sample:app'\ninclude ':sample:login:impl'\ninclude ':sample:login:impl-robots'\ninclude ':sample:login:public'\ninclude ':sample:navigation:impl'\ninclude ':sample:navigation:public'\ninclude ':sample:templates:impl'\ninclude ':sample:templates:public'\ninclude ':sample:user:impl'\ninclude ':sample:user:impl-robots'\ninclude ':sample:user:public'\ninclude ':sample:user:testing'\ninclude ':scope:public'\ninclude ':scope:testing'\n"
  }
]