diff --git a/.editorconfig b/.editorconfig
index adaaa329eb..01c20e5bc6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,611 +14,13 @@ ij_visual_guides = none
ij_wrap_on_typing = false
[*.{kt,kts}]
-ktlint_code_style = official
+ktlint_code_style = ktlint_official
twitter_compose_allowed_composition_locals = LocalTypographySettings,LocalDimens,LocalWindowSize,LocalFoldableHinge
-[*.java]
-ij_java_align_consecutive_assignments = false
-ij_java_align_consecutive_variable_declarations = false
-ij_java_align_group_field_declarations = false
-ij_java_align_multiline_annotation_parameters = false
-ij_java_align_multiline_array_initializer_expression = false
-ij_java_align_multiline_assignment = false
-ij_java_align_multiline_binary_operation = false
-ij_java_align_multiline_chained_methods = false
-ij_java_align_multiline_extends_list = false
-ij_java_align_multiline_for = true
-ij_java_align_multiline_method_parentheses = false
-ij_java_align_multiline_parameters = true
-ij_java_align_multiline_parameters_in_calls = false
-ij_java_align_multiline_parenthesized_expression = false
-ij_java_align_multiline_records = true
-ij_java_align_multiline_resources = true
-ij_java_align_multiline_ternary_operation = false
-ij_java_align_multiline_text_blocks = false
-ij_java_align_multiline_throws_list = false
-ij_java_align_subsequent_simple_methods = false
-ij_java_align_throws_keyword = false
-ij_java_annotation_parameter_wrap = off
-ij_java_array_initializer_new_line_after_left_brace = false
-ij_java_array_initializer_right_brace_on_new_line = false
-ij_java_array_initializer_wrap = off
-ij_java_assert_statement_colon_on_next_line = false
-ij_java_assert_statement_wrap = off
-ij_java_assignment_wrap = off
-ij_java_binary_operation_sign_on_next_line = false
-ij_java_binary_operation_wrap = off
-ij_java_blank_lines_after_anonymous_class_header = 0
-ij_java_blank_lines_after_class_header = 0
-ij_java_blank_lines_after_imports = 1
-ij_java_blank_lines_after_package = 1
-ij_java_blank_lines_around_class = 1
-ij_java_blank_lines_around_field = 0
-ij_java_blank_lines_around_field_in_interface = 0
-ij_java_blank_lines_around_initializer = 1
-ij_java_blank_lines_around_method = 1
-ij_java_blank_lines_around_method_in_interface = 1
-ij_java_blank_lines_before_class_end = 0
-ij_java_blank_lines_before_imports = 1
-ij_java_blank_lines_before_method_body = 0
-ij_java_blank_lines_before_package = 0
-ij_java_block_brace_style = end_of_line
-ij_java_block_comment_at_first_column = true
-ij_java_call_parameters_new_line_after_left_paren = false
-ij_java_call_parameters_right_paren_on_new_line = false
-ij_java_call_parameters_wrap = off
-ij_java_case_statement_on_separate_line = true
-ij_java_catch_on_new_line = false
-ij_java_class_annotation_wrap = split_into_lines
-ij_java_class_brace_style = end_of_line
-ij_java_class_count_to_use_import_on_demand = 99
-ij_java_class_names_in_javadoc = 1
-ij_java_do_not_indent_top_level_class_members = false
-ij_java_do_not_wrap_after_single_annotation = false
-ij_java_do_while_brace_force = never
-ij_java_doc_add_blank_line_after_description = true
-ij_java_doc_add_blank_line_after_param_comments = false
-ij_java_doc_add_blank_line_after_return = false
-ij_java_doc_add_p_tag_on_empty_lines = true
-ij_java_doc_align_exception_comments = true
-ij_java_doc_align_param_comments = true
-ij_java_doc_do_not_wrap_if_one_line = false
-ij_java_doc_enable_formatting = true
-ij_java_doc_enable_leading_asterisks = true
-ij_java_doc_indent_on_continuation = false
-ij_java_doc_keep_empty_lines = true
-ij_java_doc_keep_empty_parameter_tag = true
-ij_java_doc_keep_empty_return_tag = true
-ij_java_doc_keep_empty_throws_tag = true
-ij_java_doc_keep_invalid_tags = true
-ij_java_doc_param_description_on_new_line = false
-ij_java_doc_preserve_line_breaks = false
-ij_java_doc_use_throws_not_exception_tag = true
-ij_java_else_on_new_line = false
-ij_java_enum_constants_wrap = off
-ij_java_extends_keyword_wrap = off
-ij_java_extends_list_wrap = off
-ij_java_field_annotation_wrap = split_into_lines
-ij_java_finally_on_new_line = false
-ij_java_for_brace_force = never
-ij_java_for_statement_new_line_after_left_paren = false
-ij_java_for_statement_right_paren_on_new_line = false
-ij_java_for_statement_wrap = off
-ij_java_generate_final_locals = false
-ij_java_generate_final_parameters = false
-ij_java_if_brace_force = never
-ij_java_imports_layout = $android.**,$androidx.**,$com.**,$junit.**,$net.**,$org.**,$java.**,$javax.**,$*,|,android.**,|,androidx.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,|
-ij_java_indent_case_from_switch = true
-ij_java_insert_inner_class_imports = false
-ij_java_insert_override_annotation = true
-ij_java_keep_blank_lines_before_right_brace = 2
-ij_java_keep_blank_lines_between_package_declaration_and_header = 2
-ij_java_keep_blank_lines_in_code = 2
-ij_java_keep_blank_lines_in_declarations = 2
-ij_java_keep_control_statement_in_one_line = true
-ij_java_keep_first_column_comment = true
-ij_java_keep_indents_on_empty_lines = false
-ij_java_keep_line_breaks = true
-ij_java_keep_multiple_expressions_in_one_line = false
-ij_java_keep_simple_blocks_in_one_line = false
-ij_java_keep_simple_classes_in_one_line = false
-ij_java_keep_simple_lambdas_in_one_line = false
-ij_java_keep_simple_methods_in_one_line = false
-ij_java_label_indent_absolute = false
-ij_java_label_indent_size = 0
-ij_java_lambda_brace_style = end_of_line
-ij_java_layout_static_imports_separately = true
-ij_java_line_comment_add_space = false
-ij_java_line_comment_at_first_column = true
-ij_java_method_annotation_wrap = split_into_lines
-ij_java_method_brace_style = end_of_line
-ij_java_method_call_chain_wrap = off
-ij_java_method_parameters_new_line_after_left_paren = false
-ij_java_method_parameters_right_paren_on_new_line = false
-ij_java_method_parameters_wrap = off
-ij_java_modifier_list_wrap = false
-ij_java_names_count_to_use_import_on_demand = 99
-ij_java_new_line_after_lparen_in_record_header = false
-ij_java_parameter_annotation_wrap = off
-ij_java_parentheses_expression_new_line_after_left_paren = false
-ij_java_parentheses_expression_right_paren_on_new_line = false
-ij_java_place_assignment_sign_on_next_line = false
-ij_java_prefer_longer_names = true
-ij_java_prefer_parameters_wrap = false
-ij_java_record_components_wrap = normal
-ij_java_repeat_synchronized = true
-ij_java_replace_instanceof_and_cast = false
-ij_java_replace_null_check = true
-ij_java_replace_sum_lambda_with_method_ref = true
-ij_java_resource_list_new_line_after_left_paren = false
-ij_java_resource_list_right_paren_on_new_line = false
-ij_java_resource_list_wrap = off
-ij_java_rparen_on_new_line_in_record_header = false
-ij_java_space_after_closing_angle_bracket_in_type_argument = false
-ij_java_space_after_colon = true
-ij_java_space_after_comma = true
-ij_java_space_after_comma_in_type_arguments = true
-ij_java_space_after_for_semicolon = true
-ij_java_space_after_quest = true
-ij_java_space_after_type_cast = true
-ij_java_space_before_annotation_array_initializer_left_brace = false
-ij_java_space_before_annotation_parameter_list = false
-ij_java_space_before_array_initializer_left_brace = false
-ij_java_space_before_catch_keyword = true
-ij_java_space_before_catch_left_brace = true
-ij_java_space_before_catch_parentheses = true
-ij_java_space_before_class_left_brace = true
-ij_java_space_before_colon = true
-ij_java_space_before_colon_in_foreach = true
-ij_java_space_before_comma = false
-ij_java_space_before_do_left_brace = true
-ij_java_space_before_else_keyword = true
-ij_java_space_before_else_left_brace = true
-ij_java_space_before_finally_keyword = true
-ij_java_space_before_finally_left_brace = true
-ij_java_space_before_for_left_brace = true
-ij_java_space_before_for_parentheses = true
-ij_java_space_before_for_semicolon = false
-ij_java_space_before_if_left_brace = true
-ij_java_space_before_if_parentheses = true
-ij_java_space_before_method_call_parentheses = false
-ij_java_space_before_method_left_brace = true
-ij_java_space_before_method_parentheses = false
-ij_java_space_before_opening_angle_bracket_in_type_parameter = false
-ij_java_space_before_quest = true
-ij_java_space_before_switch_left_brace = true
-ij_java_space_before_switch_parentheses = true
-ij_java_space_before_synchronized_left_brace = true
-ij_java_space_before_synchronized_parentheses = true
-ij_java_space_before_try_left_brace = true
-ij_java_space_before_try_parentheses = true
-ij_java_space_before_type_parameter_list = false
-ij_java_space_before_while_keyword = true
-ij_java_space_before_while_left_brace = true
-ij_java_space_before_while_parentheses = true
-ij_java_space_inside_one_line_enum_braces = false
-ij_java_space_within_empty_array_initializer_braces = false
-ij_java_space_within_empty_method_call_parentheses = false
-ij_java_space_within_empty_method_parentheses = false
-ij_java_spaces_around_additive_operators = true
-ij_java_spaces_around_assignment_operators = true
-ij_java_spaces_around_bitwise_operators = true
-ij_java_spaces_around_equality_operators = true
-ij_java_spaces_around_lambda_arrow = true
-ij_java_spaces_around_logical_operators = true
-ij_java_spaces_around_method_ref_dbl_colon = false
-ij_java_spaces_around_multiplicative_operators = true
-ij_java_spaces_around_relational_operators = true
-ij_java_spaces_around_shift_operators = true
-ij_java_spaces_around_type_bounds_in_type_parameters = true
-ij_java_spaces_around_unary_operator = false
-ij_java_spaces_within_angle_brackets = false
-ij_java_spaces_within_annotation_parentheses = false
-ij_java_spaces_within_array_initializer_braces = false
-ij_java_spaces_within_braces = false
-ij_java_spaces_within_brackets = false
-ij_java_spaces_within_cast_parentheses = false
-ij_java_spaces_within_catch_parentheses = false
-ij_java_spaces_within_for_parentheses = false
-ij_java_spaces_within_if_parentheses = false
-ij_java_spaces_within_method_call_parentheses = false
-ij_java_spaces_within_method_parentheses = false
-ij_java_spaces_within_parentheses = false
-ij_java_spaces_within_record_header = false
-ij_java_spaces_within_switch_parentheses = false
-ij_java_spaces_within_synchronized_parentheses = false
-ij_java_spaces_within_try_parentheses = false
-ij_java_spaces_within_while_parentheses = false
-ij_java_special_else_if_treatment = true
-ij_java_subclass_name_suffix = Impl
-ij_java_ternary_operation_signs_on_next_line = false
-ij_java_ternary_operation_wrap = off
-ij_java_test_name_suffix = Test
-ij_java_throws_keyword_wrap = off
-ij_java_throws_list_wrap = off
-ij_java_use_external_annotations = false
-ij_java_use_fq_class_names = false
-ij_java_use_relative_indents = false
-ij_java_use_single_class_imports = true
-ij_java_variable_annotation_wrap = off
-ij_java_visibility = public
-ij_java_while_brace_force = never
-ij_java_while_on_new_line = false
-ij_java_wrap_comments = false
-ij_java_wrap_first_method_in_call_chain = false
-ij_java_wrap_long_lines = false
-
-[*.properties]
-ij_properties_align_group_field_declarations = false
-ij_properties_keep_blank_lines = false
-ij_properties_key_value_delimiter = equals
-ij_properties_spaces_around_key_value_delimiter = false
-
-[.editorconfig]
-ij_editorconfig_align_group_field_declarations = false
-ij_editorconfig_space_after_colon = false
-ij_editorconfig_space_after_comma = true
-ij_editorconfig_space_before_colon = false
-ij_editorconfig_space_before_comma = false
-ij_editorconfig_spaces_around_assignment_operators = true
-
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.opml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,rss_kuketz,rss_morningpaper}]
indent_size = 2
tab_width = 2
-ij_continuation_indent_size = 2
-ij_xml_align_attributes = false
-ij_xml_align_text = false
-ij_xml_attribute_wrap = normal
-ij_xml_block_comment_at_first_column = true
-ij_xml_keep_blank_lines = 2
-ij_xml_keep_indents_on_empty_lines = false
-ij_xml_keep_line_breaks = false
-ij_xml_keep_line_breaks_in_text = true
-ij_xml_keep_whitespaces = false
-ij_xml_keep_whitespaces_around_cdata = preserve
-ij_xml_keep_whitespaces_inside_cdata = false
-ij_xml_line_comment_at_first_column = true
-ij_xml_space_after_tag_name = false
-ij_xml_space_around_equals_in_attribute = false
-ij_xml_space_inside_empty_tag = true
-ij_xml_text_wrap = normal
-ij_xml_use_custom_settings = true
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
-ij_shell_binary_ops_start_line = false
-ij_shell_keep_column_alignment_padding = false
-ij_shell_minify_program = false
-ij_shell_redirect_followed_by_space = false
-ij_shell_switch_cases_indented = false
-
-[{*.gant,*.gradle,*.groovy,*.gy}]
-indent_size = 2
-ij_groovy_align_group_field_declarations = false
-ij_groovy_align_multiline_array_initializer_expression = false
-ij_groovy_align_multiline_assignment = false
-ij_groovy_align_multiline_binary_operation = false
-ij_groovy_align_multiline_chained_methods = false
-ij_groovy_align_multiline_extends_list = false
-ij_groovy_align_multiline_for = true
-ij_groovy_align_multiline_list_or_map = true
-ij_groovy_align_multiline_method_parentheses = false
-ij_groovy_align_multiline_parameters = true
-ij_groovy_align_multiline_parameters_in_calls = false
-ij_groovy_align_multiline_resources = true
-ij_groovy_align_multiline_ternary_operation = false
-ij_groovy_align_multiline_throws_list = false
-ij_groovy_align_named_args_in_map = true
-ij_groovy_align_throws_keyword = false
-ij_groovy_array_initializer_new_line_after_left_brace = false
-ij_groovy_array_initializer_right_brace_on_new_line = false
-ij_groovy_array_initializer_wrap = off
-ij_groovy_assert_statement_wrap = off
-ij_groovy_assignment_wrap = off
-ij_groovy_binary_operation_wrap = off
-ij_groovy_blank_lines_after_class_header = 0
-ij_groovy_blank_lines_after_imports = 1
-ij_groovy_blank_lines_after_package = 1
-ij_groovy_blank_lines_around_class = 1
-ij_groovy_blank_lines_around_field = 0
-ij_groovy_blank_lines_around_field_in_interface = 0
-ij_groovy_blank_lines_around_method = 1
-ij_groovy_blank_lines_around_method_in_interface = 1
-ij_groovy_blank_lines_before_imports = 1
-ij_groovy_blank_lines_before_method_body = 0
-ij_groovy_blank_lines_before_package = 0
-ij_groovy_block_brace_style = end_of_line
-ij_groovy_block_comment_at_first_column = true
-ij_groovy_call_parameters_new_line_after_left_paren = false
-ij_groovy_call_parameters_right_paren_on_new_line = false
-ij_groovy_call_parameters_wrap = off
-ij_groovy_catch_on_new_line = false
-ij_groovy_class_annotation_wrap = split_into_lines
-ij_groovy_class_brace_style = end_of_line
-ij_groovy_class_count_to_use_import_on_demand = 5
-ij_groovy_do_while_brace_force = never
-ij_groovy_else_on_new_line = false
-ij_groovy_enum_constants_wrap = off
-ij_groovy_extends_keyword_wrap = off
-ij_groovy_extends_list_wrap = off
-ij_groovy_field_annotation_wrap = split_into_lines
-ij_groovy_finally_on_new_line = false
-ij_groovy_for_brace_force = never
-ij_groovy_for_statement_new_line_after_left_paren = false
-ij_groovy_for_statement_right_paren_on_new_line = false
-ij_groovy_for_statement_wrap = off
-ij_groovy_if_brace_force = never
-ij_groovy_import_annotation_wrap = 2
-ij_groovy_imports_layout = *,|,javax.**,java.**,|,$*
-ij_groovy_indent_case_from_switch = true
-ij_groovy_indent_label_blocks = true
-ij_groovy_insert_inner_class_imports = false
-ij_groovy_keep_blank_lines_before_right_brace = 2
-ij_groovy_keep_blank_lines_in_code = 2
-ij_groovy_keep_blank_lines_in_declarations = 2
-ij_groovy_keep_control_statement_in_one_line = true
-ij_groovy_keep_first_column_comment = true
-ij_groovy_keep_indents_on_empty_lines = false
-ij_groovy_keep_line_breaks = true
-ij_groovy_keep_multiple_expressions_in_one_line = false
-ij_groovy_keep_simple_blocks_in_one_line = false
-ij_groovy_keep_simple_classes_in_one_line = true
-ij_groovy_keep_simple_lambdas_in_one_line = true
-ij_groovy_keep_simple_methods_in_one_line = true
-ij_groovy_label_indent_absolute = false
-ij_groovy_label_indent_size = 0
-ij_groovy_lambda_brace_style = end_of_line
-ij_groovy_layout_static_imports_separately = true
-ij_groovy_line_comment_add_space = false
-ij_groovy_line_comment_at_first_column = true
-ij_groovy_method_annotation_wrap = split_into_lines
-ij_groovy_method_brace_style = end_of_line
-ij_groovy_method_call_chain_wrap = off
-ij_groovy_method_parameters_new_line_after_left_paren = false
-ij_groovy_method_parameters_right_paren_on_new_line = false
-ij_groovy_method_parameters_wrap = off
-ij_groovy_modifier_list_wrap = false
-ij_groovy_names_count_to_use_import_on_demand = 3
-ij_groovy_parameter_annotation_wrap = off
-ij_groovy_parentheses_expression_new_line_after_left_paren = false
-ij_groovy_parentheses_expression_right_paren_on_new_line = false
-ij_groovy_prefer_parameters_wrap = false
-ij_groovy_resource_list_new_line_after_left_paren = false
-ij_groovy_resource_list_right_paren_on_new_line = false
-ij_groovy_resource_list_wrap = off
-ij_groovy_space_after_assert_separator = true
-ij_groovy_space_after_colon = true
-ij_groovy_space_after_comma = true
-ij_groovy_space_after_comma_in_type_arguments = true
-ij_groovy_space_after_for_semicolon = true
-ij_groovy_space_after_quest = true
-ij_groovy_space_after_type_cast = true
-ij_groovy_space_before_annotation_parameter_list = false
-ij_groovy_space_before_array_initializer_left_brace = false
-ij_groovy_space_before_assert_separator = false
-ij_groovy_space_before_catch_keyword = true
-ij_groovy_space_before_catch_left_brace = true
-ij_groovy_space_before_catch_parentheses = true
-ij_groovy_space_before_class_left_brace = true
-ij_groovy_space_before_closure_left_brace = true
-ij_groovy_space_before_colon = true
-ij_groovy_space_before_comma = false
-ij_groovy_space_before_do_left_brace = true
-ij_groovy_space_before_else_keyword = true
-ij_groovy_space_before_else_left_brace = true
-ij_groovy_space_before_finally_keyword = true
-ij_groovy_space_before_finally_left_brace = true
-ij_groovy_space_before_for_left_brace = true
-ij_groovy_space_before_for_parentheses = true
-ij_groovy_space_before_for_semicolon = false
-ij_groovy_space_before_if_left_brace = true
-ij_groovy_space_before_if_parentheses = true
-ij_groovy_space_before_method_call_parentheses = false
-ij_groovy_space_before_method_left_brace = true
-ij_groovy_space_before_method_parentheses = false
-ij_groovy_space_before_quest = true
-ij_groovy_space_before_switch_left_brace = true
-ij_groovy_space_before_switch_parentheses = true
-ij_groovy_space_before_synchronized_left_brace = true
-ij_groovy_space_before_synchronized_parentheses = true
-ij_groovy_space_before_try_left_brace = true
-ij_groovy_space_before_try_parentheses = true
-ij_groovy_space_before_while_keyword = true
-ij_groovy_space_before_while_left_brace = true
-ij_groovy_space_before_while_parentheses = true
-ij_groovy_space_in_named_argument = true
-ij_groovy_space_in_named_argument_before_colon = false
-ij_groovy_space_within_empty_array_initializer_braces = false
-ij_groovy_space_within_empty_method_call_parentheses = false
-ij_groovy_spaces_around_additive_operators = true
-ij_groovy_spaces_around_assignment_operators = true
-ij_groovy_spaces_around_bitwise_operators = true
-ij_groovy_spaces_around_equality_operators = true
-ij_groovy_spaces_around_lambda_arrow = true
-ij_groovy_spaces_around_logical_operators = true
-ij_groovy_spaces_around_multiplicative_operators = true
-ij_groovy_spaces_around_regex_operators = true
-ij_groovy_spaces_around_relational_operators = true
-ij_groovy_spaces_around_shift_operators = true
-ij_groovy_spaces_within_annotation_parentheses = false
-ij_groovy_spaces_within_array_initializer_braces = false
-ij_groovy_spaces_within_braces = true
-ij_groovy_spaces_within_brackets = false
-ij_groovy_spaces_within_cast_parentheses = false
-ij_groovy_spaces_within_catch_parentheses = false
-ij_groovy_spaces_within_for_parentheses = false
-ij_groovy_spaces_within_gstring_injection_braces = false
-ij_groovy_spaces_within_if_parentheses = false
-ij_groovy_spaces_within_list_or_map = false
-ij_groovy_spaces_within_method_call_parentheses = false
-ij_groovy_spaces_within_method_parentheses = false
-ij_groovy_spaces_within_parentheses = false
-ij_groovy_spaces_within_switch_parentheses = false
-ij_groovy_spaces_within_synchronized_parentheses = false
-ij_groovy_spaces_within_try_parentheses = false
-ij_groovy_spaces_within_tuple_expression = false
-ij_groovy_spaces_within_while_parentheses = false
-ij_groovy_special_else_if_treatment = true
-ij_groovy_ternary_operation_wrap = off
-ij_groovy_throws_keyword_wrap = off
-ij_groovy_throws_list_wrap = off
-ij_groovy_use_flying_geese_braces = false
-ij_groovy_use_fq_class_names = false
-ij_groovy_use_fq_class_names_in_javadoc = true
-ij_groovy_use_relative_indents = false
-ij_groovy_use_single_class_imports = true
-ij_groovy_variable_annotation_wrap = off
-ij_groovy_while_brace_force = never
-ij_groovy_while_on_new_line = false
-ij_groovy_wrap_long_lines = false
-
-[{*.gradle.kts,*.kt,*.kts,*.main.kts}]
-ij_kotlin_align_in_columns_case_branch = false
-ij_kotlin_align_multiline_binary_operation = false
-ij_kotlin_align_multiline_extends_list = false
-ij_kotlin_align_multiline_method_parentheses = false
-ij_kotlin_align_multiline_parameters = true
-ij_kotlin_align_multiline_parameters_in_calls = false
-ij_kotlin_allow_trailing_comma = true
-ij_kotlin_allow_trailing_comma_on_call_site = true
-ij_kotlin_assignment_wrap = normal
-ij_kotlin_blank_lines_after_class_header = 0
-ij_kotlin_blank_lines_around_block_when_branches = 0
-ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
-ij_kotlin_block_comment_at_first_column = true
-ij_kotlin_call_parameters_new_line_after_left_paren = true
-ij_kotlin_call_parameters_right_paren_on_new_line = true
-ij_kotlin_call_parameters_wrap = on_every_item
-ij_kotlin_catch_on_new_line = false
-ij_kotlin_class_annotation_wrap = split_into_lines
-ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
-ij_kotlin_continuation_indent_for_chained_calls = false
-ij_kotlin_continuation_indent_for_expression_bodies = false
-ij_kotlin_continuation_indent_in_argument_lists = false
-ij_kotlin_continuation_indent_in_elvis = false
-ij_kotlin_continuation_indent_in_if_conditions = false
-ij_kotlin_continuation_indent_in_parameter_lists = false
-ij_kotlin_continuation_indent_in_supertype_lists = false
-ij_kotlin_else_on_new_line = false
-ij_kotlin_enum_constants_wrap = off
-ij_kotlin_extends_list_wrap = normal
-ij_kotlin_field_annotation_wrap = split_into_lines
-ij_kotlin_finally_on_new_line = false
-ij_kotlin_if_rparen_on_new_line = true
-ij_kotlin_import_nested_classes = false
-ij_kotlin_imports_layout = *,java.*,javax.*,kotlin.*
-ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
-ij_kotlin_keep_blank_lines_before_right_brace = 2
-ij_kotlin_keep_blank_lines_in_code = 2
-ij_kotlin_keep_blank_lines_in_declarations = 2
-ij_kotlin_keep_first_column_comment = true
-ij_kotlin_keep_indents_on_empty_lines = false
-ij_kotlin_keep_line_breaks = true
-ij_kotlin_lbrace_on_next_line = false
-ij_kotlin_line_comment_add_space = false
-ij_kotlin_line_comment_at_first_column = true
-ij_kotlin_method_annotation_wrap = split_into_lines
-ij_kotlin_method_call_chain_wrap = normal
-ij_kotlin_method_parameters_new_line_after_left_paren = true
-ij_kotlin_method_parameters_right_paren_on_new_line = true
-ij_kotlin_method_parameters_wrap = on_every_item
-ij_kotlin_name_count_to_use_star_import = 99
-ij_kotlin_name_count_to_use_star_import_for_members = 99
-ij_kotlin_packages_to_use_import_on_demand = kotlinx.android.synthetic.*
-ij_kotlin_parameter_annotation_wrap = off
-ij_kotlin_space_after_comma = true
-ij_kotlin_space_after_extend_colon = true
-ij_kotlin_space_after_type_colon = true
-ij_kotlin_space_before_catch_parentheses = true
-ij_kotlin_space_before_comma = false
-ij_kotlin_space_before_extend_colon = true
-ij_kotlin_space_before_for_parentheses = true
-ij_kotlin_space_before_if_parentheses = true
-ij_kotlin_space_before_lambda_arrow = true
-ij_kotlin_space_before_type_colon = false
-ij_kotlin_space_before_when_parentheses = true
-ij_kotlin_space_before_while_parentheses = true
-ij_kotlin_spaces_around_additive_operators = true
-ij_kotlin_spaces_around_assignment_operators = true
-ij_kotlin_spaces_around_equality_operators = true
-ij_kotlin_spaces_around_function_type_arrow = true
-ij_kotlin_spaces_around_logical_operators = true
-ij_kotlin_spaces_around_multiplicative_operators = true
-ij_kotlin_spaces_around_range = false
-ij_kotlin_spaces_around_relational_operators = true
-ij_kotlin_spaces_around_unary_operator = false
-ij_kotlin_spaces_around_when_arrow = true
-ij_kotlin_use_custom_formatting_for_modifiers = true
-ij_kotlin_variable_annotation_wrap = off
-ij_kotlin_while_on_new_line = false
-ij_kotlin_wrap_elvis_expressions = 1
-ij_kotlin_wrap_expression_body_functions = 1
-ij_kotlin_wrap_first_method_in_call_chain = false
-
-[{*.har,*.json}]
-indent_size = 2
-ij_json_keep_blank_lines_in_code = 0
-ij_json_keep_indents_on_empty_lines = false
-ij_json_keep_line_breaks = true
-ij_json_space_after_colon = true
-ij_json_space_after_comma = true
-ij_json_space_before_colon = true
-ij_json_space_before_comma = false
-ij_json_spaces_within_braces = false
-ij_json_spaces_within_brackets = false
-ij_json_wrap_long_lines = false
-
-[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
-ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
-ij_html_align_attributes = true
-ij_html_align_text = false
-ij_html_attribute_wrap = normal
-ij_html_block_comment_at_first_column = true
-ij_html_do_not_align_children_of_min_lines = 0
-ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
-ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
-ij_html_enforce_quotes = false
-ij_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
-ij_html_keep_blank_lines = 2
-ij_html_keep_indents_on_empty_lines = false
-ij_html_keep_line_breaks = true
-ij_html_keep_line_breaks_in_text = true
-ij_html_keep_whitespaces = false
-ij_html_keep_whitespaces_inside = span,pre,textarea
-ij_html_line_comment_at_first_column = true
-ij_html_new_line_after_last_attribute = never
-ij_html_new_line_before_first_attribute = never
-ij_html_quote_style = double
-ij_html_remove_new_line_before_tags = br
-ij_html_space_after_tag_name = false
-ij_html_space_around_equality_in_attribute = false
-ij_html_space_inside_empty_tag = false
-ij_html_text_wrap = normal
-ij_html_uniform_ident = false
-
-[{*.markdown,*.md}]
-ij_markdown_force_one_space_after_blockquote_symbol = true
-ij_markdown_force_one_space_after_header_symbol = true
-ij_markdown_force_one_space_after_list_bullet = true
-ij_markdown_force_one_space_between_words = true
-ij_markdown_keep_indents_on_empty_lines = false
-ij_markdown_max_lines_around_block_elements = 1
-ij_markdown_max_lines_around_header = 1
-ij_markdown_max_lines_between_paragraphs = 1
-ij_markdown_min_lines_around_block_elements = 1
-ij_markdown_min_lines_around_header = 1
-ij_markdown_min_lines_between_paragraphs = 1
-
-[{*.yaml,*.yml}]
-indent_size = 2
-ij_yaml_align_values_properties = do_not_align
-ij_yaml_autoinsert_sequence_marker = true
-ij_yaml_block_mapping_on_new_line = false
-ij_yaml_indent_sequence_value = true
-ij_yaml_keep_indents_on_empty_lines = false
-ij_yaml_keep_line_breaks = true
-ij_yaml_sequence_on_new_line = false
-ij_yaml_space_before_colon = false
-ij_yaml_spaces_within_braces = true
-ij_yaml_spaces_within_brackets = true
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000000..a5a4eb22fc
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000..79ee123c2b
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a5dc3a1d0f..131046cf07 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -273,12 +273,13 @@ tasks {
doLast {
val langs = getListOfSupportedLocales()
- val localesConfig = """
+ val localesConfig =
+ """
-${langs.joinToString("\n") { " " }}
+ ${langs.joinToString(" ") { "" }}
- """.trimIndent()
+ """.trimIndent()
localesConfigFile.bufferedWriter().use { writer ->
writer.write(localesConfig)
@@ -290,9 +291,8 @@ ${langs.joinToString("\n") { " "
class RoomSchemaArgProvider(
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
- val schemaDir: File
+ val schemaDir: File,
) : CommandLineArgumentProvider {
-
override fun asArguments(): Iterable {
// Note: If you're using KSP, change the line below to return
return listOf("room.schemaLocation=${schemaDir.path}")
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrityTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrityTest.kt
index fe0ee89b4f..b36d71b457 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrityTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrityTest.kt
@@ -1,7 +1,7 @@
package com.nononsenseapps.feeder.crypto
-import kotlin.test.assertEquals
import org.junit.Test
+import kotlin.test.assertEquals
class AesCbcWithIntegrityTest {
@Test
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/legacy/LegacyDatabaseHandler.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/legacy/LegacyDatabaseHandler.kt
index 896c41e712..4deb124b83 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/legacy/LegacyDatabaseHandler.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/legacy/LegacyDatabaseHandler.kt
@@ -11,13 +11,16 @@ const val LEGACY_DATABASE_NAME = DATABASE_NAME
class LegacyDatabaseHandler constructor(
context: Context,
name: String = LEGACY_DATABASE_NAME,
- version: Int = LEGACY_DATABASE_VERSION
+ version: Int = LEGACY_DATABASE_VERSION,
) : SQLiteOpenHelper(context, name, null, version) {
-
override fun onCreate(db: SQLiteDatabase) {
}
- override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ override fun onUpgrade(
+ db: SQLiteDatabase,
+ oldVersion: Int,
+ newVersion: Int,
+ ) {
}
override fun onOpen(db: SQLiteDatabase) {
@@ -46,6 +49,7 @@ const val FEED_ITEM_TABLE_NAME = "FeedItem"
// Naming the id column with an underscore is good to be consistent
// with other Android things. This is ALWAYS needed
const val COL_ID = "_id"
+
// These fields can be anything you want.
const val COL_TITLE = "title"
const val COL_CUSTOM_TITLE = "customtitle"
@@ -63,6 +67,7 @@ const val COL_AUTHOR = "author"
const val COL_PUBDATE = "pubdate"
const val COL_UNREAD = "unread"
const val COL_NOTIFIED = "notified"
+
// These fields corresponds to columns in Feed table
const val COL_FEED = "feed"
const val COL_FEEDTITLE = "feedtitle"
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom10To11.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom10To11.kt
index 58b8ce13a7..8fa85b1ded 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom10To11.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom10To11.kt
@@ -17,12 +17,13 @@ class MigrationFrom10To11 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate10to11() {
@@ -31,15 +32,15 @@ class MigrationFrom10To11 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom11To12.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom11To12.kt
index e1f2438444..c07f6ee6bb 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom11To12.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom11To12.kt
@@ -17,12 +17,13 @@ class MigrationFrom11To12 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate11to12() {
@@ -31,15 +32,15 @@ class MigrationFrom11To12 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom12To13.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom12To13.kt
index bb147ed8b9..98f1a5f021 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom12To13.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom12To13.kt
@@ -17,12 +17,13 @@ class MigrationFrom12To13 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate12to13() {
@@ -31,15 +32,15 @@ class MigrationFrom12To13 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom13To14.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom13To14.kt
index 626e3c385d..9335fd5778 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom13To14.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom13To14.kt
@@ -17,12 +17,13 @@ class MigrationFrom13To14 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate13to14() {
@@ -31,15 +32,15 @@ class MigrationFrom13To14 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom14To15.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom14To15.kt
index a9891148b9..ae2d290ce3 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom14To15.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom14To15.kt
@@ -17,12 +17,13 @@ class MigrationFrom14To15 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate14to15() {
@@ -31,15 +32,15 @@ class MigrationFrom14To15 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default, open_articles_with)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0, '')
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default, open_articles_with)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0, '')
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom15To16.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom15To16.kt
index c6158872b3..6a631553f2 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom15To16.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom15To16.kt
@@ -17,12 +17,13 @@ class MigrationFrom15To16 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate15to16() {
@@ -31,15 +32,15 @@ class MigrationFrom15To16 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default, open_articles_with, alternate_id)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0, '', 0)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash, fulltext_by_default, open_articles_with, alternate_id)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666, 0, '', 0)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom16To17.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom16To17.kt
index ab52cb4bdb..2ffe086712 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom16To17.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom16To17.kt
@@ -16,12 +16,13 @@ class MigrationFrom16To17 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate15to16() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom17To18.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom17To18.kt
index 58083eeb50..0ca4b4a853 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom17To18.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom17To18.kt
@@ -16,12 +16,13 @@ class MigrationFrom17To18 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate15to16() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom18To19.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom18To19.kt
index 748dd3d2d5..f5557ceb66 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom18To19.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom18To19.kt
@@ -16,12 +16,13 @@ class MigrationFrom18To19 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate15to16() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom19To20.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom19To20.kt
index ef1dbd6968..6e645262f1 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom19To20.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom19To20.kt
@@ -17,12 +17,13 @@ class MigrationFrom19To20 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom20To21.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom20To21.kt
index 371f88b633..2640b6db67 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom20To21.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom20To21.kt
@@ -5,11 +5,11 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -18,12 +18,13 @@ class MigrationFrom20To21 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom21To22.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom21To22.kt
index f6f0cc9784..073cf5be24 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom21To22.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom21To22.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -17,12 +17,13 @@ class MigrationFrom21To22 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -35,8 +36,8 @@ class MigrationFrom21To22 {
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom22To23.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom22To23.kt
index 85081c538b..39e7998a7f 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom22To23.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom22To23.kt
@@ -5,10 +5,10 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -17,12 +17,13 @@ class MigrationFrom22To23 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -35,8 +36,8 @@ class MigrationFrom22To23 {
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom7To8.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom7To8.kt
index 7fb7b7864b..732a67c4b1 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom7To8.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom7To8.kt
@@ -17,12 +17,13 @@ class MigrationFrom7To8 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate7to8() {
@@ -31,8 +32,8 @@ class MigrationFrom7To8 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(title, url, custom_title, tag, notify)
- VALUES('feed', 'http://url', '', '', 0)
+ INSERT INTO feeds(title, url, custom_title, tag, notify)
+ VALUES('feed', 'http://url', '', '', 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom8To9.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom8To9.kt
index fa9393acda..b655e4f78e 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom8To9.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom8To9.kt
@@ -17,12 +17,13 @@ class MigrationFrom8To9 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate8to9() {
@@ -31,8 +32,8 @@ class MigrationFrom8To9 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(title, url, custom_title, tag, notify, last_sync)
- VALUES('feed', 'http://url', '', '', 0, 0)
+ INSERT INTO feeds(title, url, custom_title, tag, notify, last_sync)
+ VALUES('feed', 'http://url', '', '', 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom9To10.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom9To10.kt
index 778e3d82c8..3a923d2d3c 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom9To10.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFrom9To10.kt
@@ -20,12 +20,13 @@ class MigrationFrom9To10 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate9to10() {
@@ -34,15 +35,15 @@ class MigrationFrom9To10 {
db.use {
db.execSQL(
"""
- INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
- VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
+ INSERT INTO feeds(id, title, url, custom_title, tag, notify, last_sync, response_hash)
+ VALUES(1, 'feed', 'http://url', '', '', 0, 0, 666)
""".trimIndent(),
)
db.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, description)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, '$bigBody')
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, description)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, '$bigBody')
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy5ToLatest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy5ToLatest.kt
index dfac2d9a44..d0ec33ea03 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy5ToLatest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy5ToLatest.kt
@@ -38,10 +38,6 @@ import com.nononsenseapps.feeder.util.contentValues
import com.nononsenseapps.feeder.util.setInt
import com.nononsenseapps.feeder.util.setLong
import com.nononsenseapps.feeder.util.setString
-import java.net.URL
-import java.time.Instant
-import java.time.ZoneOffset
-import java.time.ZonedDateTime
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
@@ -54,31 +50,36 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.android.closestDI
+import java.net.URL
+import java.time.Instant
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
@RunWith(AndroidJUnit4::class)
@LargeTest
class MigrationFromLegacy5ToLatest {
-
private val feederApplication: FeederApplication = getApplicationContext()
private val di: DI by closestDI(feederApplication)
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
private val testDbName = "TestingDatabase"
private val legacyDb: LegacyDatabaseHandler
- get() = LegacyDatabaseHandler(
- context = feederApplication,
- name = testDbName,
- version = 5,
- )
+ get() =
+ LegacyDatabaseHandler(
+ context = feederApplication,
+ name = testDbName,
+ version = 5,
+ )
private val roomDb: AppDatabase
get() =
@@ -121,29 +122,31 @@ class MigrationFromLegacy5ToLatest {
db.execSQL(CREATE_TAGS_VIEW)
// Bare minimum non-null feeds
- val idA = db.insert(
- FEED_TABLE_NAME,
- null,
- contentValues {
- setString(COL_TITLE to "feedA")
- setString(COL_CUSTOM_TITLE to "feedACustom")
- setString(COL_URL to "https://feedA")
- setString(COL_TAG to "")
- },
- )
+ val idA =
+ db.insert(
+ FEED_TABLE_NAME,
+ null,
+ contentValues {
+ setString(COL_TITLE to "feedA")
+ setString(COL_CUSTOM_TITLE to "feedACustom")
+ setString(COL_URL to "https://feedA")
+ setString(COL_TAG to "")
+ },
+ )
// All fields filled
- val idB = db.insert(
- FEED_TABLE_NAME,
- null,
- contentValues {
- setString(COL_TITLE to "feedB")
- setString(COL_CUSTOM_TITLE to "feedBCustom")
- setString(COL_URL to "https://feedB")
- setString(COL_TAG to "tag")
- setInt(COL_NOTIFY to 1)
- },
- )
+ val idB =
+ db.insert(
+ FEED_TABLE_NAME,
+ null,
+ contentValues {
+ setString(COL_TITLE to "feedB")
+ setString(COL_CUSTOM_TITLE to "feedBCustom")
+ setString(COL_URL to "https://feedB")
+ setString(COL_TAG to "tag")
+ setInt(COL_NOTIFY to 1)
+ },
+ )
IntRange(0, 1).forEach { index ->
db.insert(
@@ -194,128 +197,132 @@ class MigrationFromLegacy5ToLatest {
}
@Test
- fun legacyMigrationTo7MinimalFeed() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_5_7,
- MIGRATION_7_8,
- )
+ fun legacyMigrationTo7MinimalFeed() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_5_7,
+ MIGRATION_7_8,
+ )
- roomDb.let { db ->
- val feeds = db.feedDao().loadFeeds()
+ roomDb.let { db ->
+ val feeds = db.feedDao().loadFeeds()
- assertEquals("Wrong number of feeds", 2, feeds.size)
+ assertEquals("Wrong number of feeds", 2, feeds.size)
- val feedA = feeds[0]
+ val feedA = feeds[0]
- assertEquals("feedA", feedA.title)
- assertEquals("feedACustom", feedA.customTitle)
- assertEquals(URL("https://feedA"), feedA.url)
- assertEquals("", feedA.tag)
- assertEquals(Instant.EPOCH, feedA.lastSync)
- assertFalse(feedA.notify)
- assertNull(feedA.imageUrl)
+ assertEquals("feedA", feedA.title)
+ assertEquals("feedACustom", feedA.customTitle)
+ assertEquals(URL("https://feedA"), feedA.url)
+ assertEquals("", feedA.tag)
+ assertEquals(Instant.EPOCH, feedA.lastSync)
+ assertFalse(feedA.notify)
+ assertNull(feedA.imageUrl)
+ }
}
- }
@Test
- fun legacyMigrationTo7CompleteFeed() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_5_7,
- MIGRATION_7_8,
- )
+ fun legacyMigrationTo7CompleteFeed() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_5_7,
+ MIGRATION_7_8,
+ )
- roomDb.let { db ->
- val feeds = db.feedDao().loadFeeds()
+ roomDb.let { db ->
+ val feeds = db.feedDao().loadFeeds()
- assertEquals("Wrong number of feeds", 2, feeds.size)
+ assertEquals("Wrong number of feeds", 2, feeds.size)
- val feedB = feeds[1]
+ val feedB = feeds[1]
- assertEquals("feedB", feedB.title)
- assertEquals("feedBCustom", feedB.customTitle)
- assertEquals(URL("https://feedB"), feedB.url)
- assertEquals("tag", feedB.tag)
- assertEquals(Instant.EPOCH, feedB.lastSync)
- assertTrue(feedB.notify)
- assertNull(feedB.imageUrl)
+ assertEquals("feedB", feedB.title)
+ assertEquals("feedBCustom", feedB.customTitle)
+ assertEquals(URL("https://feedB"), feedB.url)
+ assertEquals("tag", feedB.tag)
+ assertEquals(Instant.EPOCH, feedB.lastSync)
+ assertTrue(feedB.notify)
+ assertNull(feedB.imageUrl)
+ }
}
- }
@Test
- fun legacyMigrationTo7MinimalFeedItem() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_5_7,
- MIGRATION_7_8,
- )
+ fun legacyMigrationTo7MinimalFeedItem() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_5_7,
+ MIGRATION_7_8,
+ )
- roomDb.let { db ->
- val feed = db.feedDao().loadFeeds()[0]
- assertEquals("feedA", feed.title)
- @Suppress("DEPRECATION")
- val items =
- db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
+ roomDb.let { db ->
+ val feed = db.feedDao().loadFeeds()[0]
+ assertEquals("feedA", feed.title)
+ @Suppress("DEPRECATION")
+ val items =
+ db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
- assertEquals(2, items.size)
+ assertEquals(2, items.size)
- items.forEachIndexed { index, it ->
- assertEquals(feed.id, it.feedId)
- assertEquals("guid$index", it.guid)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("snippet$index", it.plainSnippet)
- assertTrue(it.unread)
- assertNull(it.author)
- assertNull(it.enclosureLink)
- assertNull(it.imageUrl)
- assertNull(it.pubDate)
- assertNull(it.link)
- assertFalse(it.notified)
+ items.forEachIndexed { index, it ->
+ assertEquals(feed.id, it.feedId)
+ assertEquals("guid$index", it.guid)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("snippet$index", it.plainSnippet)
+ assertTrue(it.unread)
+ assertNull(it.author)
+ assertNull(it.enclosureLink)
+ assertNull(it.imageUrl)
+ assertNull(it.pubDate)
+ assertNull(it.link)
+ assertFalse(it.notified)
+ }
}
}
- }
@Test
- fun legacyMigrationTo7CompleteFeedItem() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_5_7,
- MIGRATION_7_8,
- )
+ fun legacyMigrationTo7CompleteFeedItem() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_5_7,
+ MIGRATION_7_8,
+ )
- roomDb.let { db ->
- val feed = db.feedDao().loadFeeds()[1]
- assertEquals("feedB", feed.title)
- @Suppress("DEPRECATION")
- val items =
- db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
+ roomDb.let { db ->
+ val feed = db.feedDao().loadFeeds()[1]
+ assertEquals("feedB", feed.title)
+ @Suppress("DEPRECATION")
+ val items =
+ db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
- assertEquals(2, items.size)
+ assertEquals(2, items.size)
- items.forEachIndexed { index, it ->
- assertEquals(feed.id, it.feedId)
- assertEquals("guid$index", it.guid)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("snippet$index", it.plainSnippet)
- assertFalse(it.unread)
- assertEquals("author$index", it.author)
- assertEquals("https://enclosure$index", it.enclosureLink)
- assertEquals("https://image$index", it.imageUrl)
- assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
- assertEquals("https://link$index", it.link)
- assertTrue(it.notified)
+ items.forEachIndexed { index, it ->
+ assertEquals(feed.id, it.feedId)
+ assertEquals("guid$index", it.guid)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("snippet$index", it.plainSnippet)
+ assertFalse(it.unread)
+ assertEquals("author$index", it.author)
+ assertEquals("https://enclosure$index", it.enclosureLink)
+ assertEquals("https://image$index", it.imageUrl)
+ assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
+ assertEquals("https://link$index", it.link)
+ assertTrue(it.notified)
+ }
}
}
- }
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy6ToLatest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy6ToLatest.kt
index 72105c2197..e1bdb9ee95 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy6ToLatest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy6ToLatest.kt
@@ -37,10 +37,6 @@ import com.nononsenseapps.feeder.util.contentValues
import com.nononsenseapps.feeder.util.setInt
import com.nononsenseapps.feeder.util.setLong
import com.nononsenseapps.feeder.util.setString
-import java.net.URL
-import java.time.Instant
-import java.time.ZoneOffset
-import java.time.ZonedDateTime
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
@@ -53,31 +49,36 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.android.closestDI
+import java.net.URL
+import java.time.Instant
+import java.time.ZoneOffset
+import java.time.ZonedDateTime
@RunWith(AndroidJUnit4::class)
@LargeTest
class MigrationFromLegacy6ToLatest {
-
private val feederApplication: FeederApplication = getApplicationContext()
private val di: DI by closestDI(feederApplication)
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
private val testDbName = "TestingDatabase"
private val legacyDb: LegacyDatabaseHandler
- get() = LegacyDatabaseHandler(
- context = feederApplication,
- name = testDbName,
- version = 6,
- )
+ get() =
+ LegacyDatabaseHandler(
+ context = feederApplication,
+ name = testDbName,
+ version = 6,
+ )
private val roomDb: AppDatabase
get() =
@@ -97,30 +98,32 @@ class MigrationFromLegacy6ToLatest {
createViewsAndTriggers(db)
// Bare minimum non-null feeds
- val idA = db.insert(
- FEED_TABLE_NAME,
- null,
- contentValues {
- setString(COL_TITLE to "feedA")
- setString(COL_CUSTOM_TITLE to "feedACustom")
- setString(COL_URL to "https://feedA")
- setString(COL_TAG to "")
- },
- )
+ val idA =
+ db.insert(
+ FEED_TABLE_NAME,
+ null,
+ contentValues {
+ setString(COL_TITLE to "feedA")
+ setString(COL_CUSTOM_TITLE to "feedACustom")
+ setString(COL_URL to "https://feedA")
+ setString(COL_TAG to "")
+ },
+ )
// All fields filled
- val idB = db.insert(
- FEED_TABLE_NAME,
- null,
- contentValues {
- setString(COL_TITLE to "feedB")
- setString(COL_CUSTOM_TITLE to "feedBCustom")
- setString(COL_URL to "https://feedB")
- setString(COL_TAG to "tag")
- setString(COL_IMAGEURL to "https://image")
- setInt(COL_NOTIFY to 1)
- },
- )
+ val idB =
+ db.insert(
+ FEED_TABLE_NAME,
+ null,
+ contentValues {
+ setString(COL_TITLE to "feedB")
+ setString(COL_CUSTOM_TITLE to "feedBCustom")
+ setString(COL_URL to "https://feedB")
+ setString(COL_TAG to "tag")
+ setString(COL_IMAGEURL to "https://image")
+ setInt(COL_NOTIFY to 1)
+ },
+ )
IntRange(0, 1).forEach { index ->
db.insert(
@@ -171,122 +174,126 @@ class MigrationFromLegacy6ToLatest {
}
@Test
- fun legacyMigrationTo7MinimalFeed() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_6_7,
- )
+ fun legacyMigrationTo7MinimalFeed() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_6_7,
+ )
- roomDb.let { db ->
- val feeds = db.feedDao().loadFeeds()
+ roomDb.let { db ->
+ val feeds = db.feedDao().loadFeeds()
- assertEquals("Wrong number of feeds", 2, feeds.size)
+ assertEquals("Wrong number of feeds", 2, feeds.size)
- val feedA = feeds[0]
+ val feedA = feeds[0]
- assertEquals("feedA", feedA.title)
- assertEquals("feedACustom", feedA.customTitle)
- assertEquals(URL("https://feedA"), feedA.url)
- assertEquals("", feedA.tag)
- assertEquals(Instant.EPOCH, feedA.lastSync)
- assertFalse(feedA.notify)
- assertNull(feedA.imageUrl)
+ assertEquals("feedA", feedA.title)
+ assertEquals("feedACustom", feedA.customTitle)
+ assertEquals(URL("https://feedA"), feedA.url)
+ assertEquals("", feedA.tag)
+ assertEquals(Instant.EPOCH, feedA.lastSync)
+ assertFalse(feedA.notify)
+ assertNull(feedA.imageUrl)
+ }
}
- }
@Test
- fun legacyMigrationTo7CompleteFeed() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_6_7,
- )
+ fun legacyMigrationTo7CompleteFeed() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_6_7,
+ )
- roomDb.let { db ->
- val feeds = db.feedDao().loadFeeds()
+ roomDb.let { db ->
+ val feeds = db.feedDao().loadFeeds()
- assertEquals("Wrong number of feeds", 2, feeds.size)
+ assertEquals("Wrong number of feeds", 2, feeds.size)
- val feedB = feeds[1]
+ val feedB = feeds[1]
- assertEquals("feedB", feedB.title)
- assertEquals("feedBCustom", feedB.customTitle)
- assertEquals(URL("https://feedB"), feedB.url)
- assertEquals("tag", feedB.tag)
- assertEquals(Instant.EPOCH, feedB.lastSync)
- assertTrue(feedB.notify)
- assertEquals(URL("https://image"), feedB.imageUrl)
+ assertEquals("feedB", feedB.title)
+ assertEquals("feedBCustom", feedB.customTitle)
+ assertEquals(URL("https://feedB"), feedB.url)
+ assertEquals("tag", feedB.tag)
+ assertEquals(Instant.EPOCH, feedB.lastSync)
+ assertTrue(feedB.notify)
+ assertEquals(URL("https://image"), feedB.imageUrl)
+ }
}
- }
@Test
- fun legacyMigrationTo7MinimalFeedItem() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_6_7,
- )
+ fun legacyMigrationTo7MinimalFeedItem() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_6_7,
+ )
- roomDb.let { db ->
- val feed = db.feedDao().loadFeeds()[0]
- assertEquals("feedA", feed.title)
- @Suppress("DEPRECATION")
- val items = db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
+ roomDb.let { db ->
+ val feed = db.feedDao().loadFeeds()[0]
+ assertEquals("feedA", feed.title)
+ @Suppress("DEPRECATION")
+ val items = db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
- assertEquals(2, items.size)
+ assertEquals(2, items.size)
- items.forEachIndexed { index, it ->
- assertEquals(feed.id, it.feedId)
- assertEquals("guid$index", it.guid)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("snippet$index", it.plainSnippet)
- assertTrue(it.unread)
- assertNull(it.author)
- assertNull(it.enclosureLink)
- assertNull(it.imageUrl)
- assertNull(it.pubDate)
- assertNull(it.link)
- assertFalse(it.notified)
+ items.forEachIndexed { index, it ->
+ assertEquals(feed.id, it.feedId)
+ assertEquals("guid$index", it.guid)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("snippet$index", it.plainSnippet)
+ assertTrue(it.unread)
+ assertNull(it.author)
+ assertNull(it.enclosureLink)
+ assertNull(it.imageUrl)
+ assertNull(it.pubDate)
+ assertNull(it.link)
+ assertFalse(it.notified)
+ }
}
}
- }
@Test
- fun legacyMigrationTo7CompleteFeedItem() = runBlocking {
- testHelper.runMigrationsAndValidate(
- testDbName,
- 7,
- true,
- MIGRATION_6_7,
- )
+ fun legacyMigrationTo7CompleteFeedItem() =
+ runBlocking {
+ testHelper.runMigrationsAndValidate(
+ testDbName,
+ 7,
+ true,
+ MIGRATION_6_7,
+ )
- roomDb.let { db ->
- val feed = db.feedDao().loadFeeds()[1]
- assertEquals("feedB", feed.title)
- @Suppress("DEPRECATION")
- val items = db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
+ roomDb.let { db ->
+ val feed = db.feedDao().loadFeeds()[1]
+ assertEquals("feedB", feed.title)
+ @Suppress("DEPRECATION")
+ val items = db.feedItemDao().loadFeedItemsInFeedDesc(feedId = feed.id)
- assertEquals(2, items.size)
+ assertEquals(2, items.size)
- items.forEachIndexed { index, it ->
- assertEquals(feed.id, it.feedId)
- assertEquals("guid$index", it.guid)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("plain$index", it.plainTitle)
- assertEquals("snippet$index", it.plainSnippet)
- assertFalse(it.unread)
- assertEquals("author$index", it.author)
- assertEquals("https://enclosure$index", it.enclosureLink)
- assertEquals("https://image$index", it.imageUrl)
- assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
- assertEquals("https://link$index", it.link)
- assertTrue(it.notified)
+ items.forEachIndexed { index, it ->
+ assertEquals(feed.id, it.feedId)
+ assertEquals("guid$index", it.guid)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("plain$index", it.plainTitle)
+ assertEquals("snippet$index", it.plainSnippet)
+ assertFalse(it.unread)
+ assertEquals("author$index", it.author)
+ assertEquals("https://enclosure$index", it.enclosureLink)
+ assertEquals("https://image$index", it.imageUrl)
+ assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
+ assertEquals("https://link$index", it.link)
+ assertTrue(it.notified)
+ }
}
}
- }
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom23To24.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom23To24.kt
index 60b59f8b70..9afad8acfb 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom23To24.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom23To24.kt
@@ -8,13 +8,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.android.closestDI
import org.kodein.di.instance
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -26,12 +26,13 @@ class TestMigrationFrom23To24 {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -77,8 +78,9 @@ class TestMigrationFrom23To24 {
}
}
- val blocks = sharedPrefs.getStringSet("pref_block_list_values", null)
- ?: emptySet()
+ val blocks =
+ sharedPrefs.getStringSet("pref_block_list_values", null)
+ ?: emptySet()
assertTrue {
blocks.isEmpty()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom24To25.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom24To25.kt
index 91a0c7fa5b..397f78289a 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom24To25.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom24To25.kt
@@ -7,15 +7,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -26,12 +26,13 @@ class TestMigrationFrom24To25 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -44,8 +45,8 @@ class TestMigrationFrom24To25 : DIAware {
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom25To26.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom25To26.kt
index 527c6eb543..5bf7764dbf 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom25To26.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom25To26.kt
@@ -7,13 +7,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -24,12 +24,13 @@ class TestMigrationFrom25To26 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -42,8 +43,8 @@ class TestMigrationFrom25To26 : DIAware {
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
- VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
+ VALUES(8, 'http://item', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom26To27.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom26To27.kt
index f152c07207..c22b1ada48 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom26To27.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom26To27.kt
@@ -7,14 +7,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -25,12 +25,13 @@ class TestMigrationFrom26To27 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -43,14 +44,14 @@ class TestMigrationFrom26To27 : DIAware {
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
- VALUES(8, 'http://item1', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
+ VALUES(8, 'http://item1', 'title', 'ptitle', 'psnippet', 1, 0, 1, 0, 0, 1, 0, 0)
""".trimIndent(),
)
oldDB.execSQL(
"""
- INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
- VALUES(9, 'http://item2', 'title', 'ptitle', 'psnippet', 0, 0, 1, 0, 0, 1, 0, 0)
+ INSERT INTO feed_items(id, guid, title, plain_title, plain_snippet, unread, notified, feed_id, first_synced_time, primary_sort_time, pinned, bookmarked, fulltext_downloaded)
+ VALUES(9, 'http://item2', 'title', 'ptitle', 'psnippet', 0, 0, 1, 0, 0, 1, 0, 0)
""".trimIndent(),
)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom27To28.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom27To28.kt
index 21f4d281ee..741f502ed7 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom27To28.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom27To28.kt
@@ -7,13 +7,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -24,12 +24,13 @@ class TestMigrationFrom27To28 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -42,12 +43,13 @@ class TestMigrationFrom27To28 : DIAware {
""".trimIndent(),
)
}
- val db = testHelper.runMigrationsAndValidate(
- dbName,
- TO_VERSION,
- true,
- MigrationFrom27To28(di),
- )
+ val db =
+ testHelper.runMigrationsAndValidate(
+ dbName,
+ TO_VERSION,
+ true,
+ MigrationFrom27To28(di),
+ )
db.query(
"""
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom28To29.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom28To29.kt
index 900e545757..48ae6f11e8 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom28To29.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom28To29.kt
@@ -8,13 +8,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertNull
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -25,12 +25,13 @@ class TestMigrationFrom28To29 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -49,12 +50,13 @@ class TestMigrationFrom28To29 : DIAware {
""".trimIndent(),
)
}
- val db = testHelper.runMigrationsAndValidate(
- dbName,
- TO_VERSION,
- true,
- MigrationFrom28To29(di),
- )
+ val db =
+ testHelper.runMigrationsAndValidate(
+ dbName,
+ TO_VERSION,
+ true,
+ MigrationFrom28To29(di),
+ )
db.query(
"""
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom29To30.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom29To30.kt
index 46c7581200..169103bdb3 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom29To30.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom29To30.kt
@@ -7,13 +7,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -24,12 +24,13 @@ class TestMigrationFrom29To30 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -48,12 +49,13 @@ class TestMigrationFrom29To30 : DIAware {
""".trimIndent(),
)
}
- val db = testHelper.runMigrationsAndValidate(
- dbName,
- TO_VERSION,
- true,
- MigrationFrom29To30(di),
- )
+ val db =
+ testHelper.runMigrationsAndValidate(
+ dbName,
+ TO_VERSION,
+ true,
+ MigrationFrom29To30(di),
+ )
db.query(
"""
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom30To31.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom30To31.kt
index ff8b5584a6..7e22c8a9e1 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom30To31.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/db/room/TestMigrationFrom30To31.kt
@@ -7,13 +7,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.nononsenseapps.feeder.FeederApplication
-import kotlin.test.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@LargeTest
@@ -24,12 +24,13 @@ class TestMigrationFrom30To31 : DIAware {
@Rule
@JvmField
- val testHelper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java,
- emptyList(),
- FrameworkSQLiteOpenHelperFactory(),
- )
+ val testHelper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ AppDatabase::class.java,
+ emptyList(),
+ FrameworkSQLiteOpenHelperFactory(),
+ )
@Test
fun migrate() {
@@ -48,12 +49,13 @@ class TestMigrationFrom30To31 : DIAware {
""".trimIndent(),
)
}
- val db = testHelper.runMigrationsAndValidate(
- dbName,
- TO_VERSION,
- true,
- MigrationFrom30To31(di),
- )
+ val db =
+ testHelper.runMigrationsAndValidate(
+ dbName,
+ TO_VERSION,
+ true,
+ MigrationFrom30To31(di),
+ )
db.query(
"""
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/Feeds.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/Feeds.kt
index 2c543d1ba0..869ba09f76 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/Feeds.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/Feeds.kt
@@ -1,24 +1,25 @@
package com.nononsenseapps.feeder.model
-import java.io.InputStream
import org.intellij.lang.annotations.Language
+import java.io.InputStream
class Feeds {
-
companion object {
val nixosRss: InputStream
get() = Companion::class.java.getResourceAsStream("rss_nixos.xml")!!
val cowboyJson: String
- get() = String(
- Companion::class.java.getResourceAsStream("cowboyprogrammer_feed.json")!!
- .use { it.readBytes() },
- )
+ get() =
+ String(
+ Companion::class.java.getResourceAsStream("cowboyprogrammer_feed.json")!!
+ .use { it.readBytes() },
+ )
val cowboyAtom: String
- get() = String(
- Companion::class.java.getResourceAsStream("cowboyprogrammer_atom.xml")!!.use { it.readBytes() },
- )
+ get() =
+ String(
+ Companion::class.java.getResourceAsStream("cowboyprogrammer_atom.xml")!!.use { it.readBytes() },
+ )
/**
* Reported in https://gitlab.com/spacecowboy/Feeder/-/issues/410
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/FeedsToSyncTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/FeedsToSyncTest.kt
index 0091649f49..1728335f03 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/FeedsToSyncTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/FeedsToSyncTest.kt
@@ -11,8 +11,6 @@ import com.nononsenseapps.feeder.db.room.FeedDao
import com.nononsenseapps.feeder.db.room.ID_UNSET
import com.nononsenseapps.feeder.ui.TestDatabaseRule
import com.nononsenseapps.feeder.util.minusMinutes
-import java.net.URL
-import java.time.Instant
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Rule
@@ -24,6 +22,8 @@ import org.kodein.di.android.subDI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
+import java.net.URL
+import java.time.Instant
@RunWith(AndroidJUnit4::class)
class FeedsToSyncTest : DIAware {
@@ -41,101 +41,115 @@ class FeedsToSyncTest : DIAware {
private val rssLocalSync: RssLocalSync by instance()
@Test
- fun returnsStaleFeed() = runBlocking {
- // with stale feed
- val feed = withFeed()
+ fun returnsStaleFeed() =
+ runBlocking {
+ // with stale feed
+ val feed = withFeed()
- // when
- val result = rssLocalSync.feedsToSync(feedId = feed.id, tag = "")
+ // when
+ val result = rssLocalSync.feedsToSync(feedId = feed.id, tag = "")
- // then
- assertEquals(listOf(feed), result)
- }
+ // then
+ assertEquals(listOf(feed), result)
+ }
@Test
- fun doesNotReturnFreshFeed() = runBlocking {
- val now = Instant.now()
- val feed = withFeed(lastSync = now.minusMinutes(1))
-
- // when
- val result = rssLocalSync.feedsToSync(
- feedId = feed.id,
- tag = "",
- staleTime = now.minusMinutes(2).toEpochMilli(),
- )
-
- // then
- assertEquals(emptyList(), result)
- }
+ fun doesNotReturnFreshFeed() =
+ runBlocking {
+ val now = Instant.now()
+ val feed = withFeed(lastSync = now.minusMinutes(1))
+
+ // when
+ val result =
+ rssLocalSync.feedsToSync(
+ feedId = feed.id,
+ tag = "",
+ staleTime = now.minusMinutes(2).toEpochMilli(),
+ )
+
+ // then
+ assertEquals(emptyList(), result)
+ }
@Test
- fun returnsAllStaleFeeds() = runBlocking {
- val items = listOf(
- withFeed(url = URL("http://one")),
- withFeed(url = URL("http://two")),
- )
+ fun returnsAllStaleFeeds() =
+ runBlocking {
+ val items =
+ listOf(
+ withFeed(url = URL("http://one")),
+ withFeed(url = URL("http://two")),
+ )
- val result = rssLocalSync.feedsToSync(feedId = ID_UNSET, tag = "")
+ val result = rssLocalSync.feedsToSync(feedId = ID_UNSET, tag = "")
- assertEquals(items, result)
- }
+ assertEquals(items, result)
+ }
@Test
- fun doesNotReturnAllFreshFeeds() = runBlocking {
- val now = Instant.now()
- val items = listOf(
- withFeed(url = URL("http://one"), lastSync = now.minusMinutes(1)),
- withFeed(url = URL("http://two"), lastSync = now.minusMinutes(3)),
- )
-
- val result = rssLocalSync.feedsToSync(
- feedId = ID_UNSET,
- tag = "",
- staleTime = now.minusMinutes(2).toEpochMilli(),
- )
-
- assertEquals(listOf(items[1]), result)
- }
+ fun doesNotReturnAllFreshFeeds() =
+ runBlocking {
+ val now = Instant.now()
+ val items =
+ listOf(
+ withFeed(url = URL("http://one"), lastSync = now.minusMinutes(1)),
+ withFeed(url = URL("http://two"), lastSync = now.minusMinutes(3)),
+ )
+
+ val result =
+ rssLocalSync.feedsToSync(
+ feedId = ID_UNSET,
+ tag = "",
+ staleTime = now.minusMinutes(2).toEpochMilli(),
+ )
+
+ assertEquals(listOf(items[1]), result)
+ }
@Test
- fun returnsTaggedStaleFeeds() = runBlocking {
- val items = listOf(
- withFeed(url = URL("http://one"), tag = "tag"),
- withFeed(url = URL("http://two"), tag = "tag"),
- )
+ fun returnsTaggedStaleFeeds() =
+ runBlocking {
+ val items =
+ listOf(
+ withFeed(url = URL("http://one"), tag = "tag"),
+ withFeed(url = URL("http://two"), tag = "tag"),
+ )
- val result = rssLocalSync.feedsToSync(feedId = ID_UNSET, tag = "")
+ val result = rssLocalSync.feedsToSync(feedId = ID_UNSET, tag = "")
- assertEquals(items, result)
- }
+ assertEquals(items, result)
+ }
@Test
- fun doesNotReturnTaggedFreshFeeds() = runBlocking {
- val now = Instant.now()
- val items = listOf(
- withFeed(url = URL("http://one"), lastSync = now.minusMinutes(1), tag = "tag"),
- withFeed(url = URL("http://two"), lastSync = now.minusMinutes(3), tag = "tag"),
- )
-
- val result = rssLocalSync.feedsToSync(
- feedId = ID_UNSET,
- tag = "tag",
- staleTime = now.minusMinutes(2).toEpochMilli(),
- )
-
- assertEquals(listOf(items[1]), result)
- }
+ fun doesNotReturnTaggedFreshFeeds() =
+ runBlocking {
+ val now = Instant.now()
+ val items =
+ listOf(
+ withFeed(url = URL("http://one"), lastSync = now.minusMinutes(1), tag = "tag"),
+ withFeed(url = URL("http://two"), lastSync = now.minusMinutes(3), tag = "tag"),
+ )
+
+ val result =
+ rssLocalSync.feedsToSync(
+ feedId = ID_UNSET,
+ tag = "tag",
+ staleTime = now.minusMinutes(2).toEpochMilli(),
+ )
+
+ assertEquals(listOf(items[1]), result)
+ }
private suspend fun withFeed(
lastSync: Instant = Instant.ofEpochMilli(0),
url: URL = URL("http://url"),
tag: String = "",
): Feed {
- val feed = Feed(
- lastSync = lastSync,
- url = url,
- tag = tag,
- )
+ val feed =
+ Feed(
+ lastSync = lastSync,
+ url = url,
+ tag = tag,
+ )
val id = testDb.db.feedDao().insertFeed(feed)
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssLocalSyncKtTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssLocalSyncKtTest.kt
index 16855fdcac..6f845ac5d5 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssLocalSyncKtTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssLocalSyncKtTest.kt
@@ -19,11 +19,6 @@ import com.nononsenseapps.feeder.ui.TestDatabaseRule
import com.nononsenseapps.feeder.util.FilePathProvider
import com.nononsenseapps.feeder.util.filePathProvider
import com.nononsenseapps.feeder.util.minusMinutes
-import java.net.URL
-import java.time.Instant
-import java.util.concurrent.TimeUnit
-import kotlin.test.Ignore
-import kotlin.test.assertTrue
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
@@ -44,6 +39,11 @@ import org.kodein.di.android.subDI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
+import java.net.URL
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+import kotlin.test.Ignore
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@MediumTest
@@ -58,12 +58,13 @@ class RssLocalSyncKtTest : DIAware {
bind(overrides = true) with singleton { FeedStore(di) }
bind(overrides = true) with singleton { Repository(di) }
bind(overrides = true) with singleton { RssLocalSync(di) }
- bind(overrides = true) with singleton {
- filePathProvider(
- cacheDir = getApplicationContext().cacheDir,
- filesDir = getApplicationContext().filesDir,
- )
- }
+ bind(overrides = true) with
+ singleton {
+ filePathProvider(
+ cacheDir = getApplicationContext().cacheDir,
+ filesDir = getApplicationContext().filesDir,
+ )
+ }
}
val server = MockWebServer()
@@ -89,534 +90,567 @@ class RssLocalSyncKtTest : DIAware {
isJson: Boolean = true,
useAlternateId: Boolean = false,
): Long {
- val id = testDb.db.feedDao().insertFeed(
- Feed(
- title = title,
- url = url,
- tag = "",
- alternateId = useAlternateId,
- ),
- )
-
- server.dispatcher = object : Dispatcher() {
- override fun dispatch(request: RecordedRequest): MockResponse {
- return responses.getOrDefault(
- request.requestUrl?.toUrl(),
- MockResponse().setResponseCode(404),
- )
+ val id =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ title = title,
+ url = url,
+ tag = "",
+ alternateId = useAlternateId,
+ ),
+ )
+
+ server.dispatcher =
+ object : Dispatcher() {
+ override fun dispatch(request: RecordedRequest): MockResponse {
+ return responses.getOrDefault(
+ request.requestUrl?.toUrl(),
+ MockResponse().setResponseCode(404),
+ )
+ }
}
- }
- responses[url] = MockResponse().apply {
- setResponseCode(200)
- if (isJson) {
- setHeader("Content-Type", "application/json")
+ responses[url] =
+ MockResponse().apply {
+ setResponseCode(200)
+ if (isJson) {
+ setHeader("Content-Type", "application/json")
+ }
+ setBody(raw)
}
- setBody(raw)
- }
return id
}
@Test
- fun syncCowboyJsonWorks() = runBlocking {
- val cowboyJsonId = insertFeed(
- "cowboyjson",
- server.url("/feed.json").toUrl(),
- cowboyJson,
- )
-
+ fun syncCowboyJsonWorks() =
runBlocking {
- rssLocalSync.syncFeeds(
- feedId = cowboyJsonId,
+ val cowboyJsonId =
+ insertFeed(
+ "cowboyjson",
+ server.url("/feed.json").toUrl(),
+ cowboyJson,
+ )
+
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = cowboyJsonId,
+ )
+ }
+
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Unexpected number of items in feed",
+ 10,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyJsonId).size,
)
}
- @Suppress("DEPRECATION")
- assertEquals(
- "Unexpected number of items in feed",
- 10,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyJsonId).size,
- )
- }
-
@Test
- fun syncCowboyAtomWorks() = runBlocking {
- val cowboyAtomId = insertFeed(
- "cowboyatom",
- server.url("/atom.xml").toUrl(),
- cowboyAtom,
- isJson = false,
- )
-
+ fun syncCowboyAtomWorks() =
runBlocking {
- rssLocalSync.syncFeeds(
- feedId = cowboyAtomId,
+ val cowboyAtomId =
+ insertFeed(
+ "cowboyatom",
+ server.url("/atom.xml").toUrl(),
+ cowboyAtom,
+ isJson = false,
+ )
+
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = cowboyAtomId,
+ )
+ }
+
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Unexpected number of items in feed",
+ 15,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
)
}
- @Suppress("DEPRECATION")
- assertEquals(
- "Unexpected number of items in feed",
- 15,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
- )
- }
-
@Test
- fun alternateIdMitigatesRssFeedsWithNonUniqueGuids() = runBlocking {
- val duplicateIdRss = insertFeed(
- "aussieWeather",
- server.url("/IDZ00059.warnings_vic.xml").toUrl(),
- rssWithDuplicateGuids,
- isJson = false,
- useAlternateId = true,
- )
-
+ fun alternateIdMitigatesRssFeedsWithNonUniqueGuids() =
runBlocking {
- rssLocalSync.syncFeeds(
- feedId = duplicateIdRss,
+ val duplicateIdRss =
+ insertFeed(
+ "aussieWeather",
+ server.url("/IDZ00059.warnings_vic.xml").toUrl(),
+ rssWithDuplicateGuids,
+ isJson = false,
+ useAlternateId = true,
+ )
+
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = duplicateIdRss,
+ )
+ }
+
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Expected duplicate guids to be mitigated by alternate id",
+ 13,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(duplicateIdRss).size,
)
}
- @Suppress("DEPRECATION")
- assertEquals(
- "Expected duplicate guids to be mitigated by alternate id",
- 13,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(duplicateIdRss).size,
- )
- }
-
@Test
- fun syncAllWorks() = runBlocking {
- val cowboyJsonId = insertFeed(
- "cowboyjson",
- server.url("/feed.json").toUrl(),
- cowboyJson,
- )
- val cowboyAtomId = insertFeed(
- "cowboyatom",
- server.url("/atom.xml").toUrl(),
- cowboyAtom,
- isJson = false,
- )
-
+ fun syncAllWorks() =
runBlocking {
- rssLocalSync.syncFeeds(
- feedId = ID_UNSET,
+ val cowboyJsonId =
+ insertFeed(
+ "cowboyjson",
+ server.url("/feed.json").toUrl(),
+ cowboyJson,
+ )
+ val cowboyAtomId =
+ insertFeed(
+ "cowboyatom",
+ server.url("/atom.xml").toUrl(),
+ cowboyAtom,
+ isJson = false,
+ )
+
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = ID_UNSET,
+ )
+ }
+
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Unexpected number of items in feed",
+ 10,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyJsonId).size,
)
- }
- @Suppress("DEPRECATION")
- assertEquals(
- "Unexpected number of items in feed",
- 10,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyJsonId).size,
- )
-
- @Suppress("DEPRECATION")
- assertEquals(
- "Unexpected number of items in feed",
- 15,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
- )
- }
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Unexpected number of items in feed",
+ 15,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
+ )
+ }
@Test
- fun responsesAreNotParsedUnlessFeedHashHasChanged() = runBlocking {
- val cowboyJsonId = insertFeed(
- "cowboyjson",
- server.url("/feed.json").toUrl(),
- cowboyJson,
- )
-
+ fun responsesAreNotParsedUnlessFeedHashHasChanged() =
runBlocking {
- rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
- assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
- assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
- // "Long time" ago, but not unset
- testDb.db.feedDao().updateFeed(feed.copy(lastSync = Instant.ofEpochMilli(999L)))
+ val cowboyJsonId =
+ insertFeed(
+ "cowboyjson",
+ server.url("/feed.json").toUrl(),
+ cowboyJson,
+ )
+
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
+ assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
+ assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
+ // "Long time" ago, but not unset
+ testDb.db.feedDao().updateFeed(feed.copy(lastSync = Instant.ofEpochMilli(999L)))
+ }
+ rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
}
- rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
- }
- assertEquals("Feed should have been fetched twice", 2, server.requestCount)
+ assertEquals("Feed should have been fetched twice", 2, server.requestCount)
- assertNotEquals(
- "Cached response should still have updated feed last sync",
- 999L,
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync.toEpochMilli(),
- )
- }
+ assertNotEquals(
+ "Cached response should still have updated feed last sync",
+ 999L,
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync.toEpochMilli(),
+ )
+ }
@Test
- fun feedsSyncedWithin15MinAreIgnored() = runBlocking {
- val cowboyJsonId = insertFeed(
- "cowboyjson",
- server.url("/feed.json").toUrl(),
- cowboyJson,
- )
-
- val fourteenMinsAgo = Instant.now().minusMinutes(14)
+ fun feedsSyncedWithin15MinAreIgnored() =
runBlocking {
- rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
- assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
- assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
+ val cowboyJsonId =
+ insertFeed(
+ "cowboyjson",
+ server.url("/feed.json").toUrl(),
+ cowboyJson,
+ )
- testDb.db.feedDao().updateFeed(feed.copy(lastSync = fourteenMinsAgo))
+ val fourteenMinsAgo = Instant.now().minusMinutes(14)
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
+ assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
+ assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
+
+ testDb.db.feedDao().updateFeed(feed.copy(lastSync = fourteenMinsAgo))
+ }
+ rssLocalSync.syncFeeds(
+ feedId = cowboyJsonId,
+ forceNetwork = false,
+ minFeedAgeMinutes = 15,
+ )
}
- rssLocalSync.syncFeeds(
- feedId = cowboyJsonId,
- forceNetwork = false,
- minFeedAgeMinutes = 15,
+
+ assertEquals(
+ "Recently synced feed should not get a second network request",
+ 1,
+ server.requestCount,
)
- }
- assertEquals(
- "Recently synced feed should not get a second network request",
- 1,
- server.requestCount,
- )
-
- assertEquals(
- "Last sync should not have changed",
- fourteenMinsAgo,
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync,
- )
- }
+ assertEquals(
+ "Last sync should not have changed",
+ fourteenMinsAgo,
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync,
+ )
+ }
@Test
- fun feedsSyncedWithin15MinAreNotIgnoredWhenForcingNetwork() = runBlocking {
- val cowboyJsonId = insertFeed(
- "cowboyjson",
- server.url("/feed.json").toUrl(),
- cowboyJson,
- )
-
- val fourteenMinsAgo = Instant.now().minusMinutes(14)
+ fun feedsSyncedWithin15MinAreNotIgnoredWhenForcingNetwork() =
runBlocking {
- rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
- assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
- assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
+ val cowboyJsonId =
+ insertFeed(
+ "cowboyjson",
+ server.url("/feed.json").toUrl(),
+ cowboyJson,
+ )
- testDb.db.feedDao().updateFeed(feed.copy(lastSync = fourteenMinsAgo))
+ val fourteenMinsAgo = Instant.now().minusMinutes(14)
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = cowboyJsonId, forceNetwork = true)
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.let { feed ->
+ assertTrue("Feed should have been synced", feed.lastSync.toEpochMilli() > 0)
+ assertTrue("Feed should have a valid response hash", feed.responseHash > 0)
+
+ testDb.db.feedDao().updateFeed(feed.copy(lastSync = fourteenMinsAgo))
+ }
+ rssLocalSync.syncFeeds(
+ feedId = cowboyJsonId,
+ forceNetwork = true,
+ minFeedAgeMinutes = 15,
+ )
}
- rssLocalSync.syncFeeds(
- feedId = cowboyJsonId,
- forceNetwork = true,
- minFeedAgeMinutes = 15,
+
+ assertEquals("Request should have been sent due to forced network", 2, server.requestCount)
+
+ assertNotEquals(
+ "Last sync should have changed",
+ fourteenMinsAgo,
+ testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync,
)
}
- assertEquals("Request should have been sent due to forced network", 2, server.requestCount)
+ @Test
+ fun feedShouldNotBeUpdatedIfRequestFails() =
+ runBlocking {
+ val response =
+ MockResponse().also {
+ it.setResponseCode(500)
+ }
+ server.enqueue(response)
+
+ val url = server.url("/feed.json")
+
+ val failingJsonId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ title = "failJson",
+ url = URL("$url"),
+ tag = "",
+ ),
+ )
- assertNotEquals(
- "Last sync should have changed",
- fourteenMinsAgo,
- testDb.db.feedDao().loadFeed(cowboyJsonId)!!.lastSync,
- )
- }
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = failingJsonId)
+ }
- @Test
- fun feedShouldNotBeUpdatedIfRequestFails() = runBlocking {
- val response = MockResponse().also {
- it.setResponseCode(500)
+ assertEquals(
+ "Last sync should not have been updated",
+ Instant.EPOCH,
+ testDb.db.feedDao().loadFeed(failingJsonId)!!.lastSync,
+ )
+
+ // Assert the feed was retrieved
+ assertEquals("/feed.json", server.takeRequest().path)
}
- server.enqueue(response)
- val url = server.url("/feed.json")
+ @Test
+ fun feedWithNoUniqueLinksGetsSomeGeneratedGUIDsFromTitles() =
+ runBlocking {
+ val response =
+ MockResponse().also {
+ it.setResponseCode(200)
+ it.setBody(String(nixosRss.readBytes()))
+ }
+ server.enqueue(response)
+
+ val url = server.url("/news-rss.xml")
+
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ title = "NixOS",
+ url = URL("$url"),
+ tag = "",
+ ),
+ )
- val failingJsonId = testDb.db.feedDao().insertFeed(
- Feed(
- title = "failJson",
- url = URL("$url"),
- tag = "",
- ),
- )
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = feedId,
+ )
+ }
- runBlocking {
- rssLocalSync.syncFeeds(feedId = failingJsonId)
- }
+ // Assert the feed was retrieved
+ assertEquals("/news-rss.xml", server.takeRequest().path)
- assertEquals(
- "Last sync should not have been updated",
- Instant.EPOCH,
- testDb.db.feedDao().loadFeed(failingJsonId)!!.lastSync,
- )
+ @Suppress("DEPRECATION")
+ val items = testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId)
+ assertEquals(
+ "Unique IDs should have been generated for items",
+ 99,
+ items.size,
+ )
- // Assert the feed was retrieved
- assertEquals("/feed.json", server.takeRequest().path)
- }
+ // Should be unique to item so that it stays the same after updates
+ assertTrue {
+ items.first().guid.startsWith("https://nixos.org/news.html|")
+ }
+ }
@Test
- fun feedWithNoUniqueLinksGetsSomeGeneratedGUIDsFromTitles() = runBlocking {
- val response = MockResponse().also {
- it.setResponseCode(200)
- it.setBody(String(nixosRss.readBytes()))
- }
- server.enqueue(response)
+ fun feedWithNoDatesShouldGetSomeGenerated() =
+ runBlocking {
+ val response =
+ MockResponse().also {
+ it.setResponseCode(200)
+ it.setBody(fooRss(2))
+ }
+ server.enqueue(response)
+
+ val url = server.url("/rss")
+
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ url = URL("$url"),
+ ),
+ )
- val url = server.url("/news-rss.xml")
+ val beforeSyncTime = Instant.now()
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- title = "NixOS",
- url = URL("$url"),
- tag = "",
- ),
- )
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = feedId)
+ }
- runBlocking {
- rssLocalSync.syncFeeds(
- feedId = feedId,
+ // Assert the feed was retrieved
+ assertEquals("/rss", server.takeRequest().path)
+
+ @Suppress("DEPRECATION")
+ val items = testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId)
+
+ assertNotNull(
+ "Item should have gotten a pubDate generated",
+ items[0].pubDate,
)
- }
- // Assert the feed was retrieved
- assertEquals("/news-rss.xml", server.takeRequest().path)
+ assertNotEquals(
+ "Items should have distinct pubDates",
+ items[0].pubDate,
+ items[1].pubDate,
+ )
- @Suppress("DEPRECATION")
- val items = testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId)
- assertEquals(
- "Unique IDs should have been generated for items",
- 99,
- items.size,
- )
+ assertTrue(
+ "The pubDate should be after 'before sync time'",
+ items[0].pubDate!!.toInstant() > beforeSyncTime,
+ )
- // Should be unique to item so that it stays the same after updates
- assertTrue {
- items.first().guid.startsWith("https://nixos.org/news.html|")
+ // Compare ID to compare insertion order (and thus pubdate compared to raw feed)
+ assertTrue("The pubDates' magnitude should match descending iteration order") {
+ items[0].guid == "https://foo.bar/1" &&
+ items[1].guid == "https://foo.bar/2" &&
+ items[0].pubDate!! > items[1].pubDate!!
+ }
}
- }
@Test
- fun feedWithNoDatesShouldGetSomeGenerated() = runBlocking {
- val response = MockResponse().also {
- it.setResponseCode(200)
- it.setBody(fooRss(2))
- }
- server.enqueue(response)
+ fun feedWithNoDatesShouldNotGetOverriddenDatesNextSync() =
+ runBlocking {
+ server.enqueue(
+ MockResponse().also {
+ it.setResponseCode(200)
+ it.setBody(fooRss(1))
+ },
+ )
+ server.enqueue(
+ MockResponse().also {
+ it.setResponseCode(200)
+ it.setBody(fooRss(2))
+ },
+ )
- val url = server.url("/rss")
+ val url = server.url("/rss")
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- url = URL("$url"),
- ),
- )
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ url = URL("$url"),
+ ),
+ )
- val beforeSyncTime = Instant.now()
+ // Sync first time
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = feedId)
+ }
- runBlocking {
- rssLocalSync.syncFeeds(feedId = feedId)
- }
+ // Assert the feed was retrieved
+ assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
- // Assert the feed was retrieved
- assertEquals("/rss", server.takeRequest().path)
-
- @Suppress("DEPRECATION")
- val items = testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId)
-
- assertNotNull(
- "Item should have gotten a pubDate generated",
- items[0].pubDate,
- )
-
- assertNotEquals(
- "Items should have distinct pubDates",
- items[0].pubDate,
- items[1].pubDate,
- )
-
- assertTrue(
- "The pubDate should be after 'before sync time'",
- items[0].pubDate!!.toInstant() > beforeSyncTime,
- )
-
- // Compare ID to compare insertion order (and thus pubdate compared to raw feed)
- assertTrue("The pubDates' magnitude should match descending iteration order") {
- items[0].guid == "https://foo.bar/1" &&
- items[1].guid == "https://foo.bar/2" &&
- items[0].pubDate!! > items[1].pubDate!!
- }
- }
+ @Suppress("DEPRECATION")
+ val firstItem =
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId).let { items ->
+ assertNotNull(
+ "Item should have gotten a pubDate generated",
+ items[0].pubDate,
+ )
- @Test
- fun feedWithNoDatesShouldNotGetOverriddenDatesNextSync() = runBlocking {
- server.enqueue(
- MockResponse().also {
- it.setResponseCode(200)
- it.setBody(fooRss(1))
- },
- )
- server.enqueue(
- MockResponse().also {
- it.setResponseCode(200)
- it.setBody(fooRss(2))
- },
- )
-
- val url = server.url("/rss")
-
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- url = URL("$url"),
- ),
- )
-
- // Sync first time
- runBlocking {
- rssLocalSync.syncFeeds(feedId = feedId)
- }
+ items[0]
+ }
- // Assert the feed was retrieved
- assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
+ // Sync second time
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = feedId, forceNetwork = true)
+ }
- @Suppress("DEPRECATION")
- val firstItem =
+ // Assert the feed was retrieved
+ assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
+
+ @Suppress("DEPRECATION")
testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId).let { items ->
- assertNotNull(
- "Item should have gotten a pubDate generated",
- items[0].pubDate,
+ assertEquals(
+ "Should be 2 items in feed",
+ 2,
+ items.size,
)
- items[0]
+ val item = items.last()
+
+ assertEquals(
+ "Making sure we are comparing the same item",
+ firstItem.id,
+ item.id,
+ )
+
+ assertEquals(
+ "Pubdate should not have changed",
+ firstItem.pubDate,
+ item.pubDate,
+ )
}
+ }
- // Sync second time
+ @Test
+ fun feedShouldNotBeCleanedToHaveLessItemsThanActualFeed() =
runBlocking {
- rssLocalSync.syncFeeds(feedId = feedId, forceNetwork = true)
- }
+ val feedItemCount = 9
+ server.enqueue(
+ MockResponse().also {
+ it.setResponseCode(200)
+ it.setBody(fooRss(feedItemCount))
+ },
+ )
- // Assert the feed was retrieved
- assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
+ val url = server.url("/rss")
- @Suppress("DEPRECATION")
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId).let { items ->
- assertEquals(
- "Should be 2 items in feed",
- 2,
- items.size,
- )
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ url = URL("$url"),
+ ),
+ )
- val item = items.last()
+ val maxFeedItemCount = 5
- assertEquals(
- "Making sure we are comparing the same item",
- firstItem.id,
- item.id,
- )
+ // Sync first time
+ runBlocking {
+ rssLocalSync.syncFeeds(
+ feedId = feedId,
+ maxFeedItemCount = maxFeedItemCount,
+ )
+ }
- assertEquals(
- "Pubdate should not have changed",
- firstItem.pubDate,
- item.pubDate,
- )
+ // Assert the feed was retrieved
+ assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
+
+ @Suppress("DEPRECATION")
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId).let { items ->
+ assertEquals(
+ "Feed should have no less items than in the raw feed even if that's more than cleanup count",
+ feedItemCount,
+ items.size,
+ )
+ }
}
- }
@Test
- fun feedShouldNotBeCleanedToHaveLessItemsThanActualFeed() = runBlocking {
- val feedItemCount = 9
- server.enqueue(
- MockResponse().also {
- it.setResponseCode(200)
- it.setBody(fooRss(feedItemCount))
- },
- )
-
- val url = server.url("/rss")
-
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- url = URL("$url"),
- ),
- )
-
- val maxFeedItemCount = 5
-
- // Sync first time
+ fun slowResponseShouldBeOk() =
runBlocking {
- rssLocalSync.syncFeeds(
- feedId = feedId,
- maxFeedItemCount = maxFeedItemCount,
- )
- }
+ val url = server.url("/atom.xml").toUrl()
+ val cowboyAtomId = insertFeed("cowboy", url, cowboyAtom, isJson = false)
+ responses[url]!!.throttleBody(1024 * 100, 29, TimeUnit.SECONDS)
- // Assert the feed was retrieved
- assertEquals("/rss", server.takeRequest(100, TimeUnit.MILLISECONDS)!!.path)
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = cowboyAtomId)
+ }
- @Suppress("DEPRECATION")
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(feedId).let { items ->
+ @Suppress("DEPRECATION")
assertEquals(
- "Feed should have no less items than in the raw feed even if that's more than cleanup count",
- feedItemCount,
- items.size,
+ "Feed should have been parsed from slow response",
+ 15,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
)
}
- }
@Test
- fun slowResponseShouldBeOk() = runBlocking {
- val url = server.url("/atom.xml").toUrl()
- val cowboyAtomId = insertFeed("cowboy", url, cowboyAtom, isJson = false)
- responses[url]!!.throttleBody(1024 * 100, 29, TimeUnit.SECONDS)
-
+ fun verySlowResponseShouldBeCancelled() =
runBlocking {
- rssLocalSync.syncFeeds(feedId = cowboyAtomId)
- }
+ val url = server.url("/atom.xml").toUrl()
+ val cowboyAtomId = insertFeed("cowboy", url, cowboyAtom, isJson = false)
+ responses[url]!!.throttleBody(1024 * 100, 31, TimeUnit.SECONDS)
- @Suppress("DEPRECATION")
- assertEquals(
- "Feed should have been parsed from slow response",
- 15,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
- )
- }
-
- @Test
- fun verySlowResponseShouldBeCancelled() = runBlocking {
- val url = server.url("/atom.xml").toUrl()
- val cowboyAtomId = insertFeed("cowboy", url, cowboyAtom, isJson = false)
- responses[url]!!.throttleBody(1024 * 100, 31, TimeUnit.SECONDS)
+ runBlocking {
+ rssLocalSync.syncFeeds(feedId = cowboyAtomId)
+ }
- runBlocking {
- rssLocalSync.syncFeeds(feedId = cowboyAtomId)
+ @Suppress("DEPRECATION")
+ assertEquals(
+ "Feed should not have been parsed from extremely slow response",
+ 0,
+ testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
+ )
}
- @Suppress("DEPRECATION")
- assertEquals(
- "Feed should not have been parsed from extremely slow response",
- 0,
- testDb.db.feedItemDao().loadFeedItemsInFeedDesc(cowboyAtomId).size,
- )
- }
-
fun fooRss(itemsCount: Int = 1): String {
return """
-
-
-
- Foo Feed
- https://foo.bar
- ${
- (1..itemsCount).map {
- """
+
+
+
+ Foo Feed
+ https://foo.bar
+ ${
+ (1..itemsCount).map {
+ """
-
Foo Item $it
https://foo.bar/$it
Woop woop $it
+ """.trimIndent()
+ }.fold("") { acc, s ->
+ "$acc\n$s"
+ }
+ }
+
+
""".trimIndent()
- }.fold("") { acc, s ->
- "$acc\n$s"
- }
- }
-
-
- """.trimIndent()
}
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssNotificationsKtTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssNotificationsKtTest.kt
index c45e3e1896..237d54c821 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssNotificationsKtTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/RssNotificationsKtTest.kt
@@ -5,12 +5,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.nononsenseapps.feeder.db.COL_LINK
import com.nononsenseapps.feeder.db.room.FeedItem
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNull
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
@RunWith(AndroidJUnit4::class)
class RssNotificationsKtTest {
@@ -20,7 +20,7 @@ class RssNotificationsKtTest {
assertEquals(
"com.nononsenseapps.feeder.ui.OpenLinkInDefaultActivity",
- intent.component?.className
+ intent.component?.className,
)
assertEquals("99", intent.data?.lastPathSegment)
assertEquals("http://foo", intent.data?.getQueryParameter(COL_LINK))
@@ -28,11 +28,12 @@ class RssNotificationsKtTest {
@Test
fun openInDefaultActivityIntentsAreConsideredDifferentForSameItem() {
- val feedItem = FeedItem(
- id = 5,
- link = "http://foo",
- enclosureLink = "ftp://bar"
- )
+ val feedItem =
+ FeedItem(
+ id = 5,
+ link = "http://foo",
+ enclosureLink = "ftp://bar",
+ )
val linkIntent = getOpenInDefaultActivityIntent(getInstrumentation().context, feedItem.id, link = feedItem.link)
val enclosureIntent = getOpenInDefaultActivityIntent(getInstrumentation().context, feedItem.id, link = feedItem.enclosureLink)
@@ -40,17 +41,17 @@ class RssNotificationsKtTest {
assertFalse(
linkIntent.filterEquals(enclosureIntent),
- message = "linkIntent should not be considered equal to enclosureIntent"
+ message = "linkIntent should not be considered equal to enclosureIntent",
)
assertFalse(
linkIntent.filterEquals(markAsReadIntent),
- message = "linkIntent should not be considered equal to markAsReadIntent"
+ message = "linkIntent should not be considered equal to markAsReadIntent",
)
assertFalse(
enclosureIntent.filterEquals(markAsReadIntent),
- message = "enclosureIntent should not be considered equal to markAsReadIntent"
+ message = "enclosureIntent should not be considered equal to markAsReadIntent",
)
}
@@ -61,8 +62,9 @@ class RssNotificationsKtTest {
val enclosureIntent = getOpenInDefaultActivityIntent(getInstrumentation().context, 5, link = magnetLink)
assertEquals(
- magnetLink, enclosureIntent.data?.getQueryParameter(COL_LINK),
- message = "Expected link to not get garbled as query parameter"
+ magnetLink,
+ enclosureIntent.data?.getQueryParameter(COL_LINK),
+ message = "Expected link to not get garbled as query parameter",
)
}
@@ -72,7 +74,7 @@ class RssNotificationsKtTest {
assertNull(
enclosureIntent.data?.getQueryParameter(COL_LINK),
- message = "Expected a null query parameter"
+ message = "Expected a null query parameter",
)
}
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt
index b0a0ed3d60..23e080bb0c 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/model/opml/OPMLTest.kt
@@ -23,10 +23,6 @@ import com.nononsenseapps.feeder.db.room.OPEN_ARTICLE_WITH_APPLICATION_DEFAULT
import com.nononsenseapps.feeder.model.OPMLParserHandler
import com.nononsenseapps.feeder.util.Either
import com.nononsenseapps.feeder.util.ToastMaker
-import java.io.File
-import java.io.IOException
-import java.net.URL
-import kotlin.random.Random
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
@@ -43,32 +39,40 @@ import org.kodein.di.bind
import org.kodein.di.compose.instance
import org.kodein.di.instance
import org.kodein.di.singleton
+import java.io.File
+import java.io.IOException
+import java.net.URL
+import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
class OPMLTest : DIAware {
private val context: Context = getApplicationContext()
lateinit var db: AppDatabase
- override val di = DI.lazy {
- bind() with singleton {
- PreferenceManager.getDefaultSharedPreferences(
- this@OPMLTest.context,
- )
+ override val di =
+ DI.lazy {
+ bind() with
+ singleton {
+ PreferenceManager.getDefaultSharedPreferences(
+ this@OPMLTest.context,
+ )
+ }
+ bind() with instance(db)
+ bind() with singleton { db.feedDao() }
+ bind() with singleton { db.blocklistDao() }
+ bind() with singleton { SettingsStore(di) }
+ bind() with singleton { FeedStore(di) }
+ bind() with singleton { OPMLImporter(di) }
+ bind() with singleton { WorkManager.getInstance(this@OPMLTest.context) }
+ bind() with
+ instance(
+ object : ToastMaker {
+ override suspend fun makeToast(text: String) {}
+
+ override suspend fun makeToast(resId: Int) {}
+ },
+ )
+ bind() with singleton { this@OPMLTest.context.contentResolver }
}
- bind() with instance(db)
- bind() with singleton { db.feedDao() }
- bind() with singleton { db.blocklistDao() }
- bind() with singleton { SettingsStore(di) }
- bind() with singleton { FeedStore(di) }
- bind() with singleton { OPMLImporter(di) }
- bind() with singleton { WorkManager.getInstance(this@OPMLTest.context) }
- bind() with instance(
- object : ToastMaker {
- override suspend fun makeToast(text: String) {}
- override suspend fun makeToast(resId: Int) {}
- },
- )
- bind() with singleton { this@OPMLTest.context.contentResolver }
- }
private var dir: File? = null
private var path: File? = null
@@ -94,629 +98,648 @@ class OPMLTest : DIAware {
@MediumTest
@Test
- fun testWrite() = runBlocking {
- // Create some feeds
- createSampleFeeds()
-
- writeFile(
- path = path!!.absolutePath,
- settings = ALL_SETTINGS_WITH_VALUES,
- blockedPatterns = BLOCKED_PATTERNS,
- tags = getTags(),
- ) { tag ->
- db.feedDao().loadFeeds(tag = tag)
- }
+ fun testWrite() =
+ runBlocking {
+ // Create some feeds
+ createSampleFeeds()
+
+ writeFile(
+ path = path!!.absolutePath,
+ settings = ALL_SETTINGS_WITH_VALUES,
+ blockedPatterns = BLOCKED_PATTERNS,
+ tags = getTags(),
+ ) { tag ->
+ db.feedDao().loadFeeds(tag = tag)
+ }
- // check contents of file
- path!!.bufferedReader().useLines { lines ->
- lines.forEachIndexed { i, line ->
- assertEquals("line $i differed", sampleFile[i], line)
+ // check contents of file
+ path!!.bufferedReader().useLines { lines ->
+ lines.forEachIndexed { i, line ->
+ assertEquals("line $i differed", sampleFile[i], line)
+ }
}
}
- }
@MediumTest
@Test
- fun testReadSettings() = runBlocking {
- writeSampleFile()
+ fun testReadSettings() =
+ runBlocking {
+ writeSampleFile()
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseFile(path!!.canonicalPath)
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseFile(path!!.canonicalPath)
- // Verify database is correct
- val actual = settingsStore.getAllSettings()
+ // Verify database is correct
+ val actual = settingsStore.getAllSettings()
- ALL_SETTINGS_WITH_VALUES.forEach { (key, expected) ->
- assertEquals(expected, actual[key].toString())
- }
+ ALL_SETTINGS_WITH_VALUES.forEach { (key, expected) ->
+ assertEquals(expected, actual[key].toString())
+ }
- val actualBlocked = settingsStore.blockListPreference.first()
+ val actualBlocked = settingsStore.blockListPreference.first()
- assertEquals(1, actualBlocked.size)
- assertEquals("foo", actualBlocked.first())
- }
+ assertEquals(1, actualBlocked.size)
+ assertEquals("foo", actualBlocked.first())
+ }
@MediumTest
@Test
- fun testRead() = runBlocking {
- writeSampleFile()
-
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseFile(path!!.canonicalPath)
-
- // Verify database is correct
- val seen = ArrayList()
- val feeds = db.feedDao().loadFeeds()
- assertFalse("No feeds in DB!", feeds.isEmpty())
- for (feed in feeds) {
- val i = Integer.parseInt(feed.title.replace("[custom \"]".toRegex(), ""))
- seen.add(i)
- assertEquals("URL doesn't match", URL("http://example.com/$i/rss.xml"), feed.url)
-
- when (i) {
- 0 -> {
- assertEquals("title should be the same", "\"$i\"", feed.title)
- assertEquals(
- "custom title should have been set to title",
- "\"$i\"",
- feed.customTitle,
- )
- }
-
- else -> {
- assertEquals(
- "custom title should have overridden title",
- "custom \"$i\"",
- feed.title,
- )
- assertEquals(
- "title and custom title should match",
- feed.customTitle,
- feed.title,
- )
+ fun testRead() =
+ runBlocking {
+ writeSampleFile()
+
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseFile(path!!.canonicalPath)
+
+ // Verify database is correct
+ val seen = ArrayList()
+ val feeds = db.feedDao().loadFeeds()
+ assertFalse("No feeds in DB!", feeds.isEmpty())
+ for (feed in feeds) {
+ val i = Integer.parseInt(feed.title.replace("[custom \"]".toRegex(), ""))
+ seen.add(i)
+ assertEquals("URL doesn't match", URL("http://example.com/$i/rss.xml"), feed.url)
+
+ when (i) {
+ 0 -> {
+ assertEquals("title should be the same", "\"$i\"", feed.title)
+ assertEquals(
+ "custom title should have been set to title",
+ "\"$i\"",
+ feed.customTitle,
+ )
+ }
+
+ else -> {
+ assertEquals(
+ "custom title should have overridden title",
+ "custom \"$i\"",
+ feed.title,
+ )
+ assertEquals(
+ "title and custom title should match",
+ feed.customTitle,
+ feed.title,
+ )
+ }
+ }
+
+ when {
+ i % 3 == 1 -> assertEquals("tag1", feed.tag)
+ i % 3 == 2 -> assertEquals("tag2", feed.tag)
+ else -> assertEquals("", feed.tag)
}
}
-
- when {
- i % 3 == 1 -> assertEquals("tag1", feed.tag)
- i % 3 == 2 -> assertEquals("tag2", feed.tag)
- else -> assertEquals("", feed.tag)
+ for (i in 0..9) {
+ assertTrue("Missing $i", seen.contains(i))
}
}
- for (i in 0..9) {
- assertTrue("Missing $i", seen.contains(i))
- }
- }
@MediumTest
@Test
- fun testReadExisting() = runBlocking {
- writeSampleFile()
-
- // Create something that does not exist
- var feednew = Feed(
- url = URL("http://example.com/20/rss.xml"),
- title = "\"20\"",
- tag = "kapow",
- )
- var id = db.feedDao().insertFeed(feednew)
- feednew = feednew.copy(id = id)
- // Create something that will exist
- var feedold = Feed(
- url = URL("http://example.com/0/rss.xml"),
- title = "\"0\"",
- )
- id = db.feedDao().insertFeed(feedold)
-
- feedold = feedold.copy(id = id)
-
- // Read file
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseFile(path!!.canonicalPath)
-
- // should not kill the existing stuff
- val seen = ArrayList()
- val feeds = db.feedDao().loadFeeds()
- assertFalse("No feeds in DB!", feeds.isEmpty())
- for (feed in feeds) {
- val i = Integer.parseInt(feed.title.replace("[custom \"]".toRegex(), ""))
- seen.add(i)
- assertEquals(URL("http://example.com/$i/rss.xml"), feed.url)
-
- when {
- i == 20 -> {
- assertEquals("Should not have changed", feednew.id, feed.id)
- assertEquals("Should not have changed", feednew.url, feed.url)
- assertEquals("Should not have changed", feednew.tag, feed.tag)
- }
-
- i % 3 == 1 -> assertEquals("tag1", feed.tag)
- i % 3 == 2 -> assertEquals("tag2", feed.tag)
- else -> assertEquals("", feed.tag)
- }
-
- // Ensure titles are correct
- when (i) {
- 0 -> {
- assertEquals("title should be the same", feedold.title, feed.title)
- assertEquals(
- "custom title should have been set to title",
- feedold.title,
- feed.customTitle,
- )
- }
-
- 20 -> {
- assertEquals(
- "feed not present in OPML should not have changed",
- feednew.title,
- feed.title,
- )
- assertEquals(
- "feed not present in OPML should not have changed",
- feednew.customTitle,
- feednew.customTitle,
- )
- }
-
- else -> {
- assertEquals(
- "custom title should have overridden title",
- "custom \"$i\"",
- feed.title,
- )
+ fun testReadExisting() =
+ runBlocking {
+ writeSampleFile()
+
+ // Create something that does not exist
+ var feednew =
+ Feed(
+ url = URL("http://example.com/20/rss.xml"),
+ title = "\"20\"",
+ tag = "kapow",
+ )
+ var id = db.feedDao().insertFeed(feednew)
+ feednew = feednew.copy(id = id)
+ // Create something that will exist
+ var feedold =
+ Feed(
+ url = URL("http://example.com/0/rss.xml"),
+ title = "\"0\"",
+ )
+ id = db.feedDao().insertFeed(feedold)
+
+ feedold = feedold.copy(id = id)
+
+ // Read file
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseFile(path!!.canonicalPath)
+
+ // should not kill the existing stuff
+ val seen = ArrayList()
+ val feeds = db.feedDao().loadFeeds()
+ assertFalse("No feeds in DB!", feeds.isEmpty())
+ for (feed in feeds) {
+ val i = Integer.parseInt(feed.title.replace("[custom \"]".toRegex(), ""))
+ seen.add(i)
+ assertEquals(URL("http://example.com/$i/rss.xml"), feed.url)
+
+ when {
+ i == 20 -> {
+ assertEquals("Should not have changed", feednew.id, feed.id)
+ assertEquals("Should not have changed", feednew.url, feed.url)
+ assertEquals("Should not have changed", feednew.tag, feed.tag)
+ }
+
+ i % 3 == 1 -> assertEquals("tag1", feed.tag)
+ i % 3 == 2 -> assertEquals("tag2", feed.tag)
+ else -> assertEquals("", feed.tag)
+ }
+
+ // Ensure titles are correct
+ when (i) {
+ 0 -> {
+ assertEquals("title should be the same", feedold.title, feed.title)
+ assertEquals(
+ "custom title should have been set to title",
+ feedold.title,
+ feed.customTitle,
+ )
+ }
+
+ 20 -> {
+ assertEquals(
+ "feed not present in OPML should not have changed",
+ feednew.title,
+ feed.title,
+ )
+ assertEquals(
+ "feed not present in OPML should not have changed",
+ feednew.customTitle,
+ feednew.customTitle,
+ )
+ }
+
+ else -> {
+ assertEquals(
+ "custom title should have overridden title",
+ "custom \"$i\"",
+ feed.title,
+ )
+ assertEquals(
+ "title and custom title should match",
+ feed.customTitle,
+ feed.title,
+ )
+ }
+ }
+
+ if (i == 0) {
+ // Make sure id is same as old
+ assertEquals("Id should be same still", feedold.id, feed.id)
+
+ assertTrue("Notify is wrong", feed.notify)
+ assertTrue("AlternateId is wrong", feed.alternateId)
+ assertTrue("FullTextByDefault is wrong", feed.fullTextByDefault)
+ assertEquals("OpenArticlesWith is wrong", "reader", feed.openArticlesWith)
assertEquals(
- "title and custom title should match",
- feed.customTitle,
- feed.title,
+ "ImageURL is wrong",
+ URL("https://example.com/feedImage.png"),
+ feed.imageUrl,
)
}
}
-
- if (i == 0) {
- // Make sure id is same as old
- assertEquals("Id should be same still", feedold.id, feed.id)
-
- assertTrue("Notify is wrong", feed.notify)
- assertTrue("AlternateId is wrong", feed.alternateId)
- assertTrue("FullTextByDefault is wrong", feed.fullTextByDefault)
- assertEquals("OpenArticlesWith is wrong", "reader", feed.openArticlesWith)
- assertEquals(
- "ImageURL is wrong",
- URL("https://example.com/feedImage.png"),
- feed.imageUrl,
- )
+ assertTrue("Missing 20", seen.contains(20))
+ for (i in 0..9) {
+ assertTrue("Missing $i", seen.contains(i))
}
}
- assertTrue("Missing 20", seen.contains(20))
- for (i in 0..9) {
- assertTrue("Missing $i", seen.contains(i))
- }
- }
@MediumTest
@Test
- fun testReadBadFile() = runBlocking {
- path!!.bufferedWriter().use {
- it.write("This is just some bullshit in the file\n")
- }
+ fun testReadBadFile() =
+ runBlocking {
+ path!!.bufferedWriter().use {
+ it.write("This is just some bullshit in the file\n")
+ }
- // Read file
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseFile(path!!.absolutePath)
+ // Read file
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseFile(path!!.absolutePath)
- val feeds = db.feedDao().loadFeeds()
- assertTrue("Expected no feeds and no exception", feeds.isEmpty())
- }
+ val feeds = db.feedDao().loadFeeds()
+ assertTrue("Expected no feeds and no exception", feeds.isEmpty())
+ }
@SmallTest
@Test
- fun testReadMissingFile() = runBlocking {
- val path = File(dir, "lsadflibaslsdfa.opml")
- // Read file
- val parser = OpmlPullParser(opmlParserHandler)
- val result = parser.parseFile(path.absolutePath)
-
- assertTrue(result.isLeft())
- }
+ fun testReadMissingFile() =
+ runBlocking {
+ val path = File(dir, "lsadflibaslsdfa.opml")
+ // Read file
+ val parser = OpmlPullParser(opmlParserHandler)
+ val result = parser.parseFile(path.absolutePath)
+
+ assertTrue(result.isLeft())
+ }
@Throws(IOException::class)
- private fun writeSampleFile() = runBlocking {
- // Use test write to write the sample file
- testWrite()
- // Then delete all feeds again
- db.runInTransaction {
- runBlocking {
- db.feedDao().loadFeeds().forEach {
- db.feedDao().deleteFeed(it)
+ private fun writeSampleFile() =
+ runBlocking {
+ // Use test write to write the sample file
+ testWrite()
+ // Then delete all feeds again
+ db.runInTransaction {
+ runBlocking {
+ db.feedDao().loadFeeds().forEach {
+ db.feedDao().deleteFeed(it)
+ }
}
}
}
- }
private suspend fun createSampleFeeds() {
for (i in 0..9) {
- val feed = Feed(
- url = URL("http://example.com/$i/rss.xml"),
- title = "\"$i\"",
- customTitle = if (i == 0) "" else "custom \"$i\"",
- tag = when (i % 3) {
- 1 -> "tag1"
- 2 -> "tag2"
- else -> ""
- },
- notify = i == 0,
- alternateId = i == 0,
- fullTextByDefault = i == 0,
- imageUrl = if (i == 0) {
- URL("https://example.com/feedImage.png")
- } else {
- null
- },
- openArticlesWith = if (i == 0) {
- "reader"
- } else {
- OPEN_ARTICLE_WITH_APPLICATION_DEFAULT
- },
- )
+ val feed =
+ Feed(
+ url = URL("http://example.com/$i/rss.xml"),
+ title = "\"$i\"",
+ customTitle = if (i == 0) "" else "custom \"$i\"",
+ tag =
+ when (i % 3) {
+ 1 -> "tag1"
+ 2 -> "tag2"
+ else -> ""
+ },
+ notify = i == 0,
+ alternateId = i == 0,
+ fullTextByDefault = i == 0,
+ imageUrl =
+ if (i == 0) {
+ URL("https://example.com/feedImage.png")
+ } else {
+ null
+ },
+ openArticlesWith =
+ if (i == 0) {
+ "reader"
+ } else {
+ OPEN_ARTICLE_WITH_APPLICATION_DEFAULT
+ },
+ )
db.feedDao().insertFeed(feed)
}
}
- private suspend fun getTags(): List =
- db.feedDao().loadTags()
+ private suspend fun getTags(): List = db.feedDao().loadTags()
@Test
@MediumTest
- fun antennaPodOPMLImports() = runBlocking {
- // given
- val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("antennapod-feeds.opml")!!
-
- // when
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseInputStreamWithFallback(opmlStream)
-
- // then
- val feeds = db.feedDao().loadFeeds()
- val tags = db.feedDao().loadTags()
- assertEquals("Expecting 8 feeds", 8, feeds.size)
- assertEquals("Expecting 1 tags (incl empty)", 1, tags.size)
-
- feeds.forEach { feed ->
- assertEquals("No tag expected", "", feed.tag)
- when (feed.url) {
- URL("http://aliceisntdead.libsyn.com/rss") -> {
- assertEquals("Alice Isn't Dead", feed.title)
- }
-
- URL("http://feeds.soundcloud.com/users/soundcloud:users:154104768/sounds.rss") -> {
- assertEquals("Invisible City", feed.title)
- }
-
- URL("http://feeds.feedburner.com/PodCastle_Main") -> {
- assertEquals("PodCastle", feed.title)
- }
-
- URL("http://www.artofstorytellingshow.com/podcast/storycast.xml") -> {
- assertEquals("The Art of Storytelling with Brother Wolf", feed.title)
+ fun antennaPodOPMLImports() =
+ runBlocking {
+ // given
+ val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("antennapod-feeds.opml")!!
+
+ // when
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseInputStreamWithFallback(opmlStream)
+
+ // then
+ val feeds = db.feedDao().loadFeeds()
+ val tags = db.feedDao().loadTags()
+ assertEquals("Expecting 8 feeds", 8, feeds.size)
+ assertEquals("Expecting 1 tags (incl empty)", 1, tags.size)
+
+ feeds.forEach { feed ->
+ assertEquals("No tag expected", "", feed.tag)
+ when (feed.url) {
+ URL("http://aliceisntdead.libsyn.com/rss") -> {
+ assertEquals("Alice Isn't Dead", feed.title)
+ }
+
+ URL("http://feeds.soundcloud.com/users/soundcloud:users:154104768/sounds.rss") -> {
+ assertEquals("Invisible City", feed.title)
+ }
+
+ URL("http://feeds.feedburner.com/PodCastle_Main") -> {
+ assertEquals("PodCastle", feed.title)
+ }
+
+ URL("http://www.artofstorytellingshow.com/podcast/storycast.xml") -> {
+ assertEquals("The Art of Storytelling with Brother Wolf", feed.title)
+ }
+
+ URL("http://feeds.feedburner.com/TheCleansed") -> {
+ assertEquals("The Cleansed: A Post-Apocalyptic Saga", feed.title)
+ }
+
+ URL("http://media.signumuniversity.org/tolkienprof/feed") -> {
+ assertEquals("The Tolkien Professor", feed.title)
+ }
+
+ URL("http://nightvale.libsyn.com/rss") -> {
+ assertEquals("Welcome to Night Vale", feed.title)
+ }
+
+ URL("http://withinthewires.libsyn.com/rss") -> {
+ assertEquals("Within the Wires", feed.title)
+ }
+
+ else -> fail("Unexpected URI. Feed: $feed")
}
-
- URL("http://feeds.feedburner.com/TheCleansed") -> {
- assertEquals("The Cleansed: A Post-Apocalyptic Saga", feed.title)
- }
-
- URL("http://media.signumuniversity.org/tolkienprof/feed") -> {
- assertEquals("The Tolkien Professor", feed.title)
- }
-
- URL("http://nightvale.libsyn.com/rss") -> {
- assertEquals("Welcome to Night Vale", feed.title)
- }
-
- URL("http://withinthewires.libsyn.com/rss") -> {
- assertEquals("Within the Wires", feed.title)
- }
-
- else -> fail("Unexpected URI. Feed: $feed")
}
}
- }
@Test
@MediumTest
- fun flymOPMLImports() = runBlocking {
- // given
- val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("Flym_auto_backup.opml")!!
-
- // when
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseInputStreamWithFallback(opmlStream)
-
- // then
- val feeds = db.feedDao().loadFeeds()
- val tags = db.feedDao().loadTags()
- assertEquals("Expecting 11 feeds", 11, feeds.size)
- assertEquals("Expecting 4 tags (incl empty)", 4, tags.size)
-
- feeds.forEach { feed ->
- when (feed.url) {
- URL("http://www.smbc-comics.com/rss.php") -> {
- assertEquals("black humor", feed.tag)
- assertEquals("SMBC", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
- }
-
- URL("http://www.deathbulge.com/rss.xml") -> {
- assertEquals("black humor", feed.tag)
- assertEquals("Deathbulge", feed.customTitle)
- assertTrue(feed.fullTextByDefault)
- }
-
- URL("http://www.sandraandwoo.com/gaia/feed/") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Gaia", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
- }
-
- URL("http://replaycomic.com/feed/") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Replay", feed.customTitle)
- assertTrue(feed.fullTextByDefault)
- }
-
- URL("http://www.cuttimecomic.com/rss.php") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Cut Time", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
+ fun flymOPMLImports() =
+ runBlocking {
+ // given
+ val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("Flym_auto_backup.opml")!!
+
+ // when
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseInputStreamWithFallback(opmlStream)
+
+ // then
+ val feeds = db.feedDao().loadFeeds()
+ val tags = db.feedDao().loadTags()
+ assertEquals("Expecting 11 feeds", 11, feeds.size)
+ assertEquals("Expecting 4 tags (incl empty)", 4, tags.size)
+
+ feeds.forEach { feed ->
+ when (feed.url) {
+ URL("http://www.smbc-comics.com/rss.php") -> {
+ assertEquals("black humor", feed.tag)
+ assertEquals("SMBC", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("http://www.deathbulge.com/rss.xml") -> {
+ assertEquals("black humor", feed.tag)
+ assertEquals("Deathbulge", feed.customTitle)
+ assertTrue(feed.fullTextByDefault)
+ }
+
+ URL("http://www.sandraandwoo.com/gaia/feed/") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Gaia", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("http://replaycomic.com/feed/") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Replay", feed.customTitle)
+ assertTrue(feed.fullTextByDefault)
+ }
+
+ URL("http://www.cuttimecomic.com/rss.php") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Cut Time", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("http://www.commitstrip.com/feed/") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Commit strip", feed.customTitle)
+ assertTrue(feed.fullTextByDefault)
+ }
+
+ URL("http://www.sandraandwoo.com/feed/") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Sandra and Woo", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("http://www.awakencomic.com/rss.php") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Awaken", feed.customTitle)
+ assertTrue(feed.fullTextByDefault)
+ }
+
+ URL("http://www.questionablecontent.net/QCRSS.xml") -> {
+ assertEquals("comics", feed.tag)
+ assertEquals("Questionable Content", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("https://www.archlinux.org/feeds/news/") -> {
+ assertEquals("Tech", feed.tag)
+ assertEquals("Arch news", feed.customTitle)
+ assertFalse(feed.fullTextByDefault)
+ }
+
+ URL("https://grisebouille.net/feed/") -> {
+ assertEquals("Political humour", feed.tag)
+ assertEquals("Grisebouille", feed.customTitle)
+ assertTrue(feed.fullTextByDefault)
+ }
+
+ else -> fail("Unexpected URI. Feed: $feed")
}
-
- URL("http://www.commitstrip.com/feed/") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Commit strip", feed.customTitle)
- assertTrue(feed.fullTextByDefault)
- }
-
- URL("http://www.sandraandwoo.com/feed/") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Sandra and Woo", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
- }
-
- URL("http://www.awakencomic.com/rss.php") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Awaken", feed.customTitle)
- assertTrue(feed.fullTextByDefault)
- }
-
- URL("http://www.questionablecontent.net/QCRSS.xml") -> {
- assertEquals("comics", feed.tag)
- assertEquals("Questionable Content", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
- }
-
- URL("https://www.archlinux.org/feeds/news/") -> {
- assertEquals("Tech", feed.tag)
- assertEquals("Arch news", feed.customTitle)
- assertFalse(feed.fullTextByDefault)
- }
-
- URL("https://grisebouille.net/feed/") -> {
- assertEquals("Political humour", feed.tag)
- assertEquals("Grisebouille", feed.customTitle)
- assertTrue(feed.fullTextByDefault)
- }
-
- else -> fail("Unexpected URI. Feed: $feed")
}
}
- }
@Test
@MediumTest
- fun rssGuardOPMLImports1() = runBlocking {
- // given
- val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("rssguard_1.opml")!!
-
- // when
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseInputStreamWithFallback(opmlStream)
-
- // then
- val feeds = db.feedDao().loadFeeds()
- val tags = db.feedDao().loadTags()
- assertEquals("Expecting 30 feeds", 30, feeds.size)
- assertEquals("Expecting 6 tags (incl empty)", 6, tags.size)
-
- feeds.forEach { feed ->
- when (feed.url) {
- URL("http://www.les-trois-sagesses.org/rss-articles.xml") -> {
- assertEquals("Religion", feed.tag)
- assertEquals("Les trois sagesses", feed.customTitle)
- }
-
- URL("http://www.avrildeperthuis.com/feed/") -> {
- assertEquals("Amis", feed.tag)
- assertEquals("avril de perthuis", feed.customTitle)
- }
-
- URL("http://www.fashioningtech.com/profiles/blog/feed?xn_auth=no") -> {
- assertEquals("Actu Geek", feed.tag)
- assertEquals("Everyone's Blog Posts - Fashioning Technology", feed.customTitle)
- }
-
- URL("http://feeds2.feedburner.com/ChartPorn") -> {
- assertEquals("Graphs", feed.tag)
- assertEquals("Chart Porn", feed.customTitle)
- }
-
- URL("http://www.mosqueedeparis.net/index.php?format=feed&type=atom") -> {
- assertEquals("Religion", feed.tag)
- assertEquals("Mosquee de Paris", feed.customTitle)
- }
-
- URL("http://sourceforge.net/projects/stuntrally/rss") -> {
- assertEquals("Mainstream update", feed.tag)
- assertEquals("Stunt Rally", feed.customTitle)
- }
-
- URL("http://www.mairie6.lyon.fr/cs/Satellite?Thematique=&TypeContenu=Actualite&pagename=RSSFeed&site=Mairie6") -> {
- assertEquals("", feed.tag)
- assertEquals("Actualités", feed.customTitle)
+ fun rssGuardOPMLImports1() =
+ runBlocking {
+ // given
+ val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("rssguard_1.opml")!!
+
+ // when
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseInputStreamWithFallback(opmlStream)
+
+ // then
+ val feeds = db.feedDao().loadFeeds()
+ val tags = db.feedDao().loadTags()
+ assertEquals("Expecting 30 feeds", 30, feeds.size)
+ assertEquals("Expecting 6 tags (incl empty)", 6, tags.size)
+
+ feeds.forEach { feed ->
+ when (feed.url) {
+ URL("http://www.les-trois-sagesses.org/rss-articles.xml") -> {
+ assertEquals("Religion", feed.tag)
+ assertEquals("Les trois sagesses", feed.customTitle)
+ }
+
+ URL("http://www.avrildeperthuis.com/feed/") -> {
+ assertEquals("Amis", feed.tag)
+ assertEquals("avril de perthuis", feed.customTitle)
+ }
+
+ URL("http://www.fashioningtech.com/profiles/blog/feed?xn_auth=no") -> {
+ assertEquals("Actu Geek", feed.tag)
+ assertEquals("Everyone's Blog Posts - Fashioning Technology", feed.customTitle)
+ }
+
+ URL("http://feeds2.feedburner.com/ChartPorn") -> {
+ assertEquals("Graphs", feed.tag)
+ assertEquals("Chart Porn", feed.customTitle)
+ }
+
+ URL("http://www.mosqueedeparis.net/index.php?format=feed&type=atom") -> {
+ assertEquals("Religion", feed.tag)
+ assertEquals("Mosquee de Paris", feed.customTitle)
+ }
+
+ URL("http://sourceforge.net/projects/stuntrally/rss") -> {
+ assertEquals("Mainstream update", feed.tag)
+ assertEquals("Stunt Rally", feed.customTitle)
+ }
+
+ URL("http://www.mairie6.lyon.fr/cs/Satellite?Thematique=&TypeContenu=Actualite&pagename=RSSFeed&site=Mairie6") -> {
+ assertEquals("", feed.tag)
+ assertEquals("Actualités", feed.customTitle)
+ }
}
}
}
- }
@MediumTest
@Test
- fun testExportThenImport(): Unit = runBlocking {
- val fileUri = context.cacheDir.resolve("exporttest.opml").toUri()
- val feedIds = mutableSetOf()
- feedStore.saveFeed(
- Feed(
- title = "Ampersands are & the worst",
- url = URL("https://example.com/ampersands"),
- ),
- ).also { feedIds.add(it) }
- feedStore.saveFeed(
- Feed(
- title = "So are > brackets",
- url = URL("https://example.com/lt"),
- ),
- ).also { feedIds.add(it) }
- feedStore.saveFeed(
- Feed(
- title = "So are < brackets",
- url = URL("https://example.com/gt"),
- ),
- ).also { feedIds.add(it) }
-
- assertEquals(3, feedIds.size)
-
- val exportResult = exportOpml(di, fileUri)
-
- exportResult.leftOrNull()?.let { e ->
- throw e.throwable!!
- }
+ fun testExportThenImport(): Unit =
+ runBlocking {
+ val fileUri = context.cacheDir.resolve("exporttest.opml").toUri()
+ val feedIds = mutableSetOf()
+ feedStore.saveFeed(
+ Feed(
+ title = "Ampersands are & the worst",
+ url = URL("https://example.com/ampersands"),
+ ),
+ ).also { feedIds.add(it) }
+ feedStore.saveFeed(
+ Feed(
+ title = "So are > brackets",
+ url = URL("https://example.com/lt"),
+ ),
+ ).also { feedIds.add(it) }
+ feedStore.saveFeed(
+ Feed(
+ title = "So are < brackets",
+ url = URL("https://example.com/gt"),
+ ),
+ ).also { feedIds.add(it) }
+
+ assertEquals(3, feedIds.size)
+
+ val exportResult = exportOpml(di, fileUri)
+
+ exportResult.leftOrNull()?.let { e ->
+ throw e.throwable!!
+ }
- val opmlFeedList = OpmlFeedList()
- val parser = OpmlPullParser(opmlFeedList)
- val result = parser.parseFile(fileUri.path!!)
+ val opmlFeedList = OpmlFeedList()
+ val parser = OpmlPullParser(opmlFeedList)
+ val result = parser.parseFile(fileUri.path!!)
- result.leftOrNull()?.let { e ->
- throw e.throwable!!
- }
+ result.leftOrNull()?.let { e ->
+ throw e.throwable!!
+ }
- assertEquals(3, opmlFeedList.feeds.size)
- }
+ assertEquals(3, opmlFeedList.feeds.size)
+ }
@Test
@MediumTest
- fun importPlenaryProgramming(): Unit = runBlocking {
- // given
- val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("Programming.opml")!!
-
- // when
- val opmlFeedList = OpmlFeedList()
- val parser = OpmlPullParser(opmlFeedList)
- val result = parser.parseInputStreamWithFallback(opmlStream)
+ fun importPlenaryProgramming(): Unit =
+ runBlocking {
+ // given
+ val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("Programming.opml")!!
+
+ // when
+ val opmlFeedList = OpmlFeedList()
+ val parser = OpmlPullParser(opmlFeedList)
+ val result = parser.parseInputStreamWithFallback(opmlStream)
+
+ result.leftOrNull()?.let {
+ throw it.throwable!!
+ }
- result.leftOrNull()?.let {
- throw it.throwable!!
+ // then
+ assertEquals("Expecting feeds", 50, opmlFeedList.feeds.size)
}
- // then
- assertEquals("Expecting feeds", 50, opmlFeedList.feeds.size)
- }
-
@Test
@MediumTest
- fun rssGuardOPMLImports2() = runBlocking {
- // given
- val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("rssguard_2.opml")!!
-
- // when
- val parser = OpmlPullParser(opmlParserHandler)
- parser.parseInputStreamWithFallback(opmlStream)
-
- // then
- val feeds = db.feedDao().loadFeeds()
- val tags = db.feedDao().loadTags()
- assertEquals("Expecting 30 feeds", 30, feeds.size)
- assertEquals("Expecting 6 tags (incl empty)", 6, tags.size)
-
- feeds.forEach { feed ->
- when (feed.url) {
- URL("http://www.les-trois-sagesses.org/rss-articles.xml") -> {
- assertEquals("Religion", feed.tag)
- assertEquals("Les trois sagesses", feed.customTitle)
- }
-
- URL("http://www.avrildeperthuis.com/feed/") -> {
- assertEquals("Amis", feed.tag)
- assertEquals("avril de perthuis", feed.customTitle)
- }
-
- URL("http://www.fashioningtech.com/profiles/blog/feed?xn_auth=no") -> {
- assertEquals("Actu Geek", feed.tag)
- assertEquals("Everyone's Blog Posts - Fashioning Technology", feed.customTitle)
- }
-
- URL("http://feeds2.feedburner.com/ChartPorn") -> {
- assertEquals("Graphs", feed.tag)
- assertEquals("Chart Porn", feed.customTitle)
- }
-
- URL("http://www.mosqueedeparis.net/index.php?format=feed&type=atom") -> {
- assertEquals("Religion", feed.tag)
- assertEquals("Mosquee de Paris", feed.customTitle)
- }
-
- URL("http://sourceforge.net/projects/stuntrally/rss") -> {
- assertEquals("Mainstream update", feed.tag)
- assertEquals("Stunt Rally", feed.customTitle)
- }
-
- URL("http://www.mairie6.lyon.fr/cs/Satellite?Thematique=&TypeContenu=Actualite&pagename=RSSFeed&site=Mairie6") -> {
- assertEquals("", feed.tag)
- assertEquals("Actualités", feed.customTitle)
+ fun rssGuardOPMLImports2() =
+ runBlocking {
+ // given
+ val opmlStream = this@OPMLTest.javaClass.getResourceAsStream("rssguard_2.opml")!!
+
+ // when
+ val parser = OpmlPullParser(opmlParserHandler)
+ parser.parseInputStreamWithFallback(opmlStream)
+
+ // then
+ val feeds = db.feedDao().loadFeeds()
+ val tags = db.feedDao().loadTags()
+ assertEquals("Expecting 30 feeds", 30, feeds.size)
+ assertEquals("Expecting 6 tags (incl empty)", 6, tags.size)
+
+ feeds.forEach { feed ->
+ when (feed.url) {
+ URL("http://www.les-trois-sagesses.org/rss-articles.xml") -> {
+ assertEquals("Religion", feed.tag)
+ assertEquals("Les trois sagesses", feed.customTitle)
+ }
+
+ URL("http://www.avrildeperthuis.com/feed/") -> {
+ assertEquals("Amis", feed.tag)
+ assertEquals("avril de perthuis", feed.customTitle)
+ }
+
+ URL("http://www.fashioningtech.com/profiles/blog/feed?xn_auth=no") -> {
+ assertEquals("Actu Geek", feed.tag)
+ assertEquals("Everyone's Blog Posts - Fashioning Technology", feed.customTitle)
+ }
+
+ URL("http://feeds2.feedburner.com/ChartPorn") -> {
+ assertEquals("Graphs", feed.tag)
+ assertEquals("Chart Porn", feed.customTitle)
+ }
+
+ URL("http://www.mosqueedeparis.net/index.php?format=feed&type=atom") -> {
+ assertEquals("Religion", feed.tag)
+ assertEquals("Mosquee de Paris", feed.customTitle)
+ }
+
+ URL("http://sourceforge.net/projects/stuntrally/rss") -> {
+ assertEquals("Mainstream update", feed.tag)
+ assertEquals("Stunt Rally", feed.customTitle)
+ }
+
+ URL("http://www.mairie6.lyon.fr/cs/Satellite?Thematique=&TypeContenu=Actualite&pagename=RSSFeed&site=Mairie6") -> {
+ assertEquals("", feed.tag)
+ assertEquals("Actualités", feed.customTitle)
+ }
}
}
}
- }
companion object {
private val BLOCKED_PATTERNS: List = listOf("foo")
private val ALL_SETTINGS_WITH_VALUES: Map =
UserSettings.values().associate { userSetting ->
- userSetting.key to when (userSetting) {
- UserSettings.SETTING_OPEN_LINKS_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB
- UserSettings.SETTING_ADDED_FEEDER_NEWS -> "true"
- UserSettings.SETTING_THEME -> "night"
- UserSettings.SETTING_DARK_THEME -> "dark"
- UserSettings.SETTING_DYNAMIC_THEME -> "false"
- UserSettings.SETTING_SORT -> "oldest_first"
- UserSettings.SETTING_SHOW_FAB -> "false"
- UserSettings.SETTING_FEED_ITEM_STYLE -> "SUPER_COMPACT"
- UserSettings.SETTING_SWIPE_AS_READ -> "DISABLED"
- UserSettings.SETTING_SYNC_ON_RESUME -> "true"
- UserSettings.SETTING_SYNC_ONLY_WIFI -> "false"
- UserSettings.SETTING_IMG_ONLY_WIFI -> "true"
- UserSettings.SETTING_IMG_SHOW_THUMBNAILS -> "false"
- UserSettings.SETTING_DEFAULT_OPEN_ITEM_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB
- UserSettings.SETTING_TEXT_SCALE -> "1.6"
- UserSettings.SETTING_IS_MARK_AS_READ_ON_SCROLL -> "true"
- UserSettings.SETTING_READALOUD_USE_DETECT_LANGUAGE -> "true"
- UserSettings.SETTING_SYNC_ONLY_CHARGING -> "true"
- UserSettings.SETTING_SYNC_FREQ -> "720"
- UserSettings.SETTING_MAX_LINES -> "6"
- UserSettings.SETTINGS_FILTER_SAVED -> "true"
- UserSettings.SETTINGS_FILTER_RECENTLY_READ -> "true"
- UserSettings.SETTINGS_FILTER_READ -> "false"
- UserSettings.SETTINGS_LIST_SHOW_ONLY_TITLES -> "true"
- UserSettings.SETTING_OPEN_ADJACENT -> "true"
- }
+ userSetting.key to
+ when (userSetting) {
+ UserSettings.SETTING_OPEN_LINKS_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB
+ UserSettings.SETTING_ADDED_FEEDER_NEWS -> "true"
+ UserSettings.SETTING_THEME -> "night"
+ UserSettings.SETTING_DARK_THEME -> "dark"
+ UserSettings.SETTING_DYNAMIC_THEME -> "false"
+ UserSettings.SETTING_SORT -> "oldest_first"
+ UserSettings.SETTING_SHOW_FAB -> "false"
+ UserSettings.SETTING_FEED_ITEM_STYLE -> "SUPER_COMPACT"
+ UserSettings.SETTING_SWIPE_AS_READ -> "DISABLED"
+ UserSettings.SETTING_SYNC_ON_RESUME -> "true"
+ UserSettings.SETTING_SYNC_ONLY_WIFI -> "false"
+ UserSettings.SETTING_IMG_ONLY_WIFI -> "true"
+ UserSettings.SETTING_IMG_SHOW_THUMBNAILS -> "false"
+ UserSettings.SETTING_DEFAULT_OPEN_ITEM_WITH -> PREF_VAL_OPEN_WITH_CUSTOM_TAB
+ UserSettings.SETTING_TEXT_SCALE -> "1.6"
+ UserSettings.SETTING_IS_MARK_AS_READ_ON_SCROLL -> "true"
+ UserSettings.SETTING_READALOUD_USE_DETECT_LANGUAGE -> "true"
+ UserSettings.SETTING_SYNC_ONLY_CHARGING -> "true"
+ UserSettings.SETTING_SYNC_FREQ -> "720"
+ UserSettings.SETTING_MAX_LINES -> "6"
+ UserSettings.SETTINGS_FILTER_SAVED -> "true"
+ UserSettings.SETTINGS_FILTER_RECENTLY_READ -> "true"
+ UserSettings.SETTINGS_FILTER_READ -> "false"
+ UserSettings.SETTINGS_LIST_SHOW_ONLY_TITLES -> "true"
+ UserSettings.SETTING_OPEN_ADJACENT -> "true"
+ }
}
}
}
@@ -731,7 +754,8 @@ suspend fun OpmlPullParser.parseFile(path: String): Either {
}
}
-private val sampleFile: List = """
+private val sampleFile: List =
+ """
@@ -784,18 +808,22 @@ private val sampleFile: List = """
-""".trimIndent()
- .split("\n")
+ """.trimIndent()
+ .split("\n")
class OpmlFeedList : OPMLParserHandler {
val feeds = mutableMapOf()
val settings = mutableMapOf()
val blockList = mutableListOf()
+
override suspend fun saveFeed(feed: Feed) {
feeds[feed.url] = feed
}
- override suspend fun saveSetting(key: String, value: String) {
+ override suspend fun saveSetting(
+ key: String,
+ value: String,
+ ) {
settings.put(key, value)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/Helpers.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/Helpers.kt
index ec7d091724..9f631bc660 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/Helpers.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/Helpers.kt
@@ -10,7 +10,7 @@ suspend fun whileNotEq(
other: Any?,
timeoutMillis: Long = 500,
sleepMillis: Long = 50,
- body: (suspend () -> T)
+ body: (suspend () -> T),
): T =
withTimeout(timeoutMillis) {
var item = body.invoke()
@@ -28,7 +28,7 @@ suspend fun whileEq(
other: Any?,
timeoutMillis: Long = 500,
sleepMillis: Long = 50,
- body: (suspend () -> T)
+ body: (suspend () -> T),
): T =
withTimeout(timeoutMillis) {
var item = body.invoke()
@@ -46,13 +46,13 @@ suspend fun untilNotEq(
other: Any?,
timeoutMillis: Long = 500,
sleepMillis: Long = 50,
- body: (suspend () -> T)
+ body: (suspend () -> T),
): T =
whileEq(
other = other,
timeoutMillis = timeoutMillis,
sleepMillis = sleepMillis,
- body = body
+ body = body,
)
/**
@@ -62,11 +62,11 @@ suspend fun untilEq(
other: Any?,
timeoutMillis: Long = 500,
sleepMillis: Long = 50,
- body: (suspend () -> T)
+ body: (suspend () -> T),
): T =
whileNotEq(
other = other,
timeoutMillis = timeoutMillis,
sleepMillis = sleepMillis,
- body = body
+ body = body,
)
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/NotificationClearingTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/NotificationClearingTest.kt
index 91246ca590..3be6899cd6 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/NotificationClearingTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/NotificationClearingTest.kt
@@ -8,7 +8,6 @@ import com.nononsenseapps.feeder.db.room.FeedItemWithFeed
import com.nononsenseapps.feeder.model.RssNotificationBroadcastReceiver
import com.nononsenseapps.feeder.model.getDeleteIntent
import com.nononsenseapps.feeder.model.notify
-import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@@ -19,84 +18,95 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import java.net.URL
// This can be flaky
@RunWith(AndroidJUnit4::class)
@Ignore
class NotificationClearingTest {
private val receiver: RssNotificationBroadcastReceiver = RssNotificationBroadcastReceiver()
+
@get:Rule
val testDb = TestDatabaseRule(getApplicationContext())
@Test
- fun clearingNotificationMarksAsNotified() = runBlocking {
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- title = "testFeed",
- url = URL("http://testfeed"),
- tag = "testTag"
- )
- )
+ fun clearingNotificationMarksAsNotified() =
+ runBlocking {
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ title = "testFeed",
+ url = URL("http://testfeed"),
+ tag = "testTag",
+ ),
+ )
- val item1Id = testDb.db.feedItemDao().insertFeedItem(
- FeedItem(
- feedId = feedId,
- guid = "item1",
- title = "item1",
- notified = false
- )
- )
+ val item1Id =
+ testDb.db.feedItemDao().insertFeedItem(
+ FeedItem(
+ feedId = feedId,
+ guid = "item1",
+ title = "item1",
+ notified = false,
+ ),
+ )
- val di = getDeleteIntent(
- getApplicationContext(),
- FeedItemWithFeed(
- id = item1Id, feedId = feedId, guid = "item1", title = "item1"
- )
- )
+ val di =
+ getDeleteIntent(
+ getApplicationContext(),
+ FeedItemWithFeed(
+ id = item1Id,
+ feedId = feedId,
+ guid = "item1",
+ title = "item1",
+ ),
+ )
- runBlocking {
- // Receiver runs on main thread
- withContext(Dispatchers.Main) {
- receiver.onReceive(getApplicationContext(), di)
- }
+ runBlocking {
+ // Receiver runs on main thread
+ withContext(Dispatchers.Main) {
+ receiver.onReceive(getApplicationContext(), di)
+ }
- delay(50)
+ delay(50)
- val item = testDb.db.feedItemDao().loadFeedItem(guid = "item1", feedId = feedId)
- assertTrue(item!!.notified)
+ val item = testDb.db.feedItemDao().loadFeedItem(guid = "item1", feedId = feedId)
+ assertTrue(item!!.notified)
+ }
}
- }
@Test
- fun notifyWorksOnMainThread() = runBlocking {
- val feedId = testDb.db.feedDao().insertFeed(
- Feed(
- title = "testFeed",
- url = URL("http://testfeed"),
- tag = "testTag"
- )
- )
+ fun notifyWorksOnMainThread() =
+ runBlocking {
+ val feedId =
+ testDb.db.feedDao().insertFeed(
+ Feed(
+ title = "testFeed",
+ url = URL("http://testfeed"),
+ tag = "testTag",
+ ),
+ )
- testDb.db.feedItemDao().insertFeedItem(
- FeedItem(
- feedId = feedId,
- guid = "item1",
- title = "item1",
- notified = false
+ testDb.db.feedItemDao().insertFeedItem(
+ FeedItem(
+ feedId = feedId,
+ guid = "item1",
+ title = "item1",
+ notified = false,
+ ),
)
- )
- runBlocking {
- // Try to notify on main thread
- withContext(Dispatchers.Main) {
- notify(getApplicationContext())
- }
+ runBlocking {
+ // Try to notify on main thread
+ withContext(Dispatchers.Main) {
+ notify(getApplicationContext())
+ }
- delay(50)
+ delay(50)
- // Only care that the above call didn't crash because we ran on the main thread
- val item = testDb.db.feedItemDao().loadFeedItem(guid = "item1", feedId = feedId)
- assertFalse(item!!.notified)
+ // Only care that the above call didn't crash because we ran on the main thread
+ val item = testDb.db.feedItemDao().loadFeedItem(guid = "item1", feedId = feedId)
+ assertFalse(item!!.notified)
+ }
}
- }
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/TestDatabaseRule.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/TestDatabaseRule.kt
index 5e237f84eb..c3f1eb32d0 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/TestDatabaseRule.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/TestDatabaseRule.kt
@@ -9,13 +9,14 @@ class TestDatabaseRule(val context: Context) : ExternalResource() {
lateinit var db: AppDatabase
override fun before() {
- db = Room.inMemoryDatabaseBuilder(
- context,
- AppDatabase::class.java
- ).build().also {
- // Ensure all classes use test database
- AppDatabase.setInstance(it)
- }
+ db =
+ Room.inMemoryDatabaseBuilder(
+ context,
+ AppDatabase::class.java,
+ ).build().also {
+ // Ensure all classes use test database
+ AppDatabase.setInstance(it)
+ }
}
override fun after() {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/AddFeedFromShareActivityTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/AddFeedFromShareActivityTest.kt
index 71b8ae0205..539300f9f7 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/AddFeedFromShareActivityTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/AddFeedFromShareActivityTest.kt
@@ -6,18 +6,19 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nononsenseapps.feeder.ui.AddFeedFromShareActivity
-import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class AddFeedFromShareActivityTest {
@Test
fun activityShouldStart() {
- val intent = Intent(
- ApplicationProvider.getApplicationContext(),
- AddFeedFromShareActivity::class.java
- )
+ val intent =
+ Intent(
+ ApplicationProvider.getApplicationContext(),
+ AddFeedFromShareActivity::class.java,
+ )
launchActivity(intent).use { scenario ->
assertEquals(Lifecycle.State.RESUMED, scenario.state)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/MainActivityTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/MainActivityTest.kt
index 0f5361d6a1..f45ae60d34 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/MainActivityTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/MainActivityTest.kt
@@ -6,18 +6,19 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nononsenseapps.feeder.ui.MainActivity
-import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@Test
fun activityShouldStart() {
- val intent = Intent(
- ApplicationProvider.getApplicationContext(),
- MainActivity::class.java
- )
+ val intent =
+ Intent(
+ ApplicationProvider.getApplicationContext(),
+ MainActivity::class.java,
+ )
launchActivity(intent).use { scenario ->
assertEquals(Lifecycle.State.RESUMED, scenario.state)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/ManageSettingsActivityTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/ManageSettingsActivityTest.kt
index 783c8758b9..b77681a738 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/ManageSettingsActivityTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/activity/ManageSettingsActivityTest.kt
@@ -6,18 +6,19 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nononsenseapps.feeder.ui.ManageSettingsActivity
-import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class ManageSettingsActivityTest {
@Test
fun activityShouldStart() {
- val intent = Intent(
- ApplicationProvider.getApplicationContext(),
- ManageSettingsActivity::class.java
- )
+ val intent =
+ Intent(
+ ApplicationProvider.getApplicationContext(),
+ ManageSettingsActivity::class.java,
+ )
launchActivity(intent).use { scenario ->
assertEquals(Lifecycle.State.RESUMED, scenario.state)
}
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/EndToEndTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/EndToEndTest.kt
index 2c29b696e4..d8215d3b63 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/EndToEndTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/EndToEndTest.kt
@@ -12,7 +12,6 @@ import org.kodein.di.compose.withDI
@Ignore
class EndToEndTest : BaseComposeTest {
-
@get:Rule
override val composeTestRule = createAndroidComposeRule()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/ReaderScreenScrollingTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/ReaderScreenScrollingTest.kt
index 4958faffd6..dbf460d7d3 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/ReaderScreenScrollingTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/ReaderScreenScrollingTest.kt
@@ -6,7 +6,6 @@ import org.junit.Rule
@Ignore
class ReaderScreenScrollingTest {
-
@get:Rule
val composeTestRule = createComposeRule()
// createAndroidComposeRule()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/StartingNavigationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/StartingNavigationTest.kt
index 361f1ce822..bd50f00345 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/StartingNavigationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/StartingNavigationTest.kt
@@ -4,16 +4,15 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import com.nononsenseapps.feeder.ui.MainActivity
import com.nononsenseapps.feeder.ui.compose.theme.FeederTheme
import com.nononsenseapps.feeder.ui.robots.feedScreen
-import kotlin.test.assertFalse
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.kodein.di.compose.withDI
+import kotlin.test.assertFalse
@Ignore
class StartingNavigationTest : BaseComposeTest {
-
@get:Rule
override val composeTestRule = createAndroidComposeRule()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/SyncSetupTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/SyncSetupTest.kt
index a3a6a826e1..4abaf59c8c 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/SyncSetupTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/SyncSetupTest.kt
@@ -16,7 +16,6 @@ import org.kodein.di.compose.withDI
@Ignore
class SyncSetupTest : BaseComposeTest {
-
@get:Rule
override val composeTestRule = createAndroidComposeRule()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/AddFeedDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/AddFeedDestinationTest.kt
index 6021f1ca2c..5d03f39818 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/AddFeedDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/AddFeedDestinationTest.kt
@@ -4,10 +4,10 @@ import androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class AddFeedDestinationTest {
@@ -23,7 +23,7 @@ class AddFeedDestinationTest {
fun addFeedHasCorrectRoute() {
assertEquals(
"add/feed/{feedUrl}?feedTitle={feedTitle}",
- AddFeedDestination.route
+ AddFeedDestination.route,
)
}
@@ -31,7 +31,7 @@ class AddFeedDestinationTest {
fun addFeedNavigateNoTitle() {
AddFeedDestination.navigate(
navController,
- "https://cowboyprogrammer.org"
+ "https://cowboyprogrammer.org",
)
verify {
@@ -44,7 +44,7 @@ class AddFeedDestinationTest {
AddFeedDestination.navigate(
navController,
"https://cowboyprogrammer.org",
- ""
+ "",
)
verify {
@@ -57,7 +57,7 @@ class AddFeedDestinationTest {
AddFeedDestination.navigate(
navController,
"https://cowboyprogrammer.org",
- " "
+ " ",
)
verify {
@@ -70,7 +70,7 @@ class AddFeedDestinationTest {
AddFeedDestination.navigate(
navController,
"https://cowboyprogrammer.org",
- "A feed"
+ "A feed",
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/ArticleDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/ArticleDestinationTest.kt
index 8478e71d91..3da0d61e27 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/ArticleDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/ArticleDestinationTest.kt
@@ -5,10 +5,10 @@ import com.nononsenseapps.feeder.util.DEEP_LINK_BASE_URI
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class ArticleDestinationTest {
@@ -24,7 +24,7 @@ class ArticleDestinationTest {
fun readerHasCorrectRoute() {
assertEquals(
"reader/{itemId}",
- ArticleDestination.route
+ ArticleDestination.route,
)
}
@@ -32,9 +32,9 @@ class ArticleDestinationTest {
fun readerHasCorrectDeeplinks() {
assertEquals(
listOf(
- "$DEEP_LINK_BASE_URI/article/{itemId}"
+ "$DEEP_LINK_BASE_URI/article/{itemId}",
),
- ArticleDestination.deepLinks.map { it.uriPattern }
+ ArticleDestination.deepLinks.map { it.uriPattern },
)
}
@@ -42,7 +42,7 @@ class ArticleDestinationTest {
fun readerNavigate() {
ArticleDestination.navigate(
navController,
- 55L
+ 55L,
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/EditFeedDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/EditFeedDestinationTest.kt
index b2dbd5cf77..e0ceb0c439 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/EditFeedDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/EditFeedDestinationTest.kt
@@ -4,10 +4,10 @@ import androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class EditFeedDestinationTest {
@@ -23,7 +23,7 @@ class EditFeedDestinationTest {
fun editFeedHasCorrectRoute() {
assertEquals(
"edit/feed/{feedId}",
- EditFeedDestination.route
+ EditFeedDestination.route,
)
}
@@ -31,7 +31,7 @@ class EditFeedDestinationTest {
fun editFeedNavigate() {
EditFeedDestination.navigate(
navController,
- 99L
+ 99L,
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/FeedDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/FeedDestinationTest.kt
index 2565f1fbe0..c9ffd62cf7 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/FeedDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/FeedDestinationTest.kt
@@ -6,10 +6,10 @@ import com.nononsenseapps.feeder.util.DEEP_LINK_BASE_URI
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class FeedDestinationTest {
@@ -25,7 +25,7 @@ class FeedDestinationTest {
fun feedHasCorrectRoute() {
assertEquals(
"feed?id={id}&tag={tag}",
- FeedDestination.route
+ FeedDestination.route,
)
}
@@ -33,16 +33,16 @@ class FeedDestinationTest {
fun feedHasCorrectDeeplinks() {
assertEquals(
listOf(
- "$DEEP_LINK_BASE_URI/feed?id={id}&tag={tag}"
+ "$DEEP_LINK_BASE_URI/feed?id={id}&tag={tag}",
),
- FeedDestination.deepLinks.map { it.uriPattern }
+ FeedDestination.deepLinks.map { it.uriPattern },
)
}
@Test
fun feedNavigateDefaults() {
FeedDestination.navigate(
- navController
+ navController,
)
verify {
@@ -54,7 +54,7 @@ class FeedDestinationTest {
fun feedNavigateId() {
FeedDestination.navigate(
navController,
- feedId = 6L
+ feedId = 6L,
)
verify {
@@ -66,7 +66,7 @@ class FeedDestinationTest {
fun feedNavigateTag() {
FeedDestination.navigate(
navController,
- tag = "foo bar+cop"
+ tag = "foo bar+cop",
)
verify {
@@ -79,7 +79,7 @@ class FeedDestinationTest {
FeedDestination.navigate(
navController,
feedId = ID_ALL_FEEDS,
- tag = "foo bar+cop"
+ tag = "foo bar+cop",
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SearchFeedDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SearchFeedDestinationTest.kt
index 62d32c3782..bc3f5a0a08 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SearchFeedDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SearchFeedDestinationTest.kt
@@ -4,10 +4,10 @@ import androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class SearchFeedDestinationTest {
@@ -23,14 +23,14 @@ class SearchFeedDestinationTest {
fun searchFeedHasCorrectRoute() {
assertEquals(
"search/feed?feedUrl={feedUrl}",
- SearchFeedDestination.route
+ SearchFeedDestination.route,
)
}
@Test
fun searchFeedNavigateDefaults() {
SearchFeedDestination.navigate(
- navController
+ navController,
)
verify {
@@ -42,7 +42,7 @@ class SearchFeedDestinationTest {
fun searchFeedNavigateFeed() {
SearchFeedDestination.navigate(
navController,
- "https://cowboyprogrammer.org"
+ "https://cowboyprogrammer.org",
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SettingsDestinationTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SettingsDestinationTest.kt
index d986c82404..076055ac2b 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SettingsDestinationTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/compose/navigation/SettingsDestinationTest.kt
@@ -4,10 +4,10 @@ import androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
+import kotlin.test.assertEquals
@Ignore
class SettingsDestinationTest {
@@ -23,14 +23,14 @@ class SettingsDestinationTest {
fun settingsHasCorrectRoute() {
assertEquals(
"settings",
- SettingsDestination.route
+ SettingsDestination.route,
)
}
@Test
fun settingsNavigateDefaults() {
SettingsDestination.navigate(
- navController
+ navController,
)
verify {
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/robots/FeedScreenRobot.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/robots/FeedScreenRobot.kt
index d082388f72..1b3e73712a 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/ui/robots/FeedScreenRobot.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/ui/robots/FeedScreenRobot.kt
@@ -8,8 +8,7 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import com.nononsenseapps.feeder.ui.compose.BaseComposeTest
-fun BaseComposeTest.feedScreen(block: FeedScreenRobot.() -> Unit) =
- FeedScreenRobot(composeTestRule).apply { block() }
+fun BaseComposeTest.feedScreen(block: FeedScreenRobot.() -> Unit) = FeedScreenRobot(composeTestRule).apply { block() }
class FeedScreenRobot(
private val testRule: ComposeTestRule,
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt
index 956b5639ec..2b7a7130c9 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/util/BugReportKTest.kt
@@ -8,15 +8,14 @@ import android.content.Intent.EXTRA_TEXT
import android.net.Uri
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@MediumTest
class BugReportKTest {
-
@Test
fun bugIntentIsCorrect() {
val intent = emailBugReportIntent()
diff --git a/app/src/androidTest/java/com/nononsenseapps/feeder/util/IcoDecoderTest.kt b/app/src/androidTest/java/com/nononsenseapps/feeder/util/IcoDecoderTest.kt
index 3de5e70d6c..c1a779ea4b 100644
--- a/app/src/androidTest/java/com/nononsenseapps/feeder/util/IcoDecoderTest.kt
+++ b/app/src/androidTest/java/com/nononsenseapps/feeder/util/IcoDecoderTest.kt
@@ -9,12 +9,12 @@ import coil.decode.ImageSource
import coil.fetch.SourceResult
import coil.request.Options
import com.danielrampelt.coil.ico.IcoDecoder
-import kotlin.test.assertNotNull
import kotlinx.coroutines.runBlocking
import okio.buffer
import okio.source
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -23,45 +23,51 @@ class IcoDecoderTest {
@Test
fun testPngFavicon() {
- val decoder = factory.create(
- pngIco,
- Options(ApplicationProvider.getApplicationContext()),
- ImageLoader(ApplicationProvider.getApplicationContext()),
- )
+ val decoder =
+ factory.create(
+ pngIco,
+ Options(ApplicationProvider.getApplicationContext()),
+ ImageLoader(ApplicationProvider.getApplicationContext()),
+ )
assertNotNull(decoder)
- val result = runBlocking {
- decoder.decode()
- }
+ val result =
+ runBlocking {
+ decoder.decode()
+ }
assertNotNull(result)
}
@Test
fun testGitlabIco() {
- val decoder = factory.create(
- pngIco,
- Options(ApplicationProvider.getApplicationContext()),
- ImageLoader(ApplicationProvider.getApplicationContext()),
- )
+ val decoder =
+ factory.create(
+ pngIco,
+ Options(ApplicationProvider.getApplicationContext()),
+ ImageLoader(ApplicationProvider.getApplicationContext()),
+ )
assertNotNull(decoder)
- val result = runBlocking {
- decoder.decode()
- }
+ val result =
+ runBlocking {
+ decoder.decode()
+ }
assertNotNull(result)
}
companion object {
private val gitlabIco: SourceResult
get() {
- val buf = Companion::class.java.getResourceAsStream("gitlab.ico")!!
- .source()
- .buffer()
+ val buf =
+ Companion::class.java.getResourceAsStream("gitlab.ico")!!
+ .source()
+ .buffer()
- val imageSource = ImageSource(
- source = buf,
- context = ApplicationProvider.getApplicationContext(),
- )
+ val imageSource =
+ ImageSource(
+ source = buf,
+ context = ApplicationProvider.getApplicationContext(),
+ )
return SourceResult(
source = imageSource,
@@ -72,14 +78,16 @@ class IcoDecoderTest {
private val pngIco: SourceResult
get() {
- val buf = Companion::class.java.getResourceAsStream("png.ico")!!
- .source()
- .buffer()
+ val buf =
+ Companion::class.java.getResourceAsStream("png.ico")!!
+ .source()
+ .buffer()
- val imageSource = ImageSource(
- source = buf,
- context = ApplicationProvider.getApplicationContext(),
- )
+ val imageSource =
+ ImageSource(
+ source = buf,
+ context = ApplicationProvider.getApplicationContext(),
+ )
return SourceResult(
source = imageSource,
diff --git a/app/src/main/java/com/danielrampelt/coil/ico/IcoDecoder.kt b/app/src/main/java/com/danielrampelt/coil/ico/IcoDecoder.kt
index 569a0c4694..f02b98fefd 100644
--- a/app/src/main/java/com/danielrampelt/coil/ico/IcoDecoder.kt
+++ b/app/src/main/java/com/danielrampelt/coil/ico/IcoDecoder.kt
@@ -38,9 +38,7 @@ class IcoDecoder(
}
}
- private fun BitmapFactory.Options.decode(
- bufferedSource: BufferedSource,
- ): DecodeResult {
+ private fun BitmapFactory.Options.decode(bufferedSource: BufferedSource): DecodeResult {
// Read the image's dimensions.
// inJustDecodeBounds = true
// val peek = bufferedSource.peek()
@@ -57,9 +55,10 @@ class IcoDecoder(
inPremultiplied = options.premultipliedAlpha
// Decode the bitmap.
- val outBitmap: Bitmap? = bufferedSource.use {
- BitmapFactory.decodeStream(it.inputStream(), null, this)
- }
+ val outBitmap: Bitmap? =
+ bufferedSource.use {
+ BitmapFactory.decodeStream(it.inputStream(), null, this)
+ }
if (outBitmap == null) {
Log.w(
diff --git a/app/src/main/java/com/nononsenseapps/feeder/FeederApplication.kt b/app/src/main/java/com/nononsenseapps/feeder/FeederApplication.kt
index 79e201119b..950cd4c802 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/FeederApplication.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/FeederApplication.kt
@@ -39,9 +39,6 @@ import com.nononsenseapps.feeder.util.currentlyUnmetered
import com.nononsenseapps.feeder.util.filePathProvider
import com.nononsenseapps.feeder.util.logDebug
import com.nononsenseapps.jsonfeed.cachingHttpClient
-import java.io.File
-import java.security.Security
-import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.withContext
@@ -55,6 +52,9 @@ import org.kodein.di.bind
import org.kodein.di.direct
import org.kodein.di.instance
import org.kodein.di.singleton
+import java.io.File
+import java.security.Security
+import java.util.concurrent.TimeUnit
class FeederApplication : Application(), DIAware, ImageLoaderFactory {
private val applicationCoroutineScope = ApplicationCoroutineScope()
@@ -63,9 +63,10 @@ class FeederApplication : Application(), DIAware, ImageLoaderFactory {
override val di by DI.lazy {
// import(androidXModule(this@FeederApplication))
- bind() with singleton {
- filePathProvider(cacheDir = cacheDir, filesDir = filesDir)
- }
+ bind() with
+ singleton {
+ filePathProvider(cacheDir = cacheDir, filesDir = filesDir)
+ }
bind() with singleton { this@FeederApplication }
bind() with singleton { AppDatabase.getInstance(this@FeederApplication) }
bind() with singleton { instance().feedDao() }
@@ -83,96 +84,103 @@ class FeederApplication : Application(), DIAware, ImageLoaderFactory {
bind() with singleton { WorkManager.getInstance(this@FeederApplication) }
bind() with singleton { contentResolver }
- bind() with singleton {
- object : ToastMaker {
- override suspend fun makeToast(text: String) = withContext(Dispatchers.Main) {
- Toast.makeText(this@FeederApplication, text, Toast.LENGTH_SHORT).show()
- }
+ bind() with
+ singleton {
+ object : ToastMaker {
+ override suspend fun makeToast(text: String) =
+ withContext(Dispatchers.Main) {
+ Toast.makeText(this@FeederApplication, text, Toast.LENGTH_SHORT).show()
+ }
- override suspend fun makeToast(resId: Int) = withContext(Dispatchers.Main) {
- Toast.makeText(this@FeederApplication, resId, Toast.LENGTH_SHORT).show()
+ override suspend fun makeToast(resId: Int) =
+ withContext(Dispatchers.Main) {
+ Toast.makeText(this@FeederApplication, resId, Toast.LENGTH_SHORT).show()
+ }
}
}
- }
bind() with singleton { NotificationManagerCompat.from(this@FeederApplication) }
- bind() with singleton {
- PreferenceManager.getDefaultSharedPreferences(
- this@FeederApplication,
- )
- }
+ bind() with
+ singleton {
+ PreferenceManager.getDefaultSharedPreferences(
+ this@FeederApplication,
+ )
+ }
- bind() with singleton {
- val filePathProvider = instance()
- cachingHttpClient(
- cacheDirectory = (filePathProvider.httpCacheDir),
- ) {
- addNetworkInterceptor(UserAgentInterceptor)
- if (BuildConfig.DEBUG) {
- addInterceptor { chain ->
- val request = chain.request()
- logDebug(
- "FEEDER",
- "Request ${request.url} headers [${request.headers}]",
- )
-
- chain.proceed(request).also {
+ bind() with
+ singleton {
+ val filePathProvider = instance()
+ cachingHttpClient(
+ cacheDirectory = (filePathProvider.httpCacheDir),
+ ) {
+ addNetworkInterceptor(UserAgentInterceptor)
+ if (BuildConfig.DEBUG) {
+ addInterceptor { chain ->
+ val request = chain.request()
logDebug(
"FEEDER",
- "Response ${it.request.url} code ${it.networkResponse?.code} cached ${it.cacheResponse != null}",
+ "Request ${request.url} headers [${request.headers}]",
)
+
+ chain.proceed(request).also {
+ logDebug(
+ "FEEDER",
+ "Response ${it.request.url} code ${it.networkResponse?.code} cached ${it.cacheResponse != null}",
+ )
+ }
}
}
}
}
- }
- bind() with singleton {
- val filePathProvider = instance()
- val repository = instance()
- val okHttpClient = instance()
- .newBuilder()
- // This is not used by Coil but no need to risk evicting the real cache
- .cache(Cache(filePathProvider.cacheDir.resolve("dummy_img"), 1024L))
- .addInterceptor { chain ->
- chain.proceed(
- when (!repository.loadImageOnlyOnWifi.value || currentlyUnmetered(this@FeederApplication)) {
- true -> chain.request()
- false -> {
- // Forces only cached responses to be used - if no cache then 504 is thrown
- chain.request().newBuilder()
- .cacheControl(
- CacheControl.Builder()
- .onlyIfCached()
- .maxStale(Int.MAX_VALUE, TimeUnit.SECONDS)
- .maxAge(Int.MAX_VALUE, TimeUnit.SECONDS)
- .build(),
- )
- .build()
- }
- },
+ bind() with
+ singleton {
+ val filePathProvider = instance()
+ val repository = instance()
+ val okHttpClient =
+ instance()
+ .newBuilder()
+ // This is not used by Coil but no need to risk evicting the real cache
+ .cache(Cache(filePathProvider.cacheDir.resolve("dummy_img"), 1024L))
+ .addInterceptor { chain ->
+ chain.proceed(
+ when (!repository.loadImageOnlyOnWifi.value || currentlyUnmetered(this@FeederApplication)) {
+ true -> chain.request()
+ false -> {
+ // Forces only cached responses to be used - if no cache then 504 is thrown
+ chain.request().newBuilder()
+ .cacheControl(
+ CacheControl.Builder()
+ .onlyIfCached()
+ .maxStale(Int.MAX_VALUE, TimeUnit.SECONDS)
+ .maxAge(Int.MAX_VALUE, TimeUnit.SECONDS)
+ .build(),
+ )
+ .build()
+ }
+ },
+ )
+ }
+ .build()
+
+ ImageLoader.Builder(instance())
+ .okHttpClient(okHttpClient = okHttpClient)
+ .diskCache(
+ DiskCache.Builder()
+ .directory(filePathProvider.imageCacheDir)
+ .maxSizeBytes(250L * 1024 * 1024)
+ .build(),
)
- }
- .build()
-
- ImageLoader.Builder(instance())
- .okHttpClient(okHttpClient = okHttpClient)
- .diskCache(
- DiskCache.Builder()
- .directory(filePathProvider.imageCacheDir)
- .maxSizeBytes(250L * 1024 * 1024)
- .build(),
- )
- .components {
- add(TooLargeImageInterceptor())
- add(SvgDecoder.Factory())
- if (SDK_INT >= 28) {
- add(ImageDecoderDecoder.Factory())
- } else {
- add(GifDecoder.Factory())
+ .components {
+ add(TooLargeImageInterceptor())
+ add(SvgDecoder.Factory())
+ if (SDK_INT >= 28) {
+ add(ImageDecoderDecoder.Factory())
+ } else {
+ add(GifDecoder.Factory())
+ }
+ add(IcoDecoder.Factory(this@FeederApplication))
}
- add(IcoDecoder.Factory(this@FeederApplication))
- }
- .build()
- }
+ .build()
+ }
bind() with instance(applicationCoroutineScope)
import(networkModule)
bind() with instance(ttsStateHolder)
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedItemStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedItemStore.kt
index c390d20079..ee4e650589 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedItemStore.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedItemStore.kt
@@ -20,13 +20,6 @@ import com.nononsenseapps.feeder.model.PreviewItem
import com.nononsenseapps.feeder.model.previewColumns
import com.nononsenseapps.feeder.ui.compose.feed.FeedListItem
import com.nononsenseapps.feeder.ui.compose.feedarticle.FeedListFilter
-import java.net.URL
-import java.time.Instant
-import java.time.LocalDate
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
-import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -34,6 +27,13 @@ import kotlinx.coroutines.withContext
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.net.URL
+import java.time.Instant
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.time.format.FormatStyle
+import java.util.Locale
class FeedItemStore(override val di: DI) : DIAware {
private val dao: FeedItemDao by instance()
@@ -67,10 +67,11 @@ class FeedItemStore(override val di: DI) : DIAware {
filter: FeedListFilter,
): Flow> =
Pager(
- config = PagingConfig(
- pageSize = PAGE_SIZE,
- enablePlaceholders = false,
- ),
+ config =
+ PagingConfig(
+ pageSize = PAGE_SIZE,
+ enablePlaceholders = false,
+ ),
) {
val queryString = StringBuilder()
val args = mutableListOf()
@@ -209,14 +210,20 @@ class FeedItemStore(override val di: DI) : DIAware {
dao.markAsRead(itemIds)
}
- suspend fun markAsReadAndNotified(itemId: Long, readTime: Instant = Instant.now()) {
+ suspend fun markAsReadAndNotified(
+ itemId: Long,
+ readTime: Instant = Instant.now(),
+ ) {
dao.markAsReadAndNotified(
id = itemId,
readTime = readTime.coerceAtLeast(Instant.EPOCH),
)
}
- suspend fun markAsReadAndNotifiedAndOverwriteReadTime(itemId: Long, readTime: Instant) {
+ suspend fun markAsReadAndNotifiedAndOverwriteReadTime(
+ itemId: Long,
+ readTime: Instant,
+ ) {
dao.markAsReadAndNotifiedAndOverwriteReadTime(
id = itemId,
readTime = readTime.coerceAtLeast(Instant.EPOCH),
@@ -227,7 +234,10 @@ class FeedItemStore(override val di: DI) : DIAware {
dao.markAsUnread(itemId)
}
- suspend fun setBookmarked(itemId: Long, bookmarked: Boolean) {
+ suspend fun setBookmarked(
+ itemId: Long,
+ bookmarked: Boolean,
+ ) {
dao.setBookmarked(itemId, bookmarked)
}
@@ -239,7 +249,10 @@ class FeedItemStore(override val di: DI) : DIAware {
return dao.loadFeedItemFlow(itemId)
}
- suspend fun getFeedItemId(feedUrl: URL, articleGuid: String): Long? {
+ suspend fun getFeedItemId(
+ feedUrl: URL,
+ articleGuid: String,
+ ): Long? {
return dao.getItemWith(feedUrl = feedUrl, articleGuid = articleGuid)
}
@@ -266,15 +279,16 @@ class FeedItemStore(override val di: DI) : DIAware {
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow> =
dao.getFeedsItemsWithDefaultFullTextNeedingDownload()
- suspend fun markAsFullTextDownloaded(feedItemId: Long) =
- dao.markAsFullTextDownloaded(feedItemId)
+ suspend fun markAsFullTextDownloaded(feedItemId: Long) = dao.markAsFullTextDownloaded(feedItemId)
fun getFeedItemsNeedingNotifying(): Flow> {
return dao.getFeedItemsNeedingNotifying()
}
- suspend fun loadFeedItem(guid: String, feedId: Long): FeedItem? =
- dao.loadFeedItem(guid = guid, feedId = feedId)
+ suspend fun loadFeedItem(
+ guid: String,
+ feedId: Long,
+ ): FeedItem? = dao.loadFeedItem(guid = guid, feedId = feedId)
suspend fun upsertFeedItems(
itemsWithText: List>,
@@ -283,14 +297,19 @@ class FeedItemStore(override val di: DI) : DIAware {
dao.upsertFeedItems(itemsWithText = itemsWithText, block = block)
}
- suspend fun getItemsToBeCleanedFromFeed(feedId: Long, keepCount: Int) =
- dao.getItemsToBeCleanedFromFeed(feedId = feedId, keepCount = keepCount)
+ suspend fun getItemsToBeCleanedFromFeed(
+ feedId: Long,
+ keepCount: Int,
+ ) = dao.getItemsToBeCleanedFromFeed(feedId = feedId, keepCount = keepCount)
suspend fun deleteFeedItems(ids: List) {
dao.deleteFeedItems(ids)
}
- suspend fun updateWordCountFull(id: Long, wordCount: Int) {
+ suspend fun updateWordCountFull(
+ id: Long,
+ wordCount: Int,
+ ) {
dao.updateWordCountFull(id, wordCount)
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedStore.kt
index 3f76eff76a..d53f895b2e 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedStore.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedStore.kt
@@ -11,14 +11,14 @@ import com.nononsenseapps.feeder.ui.compose.navdrawer.DrawerFeed
import com.nononsenseapps.feeder.ui.compose.navdrawer.DrawerItemWithUnreadCount
import com.nononsenseapps.feeder.ui.compose.navdrawer.DrawerTag
import com.nononsenseapps.feeder.ui.compose.navdrawer.DrawerTop
-import java.net.URL
-import java.time.Instant
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.net.URL
+import java.time.Instant
class FeedStore(override val di: DI) : DIAware {
private val feedDao: FeedDao by instance()
@@ -49,11 +49,12 @@ class FeedStore(override val di: DI) : DIAware {
}
}
- suspend fun toggleNotifications(feedId: Long, value: Boolean) =
- feedDao.setNotify(id = feedId, notify = value)
+ suspend fun toggleNotifications(
+ feedId: Long,
+ value: Boolean,
+ ) = feedDao.setNotify(id = feedId, notify = value)
- suspend fun getDisplayTitle(feedId: Long): String? =
- feedDao.getFeedTitle(feedId)?.displayTitle
+ suspend fun getDisplayTitle(feedId: Long): String? = feedDao.getFeedTitle(feedId)?.displayTitle
suspend fun deleteFeeds(feedIds: List) {
feedDao.deleteFeeds(feedIds)
@@ -73,39 +74,41 @@ class FeedStore(override val di: DI) : DIAware {
mapFeedsToSortedDrawerItems(feeds)
}
- private fun mapFeedsToSortedDrawerItems(
- feeds: List,
- ): List {
+ private fun mapFeedsToSortedDrawerItems(feeds: List): List {
var topTag = DrawerTop(unreadCount = 0, totalChildren = 0)
val tags: MutableMap = mutableMapOf()
val data: MutableList = mutableListOf()
for (feedDbo in feeds) {
- val feed = DrawerFeed(
- unreadCount = feedDbo.unreadCount,
- tag = feedDbo.tag,
- id = feedDbo.id,
- displayTitle = feedDbo.displayTitle,
- imageUrl = feedDbo.imageUrl,
- )
+ val feed =
+ DrawerFeed(
+ unreadCount = feedDbo.unreadCount,
+ tag = feedDbo.tag,
+ id = feedDbo.id,
+ displayTitle = feedDbo.displayTitle,
+ imageUrl = feedDbo.imageUrl,
+ )
data.add(feed)
- topTag = topTag.copy(
- unreadCount = topTag.unreadCount + feed.unreadCount,
- totalChildren = topTag.totalChildren + 1,
- )
+ topTag =
+ topTag.copy(
+ unreadCount = topTag.unreadCount + feed.unreadCount,
+ totalChildren = topTag.totalChildren + 1,
+ )
if (feed.tag.isNotEmpty()) {
- val tag = tags[feed.tag] ?: DrawerTag(
- tag = feed.tag,
- unreadCount = 0,
- uiId = getTagUiId(feed.tag),
- totalChildren = 0,
- )
- tags[feed.tag] = tag.copy(
- unreadCount = tag.unreadCount + feed.unreadCount,
- totalChildren = tag.totalChildren + 1,
- )
+ val tag =
+ tags[feed.tag] ?: DrawerTag(
+ tag = feed.tag,
+ unreadCount = 0,
+ uiId = getTagUiId(feed.tag),
+ totalChildren = 0,
+ )
+ tags[feed.tag] =
+ tag.copy(
+ unreadCount = tag.unreadCount + feed.unreadCount,
+ totalChildren = tag.totalChildren + 1,
+ )
}
}
@@ -115,17 +118,23 @@ class FeedStore(override val di: DI) : DIAware {
return data.sorted()
}
- fun getFeedTitles(feedId: Long, tag: String): Flow> =
+ fun getFeedTitles(
+ feedId: Long,
+ tag: String,
+ ): Flow> =
when {
feedId > ID_UNSET -> feedDao.getFeedTitlesWithId(feedId)
tag.isNotBlank() -> feedDao.getFeedTitlesWithTag(tag)
else -> feedDao.getAllFeedTitles()
}
- fun getCurrentlySyncingLatestTimestamp(): Flow =
- feedDao.getCurrentlySyncingLatestTimestamp()
+ fun getCurrentlySyncingLatestTimestamp(): Flow = feedDao.getCurrentlySyncingLatestTimestamp()
- suspend fun setCurrentlySyncingOn(feedId: Long, syncing: Boolean, lastSync: Instant? = null) {
+ suspend fun setCurrentlySyncingOn(
+ feedId: Long,
+ syncing: Boolean,
+ lastSync: Instant? = null,
+ ) {
if (lastSync != null) {
feedDao.setCurrentlySyncingOn(feedId = feedId, syncing = syncing, lastSync = lastSync)
} else {
@@ -164,21 +173,21 @@ class FeedStore(override val di: DI) : DIAware {
feedDao.deleteFeedWithUrl(url)
}
- suspend fun loadFeedIfStale(feedId: Long, staleTime: Long) =
- feedDao.loadFeedIfStale(feedId = feedId, staleTime = staleTime)
+ suspend fun loadFeedIfStale(
+ feedId: Long,
+ staleTime: Long,
+ ) = feedDao.loadFeedIfStale(feedId = feedId, staleTime = staleTime)
- suspend fun loadFeed(feedId: Long): Feed? =
- feedDao.loadFeed(feedId = feedId)
+ suspend fun loadFeed(feedId: Long): Feed? = feedDao.loadFeed(feedId = feedId)
- suspend fun loadFeedsIfStale(tag: String, staleTime: Long) =
- feedDao.loadFeedsIfStale(tag = tag, staleTime = staleTime)
+ suspend fun loadFeedsIfStale(
+ tag: String,
+ staleTime: Long,
+ ) = feedDao.loadFeedsIfStale(tag = tag, staleTime = staleTime)
- suspend fun loadFeedsIfStale(staleTime: Long) =
- feedDao.loadFeedsIfStale(staleTime = staleTime)
+ suspend fun loadFeedsIfStale(staleTime: Long) = feedDao.loadFeedsIfStale(staleTime = staleTime)
- suspend fun loadFeeds(tag: String) =
- feedDao.loadFeeds(tag = tag)
+ suspend fun loadFeeds(tag: String) = feedDao.loadFeeds(tag = tag)
- suspend fun loadFeeds() =
- feedDao.loadFeeds()
+ suspend fun loadFeeds() = feedDao.loadFeeds()
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt
index 61f3857807..a3a80b156d 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt
@@ -37,10 +37,6 @@ import com.nononsenseapps.feeder.util.Either
import com.nononsenseapps.feeder.util.addDynamicShortcutToFeed
import com.nononsenseapps.feeder.util.logDebug
import com.nononsenseapps.feeder.util.reportShortcutToFeedUsed
-import java.net.URL
-import java.time.Instant
-import java.time.ZonedDateTime
-import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -52,6 +48,10 @@ import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.net.URL
+import java.time.Instant
+import java.time.ZonedDateTime
+import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class)
class Repository(override val di: DI) : DIAware {
@@ -73,12 +73,13 @@ class Repository(override val di: DI) : DIAware {
private fun addFeederNewsIfInitialStart() {
if (!settingsStore.addedFeederNews.value) {
applicationCoroutineScope.launch {
- val feedId = feedStore.upsertFeed(
- Feed(
- title = "Feeder News",
- url = URL("https://news.nononsenseapps.com/index.atom"),
- ),
- )
+ val feedId =
+ feedStore.upsertFeed(
+ Feed(
+ title = "Feeder News",
+ url = URL("https://news.nononsenseapps.com/index.atom"),
+ ),
+ )
settingsStore.setAddedFeederNews(true)
requestFeedSync(
di = di,
@@ -89,10 +90,15 @@ class Repository(override val di: DI) : DIAware {
}
val minReadTime: StateFlow = settingsStore.minReadTime
+
fun setMinReadTime(value: Instant) = settingsStore.setMinReadTime(value)
val currentFeedAndTag: StateFlow> = settingsStore.currentFeedAndTag
- fun setCurrentFeedAndTag(feedId: Long, tag: String) {
+
+ fun setCurrentFeedAndTag(
+ feedId: Long,
+ tag: String,
+ ) {
if (feedId > ID_UNSET) {
applicationCoroutineScope.launch {
application.apply {
@@ -112,21 +118,25 @@ class Repository(override val di: DI) : DIAware {
}
val isArticleOpen: StateFlow = settingsStore.isArticleOpen
+
fun setIsArticleOpen(open: Boolean) {
settingsStore.setIsArticleOpen(open)
}
val isMarkAsReadOnScroll: StateFlow = settingsStore.isMarkAsReadOnScroll
+
fun setIsMarkAsReadOnScroll(value: Boolean) {
settingsStore.setIsMarkAsReadOnScroll(value)
}
val maxLines: StateFlow = settingsStore.maxLines
+
fun setMaxLines(value: Int) {
settingsStore.setMaxLines(value.coerceAtLeast(1))
}
val showOnlyTitle: StateFlow = settingsStore.showOnlyTitle
+
fun setShowOnlyTitles(value: Boolean) {
settingsStore.setShowOnlyTitles(value)
}
@@ -154,62 +164,73 @@ class Repository(override val di: DI) : DIAware {
}
val currentArticleId: StateFlow = settingsStore.currentArticleId
- fun setCurrentArticle(articleId: Long) =
- settingsStore.setCurrentArticle(articleId)
+
+ fun setCurrentArticle(articleId: Long) = settingsStore.setCurrentArticle(articleId)
val currentTheme: StateFlow = settingsStore.currentTheme
+
fun setCurrentTheme(value: ThemeOptions) = settingsStore.setCurrentTheme(value)
val preferredDarkTheme: StateFlow = settingsStore.darkThemePreference
- fun setPreferredDarkTheme(value: DarkThemePreferences) =
- settingsStore.setDarkThemePreference(value)
+
+ fun setPreferredDarkTheme(value: DarkThemePreferences) = settingsStore.setDarkThemePreference(value)
val blockList: Flow> = settingsStore.blockListPreference
- suspend fun addBlocklistPattern(pattern: String) =
- settingsStore.addBlocklistPattern(pattern)
+ suspend fun addBlocklistPattern(pattern: String) = settingsStore.addBlocklistPattern(pattern)
- suspend fun removeBlocklistPattern(pattern: String) =
- settingsStore.removeBlocklistPattern(pattern)
+ suspend fun removeBlocklistPattern(pattern: String) = settingsStore.removeBlocklistPattern(pattern)
val currentSorting: StateFlow = settingsStore.currentSorting
+
fun setCurrentSorting(value: SortingOptions) = settingsStore.setCurrentSorting(value)
val showFab: StateFlow = settingsStore.showFab
+
fun setShowFab(value: Boolean) = settingsStore.setShowFab(value)
val feedItemStyle: StateFlow = settingsStore.feedItemStyle
+
fun setFeedItemStyle(value: FeedItemStyle) = settingsStore.setFeedItemStyle(value)
val swipeAsRead: StateFlow = settingsStore.swipeAsRead
+
fun setSwipeAsRead(value: SwipeAsRead) = settingsStore.setSwipeAsRead(value)
val syncOnResume: StateFlow = settingsStore.syncOnResume
+
fun setSyncOnResume(value: Boolean) = settingsStore.setSyncOnResume(value)
val syncOnlyOnWifi: StateFlow = settingsStore.syncOnlyOnWifi
+
fun setSyncOnlyOnWifi(value: Boolean) = settingsStore.setSyncOnlyOnWifi(value)
val syncOnlyWhenCharging: StateFlow = settingsStore.syncOnlyWhenCharging
- fun setSyncOnlyWhenCharging(value: Boolean) =
- settingsStore.setSyncOnlyWhenCharging(value)
+
+ fun setSyncOnlyWhenCharging(value: Boolean) = settingsStore.setSyncOnlyWhenCharging(value)
val loadImageOnlyOnWifi = settingsStore.loadImageOnlyOnWifi
+
fun setLoadImageOnlyOnWifi(value: Boolean) = settingsStore.setLoadImageOnlyOnWifi(value)
val showThumbnails = settingsStore.showThumbnails
+
fun setShowThumbnails(value: Boolean) = settingsStore.setShowThumbnails(value)
val useDetectLanguage = settingsStore.useDetectLanguage
+
fun setUseDetectLanguage(value: Boolean) = settingsStore.setUseDetectLanguage(value)
val useDynamicTheme = settingsStore.useDynamicTheme
+
fun setUseDynamicTheme(value: Boolean) = settingsStore.setUseDynamicTheme(value)
val textScale = settingsStore.textScale
+
fun setTextScale(value: Float) = settingsStore.setTextScale(value)
val maximumCountPerFeed = settingsStore.maximumCountPerFeed
+
fun setMaxCountPerFeed(value: Int) = settingsStore.setMaxCountPerFeed(value)
val itemOpener
@@ -218,12 +239,15 @@ class Repository(override val di: DI) : DIAware {
fun setItemOpener(value: ItemOpener) = settingsStore.setItemOpener(value)
val linkOpener = settingsStore.linkOpener
+
fun setLinkOpener(value: LinkOpener) = settingsStore.setLinkOpener(value)
val syncFrequency = settingsStore.syncFrequency
+
fun setSyncFrequency(value: SyncFrequency) = settingsStore.setSyncFrequency(value)
val resumeTime: StateFlow = sessionStore.resumeTime
+
fun setResumeTime(value: Instant) {
sessionStore.setResumeTime(value)
}
@@ -239,84 +263,96 @@ class Repository(override val di: DI) : DIAware {
}
@OptIn(ExperimentalCoroutinesApi::class)
- fun getCurrentFeedListItems(): Flow> = combine(
- currentFeedAndTag,
- minReadTime,
- currentSorting,
- feedListFilter,
- ) { feedAndTag, minReadTime, currentSorting, feedListFilter ->
- val (feedId, tag) = feedAndTag
- FeedListArgs(
- feedId = feedId,
- tag = tag,
- minReadTime = when (feedId) {
- ID_SAVED_ARTICLES -> Instant.EPOCH
- else -> minReadTime
- },
- newestFirst = currentSorting == SortingOptions.NEWEST_FIRST,
- filter = feedListFilter,
- )
- }.flatMapLatest {
- feedItemStore.getPagedFeedItemsRaw(
- feedId = it.feedId,
- tag = it.tag,
- minReadTime = it.minReadTime,
- newestFirst = it.newestFirst,
- filter = it.filter,
- )
- }
+ fun getCurrentFeedListItems(): Flow> =
+ combine(
+ currentFeedAndTag,
+ minReadTime,
+ currentSorting,
+ feedListFilter,
+ ) { feedAndTag, minReadTime, currentSorting, feedListFilter ->
+ val (feedId, tag) = feedAndTag
+ FeedListArgs(
+ feedId = feedId,
+ tag = tag,
+ minReadTime =
+ when (feedId) {
+ ID_SAVED_ARTICLES -> Instant.EPOCH
+ else -> minReadTime
+ },
+ newestFirst = currentSorting == SortingOptions.NEWEST_FIRST,
+ filter = feedListFilter,
+ )
+ }.flatMapLatest {
+ feedItemStore.getPagedFeedItemsRaw(
+ feedId = it.feedId,
+ tag = it.tag,
+ minReadTime = it.minReadTime,
+ newestFirst = it.newestFirst,
+ filter = it.filter,
+ )
+ }
@OptIn(ExperimentalCoroutinesApi::class)
- fun getCurrentFeedListVisibleItemCount(): Flow = combine(
- currentFeedAndTag,
- minReadTime,
- feedListFilter,
- ) { feedAndTag, minReadTime, feedListFilter ->
- val (feedId, tag) = feedAndTag
- FeedListArgs(
- feedId = feedId,
- tag = tag,
- minReadTime = when (feedId) {
- ID_SAVED_ARTICLES -> Instant.EPOCH
- else -> minReadTime
- },
- newestFirst = false,
- filter = feedListFilter,
- )
- }.flatMapLatest {
- feedItemStore.getFeedItemCountRaw(
- feedId = it.feedId,
- tag = it.tag,
- minReadTime = it.minReadTime,
- filter = it.filter,
- )
- }
-
- val currentArticle: Flow = currentArticleId
- .flatMapLatest { itemId ->
- feedItemStore.getFeedItem(itemId)
- }
- .mapLatest { item ->
- Article(item = item)
+ fun getCurrentFeedListVisibleItemCount(): Flow =
+ combine(
+ currentFeedAndTag,
+ minReadTime,
+ feedListFilter,
+ ) { feedAndTag, minReadTime, feedListFilter ->
+ val (feedId, tag) = feedAndTag
+ FeedListArgs(
+ feedId = feedId,
+ tag = tag,
+ minReadTime =
+ when (feedId) {
+ ID_SAVED_ARTICLES -> Instant.EPOCH
+ else -> minReadTime
+ },
+ newestFirst = false,
+ filter = feedListFilter,
+ )
+ }.flatMapLatest {
+ feedItemStore.getFeedItemCountRaw(
+ feedId = it.feedId,
+ tag = it.tag,
+ minReadTime = it.minReadTime,
+ filter = it.filter,
+ )
}
+ val currentArticle: Flow =
+ currentArticleId
+ .flatMapLatest { itemId ->
+ feedItemStore.getFeedItem(itemId)
+ }
+ .mapLatest { item ->
+ Article(item = item)
+ }
+
suspend fun getFeed(feedId: Long): Feed? = feedStore.getFeed(feedId)
suspend fun getFeed(url: URL): Feed? = feedStore.getFeed(url)
suspend fun saveFeed(feed: Feed): Long = feedStore.saveFeed(feed)
- suspend fun setBookmarked(itemId: Long, bookmarked: Boolean) =
- feedItemStore.setBookmarked(itemId = itemId, bookmarked = bookmarked)
+ suspend fun setBookmarked(
+ itemId: Long,
+ bookmarked: Boolean,
+ ) = feedItemStore.setBookmarked(itemId = itemId, bookmarked = bookmarked)
suspend fun markAsNotified(itemIds: List) = feedItemStore.markAsNotified(itemIds)
- suspend fun toggleNotifications(feedId: Long, value: Boolean) =
- feedStore.toggleNotifications(feedId, value)
+ suspend fun toggleNotifications(
+ feedId: Long,
+ value: Boolean,
+ ) = feedStore.toggleNotifications(feedId, value)
val feedNotificationSettings: Flow> = feedStore.feedForSettings
- suspend fun markAsReadAndNotified(itemId: Long, readTimeBeforeMinReadTime: Boolean = false) {
+ suspend fun markAsReadAndNotified(
+ itemId: Long,
+ readTimeBeforeMinReadTime: Boolean = false,
+ ) {
minReadTime.value.let { minReadTimeValue ->
if (readTimeBeforeMinReadTime && minReadTimeValue.isAfter(Instant.EPOCH)) {
// If read time is not EPOCH, one second before so swipe can get rid of it
@@ -352,20 +388,25 @@ class Repository(override val di: DI) : DIAware {
else -> itemOpener.value // Global default
}
- fun getScreenTitleForFeedOrTag(feedId: Long, tag: String) = flow {
+ fun getScreenTitleForFeedOrTag(
+ feedId: Long,
+ tag: String,
+ ) = flow {
emit(
ScreenTitle(
- title = when {
- feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
- tag.isNotBlank() -> tag
- else -> null
- },
- type = when (feedId) {
- ID_UNSET -> FeedType.TAG
- ID_ALL_FEEDS -> FeedType.ALL_FEEDS
- ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
- else -> FeedType.FEED
- },
+ title =
+ when {
+ feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
+ tag.isNotBlank() -> tag
+ else -> null
+ },
+ type =
+ when (feedId) {
+ ID_UNSET -> FeedType.TAG
+ ID_ALL_FEEDS -> FeedType.ALL_FEEDS
+ ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
+ else -> FeedType.FEED
+ },
),
)
}
@@ -374,17 +415,19 @@ class Repository(override val di: DI) : DIAware {
fun getScreenTitleForCurrentFeedOrTag(): Flow =
currentFeedAndTag.mapLatest { (feedId, tag) ->
ScreenTitle(
- title = when {
- feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
- tag.isNotBlank() -> tag
- else -> null
- },
- type = when (feedId) {
- ID_UNSET -> FeedType.TAG
- ID_ALL_FEEDS -> FeedType.ALL_FEEDS
- ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
- else -> FeedType.FEED
- },
+ title =
+ when {
+ feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
+ tag.isNotBlank() -> tag
+ else -> null
+ },
+ type =
+ when (feedId) {
+ ID_UNSET -> FeedType.TAG
+ ID_ALL_FEEDS -> FeedType.ALL_FEEDS
+ ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
+ else -> FeedType.FEED
+ },
)
}
@@ -396,7 +439,10 @@ class Repository(override val di: DI) : DIAware {
}
}
- suspend fun markAllAsReadInFeedOrTag(feedId: Long, tag: String) {
+ suspend fun markAllAsReadInFeedOrTag(
+ feedId: Long,
+ tag: String,
+ ) {
when {
feedId > ID_UNSET -> feedItemStore.markAllAsReadInFeed(feedId)
tag.isNotBlank() -> feedItemStore.markAllAsReadInTag(tag)
@@ -406,7 +452,11 @@ class Repository(override val di: DI) : DIAware {
setMinReadTime(Instant.now())
}
- suspend fun markBeforeAsRead(cursor: FeedItemCursor, feedId: Long, tag: String) {
+ suspend fun markBeforeAsRead(
+ cursor: FeedItemCursor,
+ feedId: Long,
+ tag: String,
+ ) {
feedItemStore.markAsReadRaw(
feedId = feedId,
tag = tag,
@@ -418,7 +468,11 @@ class Repository(override val di: DI) : DIAware {
scheduleSendRead()
}
- suspend fun markAfterAsRead(cursor: FeedItemCursor, feedId: Long, tag: String) {
+ suspend fun markAfterAsRead(
+ cursor: FeedItemCursor,
+ feedId: Long,
+ tag: String,
+ ) {
feedItemStore.markAsReadRaw(
feedId = feedId,
tag = tag,
@@ -436,12 +490,13 @@ class Repository(override val di: DI) : DIAware {
feedStore.drawerItemsWithUnreadCounts
val getUnreadBookmarksCount
- get() = feedItemStore.getFeedItemCountRaw(
- feedId = ID_SAVED_ARTICLES,
- tag = "",
- minReadTime = Instant.EPOCH,
- filter = emptyFeedListFilter,
- )
+ get() =
+ feedItemStore.getFeedItemCountRaw(
+ feedId = ID_SAVED_ARTICLES,
+ tag = "",
+ minReadTime = Instant.EPOCH,
+ filter = emptyFeedListFilter,
+ )
@OptIn(ExperimentalCoroutinesApi::class)
fun getCurrentlyVisibleFeedTitles(): Flow> =
@@ -453,20 +508,21 @@ class Repository(override val di: DI) : DIAware {
fun toggleTagExpansion(tag: String) = sessionStore.toggleTagExpansion(tag)
- fun ensurePeriodicSyncConfigured() =
- settingsStore.configurePeriodicSync(replace = false)
+ fun ensurePeriodicSyncConfigured() = settingsStore.configurePeriodicSync(replace = false)
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow> =
feedItemStore.getFeedsItemsWithDefaultFullTextNeedingDownload()
- suspend fun markAsFullTextDownloaded(feedItemId: Long) =
- feedItemStore.markAsFullTextDownloaded(feedItemId)
+ suspend fun markAsFullTextDownloaded(feedItemId: Long) = feedItemStore.markAsFullTextDownloaded(feedItemId)
fun getFeedItemsNeedingNotifying(): Flow> {
return feedItemStore.getFeedItemsNeedingNotifying()
}
- suspend fun remoteMarkAsRead(feedUrl: URL, articleGuid: String) {
+ suspend fun remoteMarkAsRead(
+ feedUrl: URL,
+ articleGuid: String,
+ ) {
// Always write a remoteReadMark - this is part of concurrency mitigation
syncRemoteStore.addRemoteReadMark(feedUrl = feedUrl, articleGuid = articleGuid)
// But also try to get an existing ID and set
@@ -500,11 +556,12 @@ class Repository(override val di: DI) : DIAware {
syncRemoteStore.setSynced(feedItemId)
}
- suspend fun upsertFeed(feedSql: Feed) =
- feedStore.upsertFeed(feedSql)
+ suspend fun upsertFeed(feedSql: Feed) = feedStore.upsertFeed(feedSql)
- suspend fun loadFeedItem(guid: String, feedId: Long): FeedItem? =
- feedItemStore.loadFeedItem(guid = guid, feedId = feedId)
+ suspend fun loadFeedItem(
+ guid: String,
+ feedId: Long,
+ ): FeedItem? = feedItemStore.loadFeedItem(guid = guid, feedId = feedId)
suspend fun upsertFeedItems(
itemsWithText: List>,
@@ -513,8 +570,10 @@ class Repository(override val di: DI) : DIAware {
feedItemStore.upsertFeedItems(itemsWithText, block)
}
- suspend fun getItemsToBeCleanedFromFeed(feedId: Long, keepCount: Int) =
- feedItemStore.getItemsToBeCleanedFromFeed(feedId = feedId, keepCount = keepCount)
+ suspend fun getItemsToBeCleanedFromFeed(
+ feedId: Long,
+ keepCount: Int,
+ ) = feedItemStore.getItemsToBeCleanedFromFeed(feedId = feedId, keepCount = keepCount)
suspend fun deleteFeedItems(ids: List) {
feedItemStore.deleteFeedItems(ids)
@@ -524,8 +583,7 @@ class Repository(override val di: DI) : DIAware {
syncRemoteStore.deleteStaleRemoteReadMarks(Instant.now())
}
- suspend fun getGuidsWhichAreSyncedAsReadInFeed(feed: Feed) =
- syncRemoteStore.getGuidsWhichAreSyncedAsReadInFeed(feed.url)
+ suspend fun getGuidsWhichAreSyncedAsReadInFeed(feed: Feed) = syncRemoteStore.getGuidsWhichAreSyncedAsReadInFeed(feed.url)
suspend fun applyRemoteReadMarks() {
val toBeApplied = syncRemoteStore.getRemoteReadMarksReadyToBeApplied()
@@ -569,7 +627,10 @@ class Repository(override val di: DI) : DIAware {
return syncClient.getDevices()
}
- suspend fun joinSyncChain(syncCode: String, secretKey: String): Either {
+ suspend fun joinSyncChain(
+ syncCode: String,
+ secretKey: String,
+ ): Either {
return syncClient.join(syncCode = syncCode, remoteSecretKey = secretKey)
.onRight {
syncClient.getDevices()
@@ -603,9 +664,10 @@ class Repository(override val di: DI) : DIAware {
private fun scheduleSendRead() {
logDebug(LOG_TAG, "Scheduling work")
- val constraints = Constraints.Builder()
- // This prevents expedited if true
- .setRequiresCharging(syncOnlyWhenCharging.value)
+ val constraints =
+ Constraints.Builder()
+ // This prevents expedited if true
+ .setRequiresCharging(syncOnlyWhenCharging.value)
if (syncOnlyOnWifi.value) {
constraints.setRequiredNetworkType(NetworkType.UNMETERED)
@@ -613,11 +675,12 @@ class Repository(override val di: DI) : DIAware {
constraints.setRequiredNetworkType(NetworkType.CONNECTED)
}
- val workRequest = OneTimeWorkRequestBuilder()
- .addTag("feeder")
- .keepResultsForAtLeast(5, TimeUnit.MINUTES)
- .setConstraints(constraints.build())
- .setInitialDelay(10, TimeUnit.SECONDS)
+ val workRequest =
+ OneTimeWorkRequestBuilder()
+ .addTag("feeder")
+ .keepResultsForAtLeast(5, TimeUnit.MINUTES)
+ .setConstraints(constraints.build())
+ .setInitialDelay(10, TimeUnit.SECONDS)
workManager.enqueueUniqueWork(
SyncServiceSendReadWorker.UNIQUE_SENDREAD_NAME,
@@ -626,38 +689,46 @@ class Repository(override val di: DI) : DIAware {
)
}
- suspend fun loadFeedIfStale(feedId: Long, staleTime: Long) =
- feedStore.loadFeedIfStale(feedId = feedId, staleTime = staleTime)
+ suspend fun loadFeedIfStale(
+ feedId: Long,
+ staleTime: Long,
+ ) = feedStore.loadFeedIfStale(feedId = feedId, staleTime = staleTime)
- suspend fun loadFeed(feedId: Long): Feed? =
- feedStore.loadFeed(feedId = feedId)
+ suspend fun loadFeed(feedId: Long): Feed? = feedStore.loadFeed(feedId = feedId)
- suspend fun loadFeedsIfStale(tag: String, staleTime: Long) =
- feedStore.loadFeedsIfStale(tag = tag, staleTime = staleTime)
+ suspend fun loadFeedsIfStale(
+ tag: String,
+ staleTime: Long,
+ ) = feedStore.loadFeedsIfStale(tag = tag, staleTime = staleTime)
- suspend fun loadFeedsIfStale(staleTime: Long) =
- feedStore.loadFeedsIfStale(staleTime = staleTime)
+ suspend fun loadFeedsIfStale(staleTime: Long) = feedStore.loadFeedsIfStale(staleTime = staleTime)
- suspend fun loadFeeds(tag: String): List =
- feedStore.loadFeeds(tag = tag)
+ suspend fun loadFeeds(tag: String): List = feedStore.loadFeeds(tag = tag)
- suspend fun loadFeeds(): List =
- feedStore.loadFeeds()
+ suspend fun loadFeeds(): List = feedStore.loadFeeds()
- suspend fun setCurrentlySyncingOn(feedId: Long, syncing: Boolean, lastSync: Instant? = null) =
- feedStore.setCurrentlySyncingOn(feedId = feedId, syncing = syncing, lastSync = lastSync)
+ suspend fun setCurrentlySyncingOn(
+ feedId: Long,
+ syncing: Boolean,
+ lastSync: Instant? = null,
+ ) = feedStore.setCurrentlySyncingOn(feedId = feedId, syncing = syncing, lastSync = lastSync)
val isOpenAdjacent: StateFlow = settingsStore.openAdjacent
+
fun setOpenAdjacent(value: Boolean) {
settingsStore.setOpenAdjacent(value)
}
val showReadingTime: StateFlow = settingsStore.showReadingTime
+
fun setShowReadingTime(value: Boolean) {
settingsStore.setShowReadingTime(value)
}
- suspend fun updateWordCountFull(id: Long, wordCount: Int) {
+ suspend fun updateWordCountFull(
+ id: Long,
+ wordCount: Int,
+ ) {
feedItemStore.updateWordCountFull(id, wordCount)
}
@@ -707,16 +778,17 @@ data class Article(
val link: String? = item?.link
val feedDisplayTitle: String = item?.feedDisplayTitle ?: ""
val title: String = item?.plainTitle ?: ""
- val enclosure: Enclosure = item?.enclosureLink?.let { link ->
- Enclosure(
- present = true,
- link = link,
- name = item.enclosureFilename ?: "",
- type = item.enclosureType ?: "",
+ val enclosure: Enclosure =
+ item?.enclosureLink?.let { link ->
+ Enclosure(
+ present = true,
+ link = link,
+ name = item.enclosureFilename ?: "",
+ type = item.enclosureType ?: "",
+ )
+ } ?: Enclosure(
+ present = false,
)
- } ?: Enclosure(
- present = false,
- )
val author: String? = item?.author
val pubDate: ZonedDateTime? = item?.pubDate
val feedId: Long = item?.feedId ?: ID_UNSET
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SessionStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SessionStore.kt
index e192a097b8..3884182138 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SessionStore.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SessionStore.kt
@@ -1,10 +1,10 @@
package com.nononsenseapps.feeder.archmodel
-import java.time.Instant
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import java.time.Instant
class SessionStore {
private val _resumeTime = MutableStateFlow(Instant.EPOCH)
@@ -14,6 +14,7 @@ class SessionStore {
* activity returns to the foreground
*/
val resumeTime: StateFlow = _resumeTime.asStateFlow()
+
fun setResumeTime(instant: Instant) {
_resumeTime.update {
instant
@@ -22,6 +23,7 @@ class SessionStore {
private val _expandedTags = MutableStateFlow(emptySet())
val expandedTags: StateFlow> = _expandedTags.asStateFlow()
+
fun toggleTagExpansion(tag: String) {
_expandedTags.update {
if (tag in expandedTags.value) {
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt
index aeaab64245..35233b9ed4 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SettingsStore.kt
@@ -18,8 +18,6 @@ import com.nononsenseapps.feeder.model.workmanager.oldPeriodics
import com.nononsenseapps.feeder.ui.compose.feedarticle.FeedListFilter
import com.nononsenseapps.feeder.util.PREF_MAX_ITEM_COUNT_PER_FEED
import com.nononsenseapps.feeder.util.getStringNonNull
-import java.time.Instant
-import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,6 +28,8 @@ import kotlinx.coroutines.flow.update
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.time.Instant
+import java.util.concurrent.TimeUnit
@OptIn(ExperimentalCoroutinesApi::class)
class SettingsStore(override val di: DI) : DIAware {
@@ -38,29 +38,36 @@ class SettingsStore(override val di: DI) : DIAware {
private val _addedFeederNews = MutableStateFlow(sp.getBoolean(PREF_ADDED_FEEDER_NEWS, false))
val addedFeederNews: StateFlow = _addedFeederNews.asStateFlow()
+
fun setAddedFeederNews(value: Boolean) {
sp.edit().putBoolean(PREF_ADDED_FEEDER_NEWS, value).apply()
_addedFeederNews.value = value
}
- private val _minReadTime: MutableStateFlow = MutableStateFlow(
- // by design - min read time is never written to disk
- Instant.now(),
- )
+ private val _minReadTime: MutableStateFlow =
+ MutableStateFlow(
+ // by design - min read time is never written to disk
+ Instant.now(),
+ )
val minReadTime: StateFlow = _minReadTime.asStateFlow()
+
fun setMinReadTime(value: Instant) {
_minReadTime.value = value
}
- private val _currentFeedAndTag = MutableStateFlow(
- sp.getLong(PREF_LAST_FEED_ID, ID_UNSET) to (sp.getString(PREF_LAST_FEED_TAG, null) ?: ""),
- )
+ private val _currentFeedAndTag =
+ MutableStateFlow(
+ sp.getLong(PREF_LAST_FEED_ID, ID_UNSET) to (sp.getString(PREF_LAST_FEED_TAG, null) ?: ""),
+ )
val currentFeedAndTag = _currentFeedAndTag.asStateFlow()
/**
* Returns true if the parameters were different from current state
*/
- fun setCurrentFeedAndTag(feedId: Long, tag: String): Boolean =
+ fun setCurrentFeedAndTag(
+ feedId: Long,
+ tag: String,
+ ): Boolean =
if (_currentFeedAndTag.value.first != feedId ||
_currentFeedAndTag.value.second != tag
) {
@@ -72,37 +79,45 @@ class SettingsStore(override val di: DI) : DIAware {
false
}
- private val _currentArticle = MutableStateFlow(
- sp.getLong(PREF_LAST_ARTICLE_ID, ID_UNSET),
- )
+ private val _currentArticle =
+ MutableStateFlow(
+ sp.getLong(PREF_LAST_ARTICLE_ID, ID_UNSET),
+ )
val currentArticleId = _currentArticle.asStateFlow()
+
fun setCurrentArticle(articleId: Long) {
_currentArticle.value = articleId
sp.edit().putLong(PREF_LAST_ARTICLE_ID, articleId).apply()
}
- private val _isArticleOpen = MutableStateFlow(
- sp.getBoolean(PREF_IS_ARTICLE_OPEN, false),
- )
+ private val _isArticleOpen =
+ MutableStateFlow(
+ sp.getBoolean(PREF_IS_ARTICLE_OPEN, false),
+ )
val isArticleOpen: StateFlow = _isArticleOpen.asStateFlow()
+
fun setIsArticleOpen(open: Boolean) {
_isArticleOpen.update { open }
sp.edit().putBoolean(PREF_IS_ARTICLE_OPEN, open).apply()
}
- private val _isMarkAsReadOnScroll = MutableStateFlow(
- sp.getBoolean(PREF_IS_MARK_AS_READ_ON_SCROLL, false),
- )
+ private val _isMarkAsReadOnScroll =
+ MutableStateFlow(
+ sp.getBoolean(PREF_IS_MARK_AS_READ_ON_SCROLL, false),
+ )
val isMarkAsReadOnScroll: StateFlow = _isMarkAsReadOnScroll.asStateFlow()
+
fun setIsMarkAsReadOnScroll(open: Boolean) {
_isMarkAsReadOnScroll.update { open }
sp.edit().putBoolean(PREF_IS_MARK_AS_READ_ON_SCROLL, open).apply()
}
- private val _maxLines = MutableStateFlow(
- sp.getInt(PREF_MAX_LINES, 2),
- )
+ private val _maxLines =
+ MutableStateFlow(
+ sp.getInt(PREF_MAX_LINES, 2),
+ )
val maxLines: StateFlow = _maxLines.asStateFlow()
+
fun setMaxLines(value: Int) {
if (value > 0) {
_maxLines.update { value }
@@ -110,27 +125,32 @@ class SettingsStore(override val di: DI) : DIAware {
}
}
- private val _showOnlyTitle = MutableStateFlow(
- sp.getBoolean(PREF_LIST_SHOW_ONLY_TITLES, false),
- )
+ private val _showOnlyTitle =
+ MutableStateFlow(
+ sp.getBoolean(PREF_LIST_SHOW_ONLY_TITLES, false),
+ )
val showOnlyTitle: StateFlow = _showOnlyTitle.asStateFlow()
+
fun setShowOnlyTitles(value: Boolean) {
_showOnlyTitle.update { value }
sp.edit().putBoolean(PREF_LIST_SHOW_ONLY_TITLES, value).apply()
}
- private val _feedListFilter = MutableStateFlow(
- PrefsFeedListFilter(
- saved = sp.getBoolean(PREFS_FILTER_SAVED, false),
- recentlyRead = sp.getBoolean(PREFS_FILTER_RECENTLY_READ, true),
- read = sp.getBoolean(
- PREFS_FILTER_READ,
- // Migration
- !sp.getBoolean("pref_show_only_unread", true),
+ private val _feedListFilter =
+ MutableStateFlow(
+ PrefsFeedListFilter(
+ saved = sp.getBoolean(PREFS_FILTER_SAVED, false),
+ recentlyRead = sp.getBoolean(PREFS_FILTER_RECENTLY_READ, true),
+ read =
+ sp.getBoolean(
+ PREFS_FILTER_READ,
+ // Migration
+ !sp.getBoolean("pref_show_only_unread", true),
+ ),
),
- ),
- )
+ )
val feedListFilter: StateFlow = _feedListFilter.asStateFlow()
+
fun setFeedListFilterSaved(value: Boolean) {
_feedListFilter.update { it.copy(saved = value) }
sp.edit().putBoolean(PREFS_FILTER_SAVED, value).apply()
@@ -146,37 +166,43 @@ class SettingsStore(override val di: DI) : DIAware {
sp.edit().putBoolean(PREFS_FILTER_READ, value).apply()
}
- private val _currentTheme = MutableStateFlow(
- themeOptionsFromString(
- sp.getString(PREF_THEME, null)?.uppercase()
- ?: ThemeOptions.SYSTEM.name,
- ),
- )
+ private val _currentTheme =
+ MutableStateFlow(
+ themeOptionsFromString(
+ sp.getString(PREF_THEME, null)?.uppercase()
+ ?: ThemeOptions.SYSTEM.name,
+ ),
+ )
val currentTheme = _currentTheme.asStateFlow()
+
fun setCurrentTheme(value: ThemeOptions) {
_currentTheme.value = value
sp.edit().putString(PREF_THEME, value.name.lowercase()).apply()
}
- private val _darkThemePreference = MutableStateFlow(
- darkThemePreferenceFromString(
- sp.getString(PREF_DARK_THEME, null)?.uppercase()
- ?: DarkThemePreferences.BLACK.name,
- ),
- )
+ private val _darkThemePreference =
+ MutableStateFlow(
+ darkThemePreferenceFromString(
+ sp.getString(PREF_DARK_THEME, null)?.uppercase()
+ ?: DarkThemePreferences.BLACK.name,
+ ),
+ )
val darkThemePreference = _darkThemePreference.asStateFlow()
+
fun setDarkThemePreference(value: DarkThemePreferences) {
_darkThemePreference.value = value
sp.edit().putString(PREF_DARK_THEME, value.name.lowercase()).apply()
}
- private val _currentSorting = MutableStateFlow(
- sortingOptionsFromString(
- sp.getString(PREF_SORT, null)?.uppercase()
- ?: SortingOptions.NEWEST_FIRST.name,
- ),
- )
+ private val _currentSorting =
+ MutableStateFlow(
+ sortingOptionsFromString(
+ sp.getString(PREF_SORT, null)?.uppercase()
+ ?: SortingOptions.NEWEST_FIRST.name,
+ ),
+ )
val currentSorting = _currentSorting.asStateFlow()
+
fun setCurrentSorting(value: SortingOptions) {
_currentSorting.value = value
sp.edit().putString(PREF_SORT, value.name.lowercase()).apply()
@@ -184,6 +210,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _showFab = MutableStateFlow(sp.getBoolean(PREF_SHOW_FAB, true))
val showFab = _showFab.asStateFlow()
+
fun setShowFab(value: Boolean) {
_showFab.value = value
sp.edit().putBoolean(PREF_SHOW_FAB, value).apply()
@@ -191,6 +218,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _syncOnResume = MutableStateFlow(sp.getBoolean(PREF_SYNC_ON_RESUME, false))
val syncOnResume = _syncOnResume.asStateFlow()
+
fun setSyncOnResume(value: Boolean) {
_syncOnResume.value = value
sp.edit().putBoolean(PREF_SYNC_ON_RESUME, value).apply()
@@ -198,6 +226,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _syncOnlyOnWifi = MutableStateFlow(sp.getBoolean(PREF_SYNC_ONLY_WIFI, false))
val syncOnlyOnWifi = _syncOnlyOnWifi.asStateFlow()
+
fun setSyncOnlyOnWifi(value: Boolean) {
_syncOnlyOnWifi.value = value
sp.edit().putBoolean(PREF_SYNC_ONLY_WIFI, value).apply()
@@ -207,6 +236,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _syncOnlyWhenCharging =
MutableStateFlow(sp.getBoolean(PREF_SYNC_ONLY_CHARGING, false))
val syncOnlyWhenCharging = _syncOnlyWhenCharging.asStateFlow()
+
fun setSyncOnlyWhenCharging(value: Boolean) {
_syncOnlyWhenCharging.value = value
sp.edit().putBoolean(PREF_SYNC_ONLY_CHARGING, value).apply()
@@ -215,6 +245,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _loadImageOnlyOnWifi = MutableStateFlow(sp.getBoolean(PREF_IMG_ONLY_WIFI, false))
val loadImageOnlyOnWifi = _loadImageOnlyOnWifi.asStateFlow()
+
fun setLoadImageOnlyOnWifi(value: Boolean) {
_loadImageOnlyOnWifi.value = value
sp.edit().putBoolean(PREF_IMG_ONLY_WIFI, value).apply()
@@ -222,6 +253,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _showThumbnails = MutableStateFlow(sp.getBoolean(PREF_IMG_SHOW_THUMBNAILS, true))
val showThumbnails = _showThumbnails.asStateFlow()
+
fun setShowThumbnails(value: Boolean) {
_showThumbnails.value = value
sp.edit().putBoolean(PREF_IMG_SHOW_THUMBNAILS, value).apply()
@@ -230,6 +262,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _useDetectLanguage =
MutableStateFlow(sp.getBoolean(PREF_READALOUD_USE_DETECT_LANGUAGE, true))
val useDetectLanguage = _useDetectLanguage.asStateFlow()
+
fun setUseDetectLanguage(value: Boolean) {
_useDetectLanguage.value = value
sp.edit().putBoolean(PREF_READALOUD_USE_DETECT_LANGUAGE, value).apply()
@@ -237,12 +270,14 @@ class SettingsStore(override val di: DI) : DIAware {
private val _useDynamicTheme =
MutableStateFlow(
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && sp.getBoolean(
- PREF_DYNAMIC_THEME,
- true,
- ),
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+ sp.getBoolean(
+ PREF_DYNAMIC_THEME,
+ true,
+ ),
)
val useDynamicTheme = _useDynamicTheme.asStateFlow()
+
fun setUseDynamicTheme(value: Boolean) {
_useDynamicTheme.value = value && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
sp.edit().putBoolean(PREF_DYNAMIC_THEME, value).apply()
@@ -256,6 +291,7 @@ class SettingsStore(override val di: DI) : DIAware {
),
)
val textScale = _textScale.asStateFlow()
+
fun setTextScale(value: Float) {
_textScale.value = value
sp.edit().putFloat(PREF_TEXT_SCALE, value).apply()
@@ -264,20 +300,23 @@ class SettingsStore(override val di: DI) : DIAware {
private val _maximumCountPerFeed =
MutableStateFlow(sp.getStringNonNull(PREF_MAX_ITEM_COUNT_PER_FEED, "100").toInt())
val maximumCountPerFeed = _maximumCountPerFeed.asStateFlow()
+
fun setMaxCountPerFeed(value: Int) {
_maximumCountPerFeed.value = value
sp.edit().putString(PREF_MAX_ITEM_COUNT_PER_FEED, "$value").apply()
}
- private val _itemOpener = MutableStateFlow(
- itemOpenerFromString(
- sp.getStringNonNull(
- PREF_DEFAULT_OPEN_ITEM_WITH,
- PREF_VAL_OPEN_WITH_READER,
+ private val _itemOpener =
+ MutableStateFlow(
+ itemOpenerFromString(
+ sp.getStringNonNull(
+ PREF_DEFAULT_OPEN_ITEM_WITH,
+ PREF_VAL_OPEN_WITH_READER,
+ ),
),
- ),
- )
+ )
val itemOpener = _itemOpener.asStateFlow()
+
fun setItemOpener(value: ItemOpener) {
_itemOpener.value = value
sp.edit().putString(
@@ -290,12 +329,14 @@ class SettingsStore(override val di: DI) : DIAware {
).apply()
}
- private val _linkOpener = MutableStateFlow(
- linkOpenerFromString(
- sp.getStringNonNull(PREF_OPEN_LINKS_WITH, PREF_VAL_OPEN_WITH_CUSTOM_TAB),
- ),
- )
+ private val _linkOpener =
+ MutableStateFlow(
+ linkOpenerFromString(
+ sp.getStringNonNull(PREF_OPEN_LINKS_WITH, PREF_VAL_OPEN_WITH_CUSTOM_TAB),
+ ),
+ )
val linkOpener = _linkOpener.asStateFlow()
+
fun setLinkOpener(value: LinkOpener) {
_linkOpener.value = value
sp.edit().putString(
@@ -309,6 +350,7 @@ class SettingsStore(override val di: DI) : DIAware {
private val _openAdjacent = MutableStateFlow(sp.getBoolean(PREF_OPEN_ADJACENT, true))
val openAdjacent = _openAdjacent.asStateFlow()
+
fun setOpenAdjacent(value: Boolean) {
_openAdjacent.value = value
sp.edit().putBoolean(PREF_OPEN_ADJACENT, value).apply()
@@ -316,15 +358,18 @@ class SettingsStore(override val di: DI) : DIAware {
private val _showReadingTime = MutableStateFlow(sp.getBoolean(PREF_LIST_SHOW_READING_TIME, false))
val showReadingTime = _showReadingTime.asStateFlow()
+
fun setShowReadingTime(value: Boolean) {
_showReadingTime.value = value
sp.edit().putBoolean(PREF_LIST_SHOW_READING_TIME, value).apply()
}
- private val _feedItemStyle = MutableStateFlow(
- feedItemStyleFromString(sp.getStringNonNull(PREF_FEED_ITEM_STYLE, FeedItemStyle.CARD.name)),
- )
+ private val _feedItemStyle =
+ MutableStateFlow(
+ feedItemStyleFromString(sp.getStringNonNull(PREF_FEED_ITEM_STYLE, FeedItemStyle.CARD.name)),
+ )
val feedItemStyle = _feedItemStyle.asStateFlow()
+
fun setFeedItemStyle(value: FeedItemStyle) {
_feedItemStyle.value = value
sp.edit().putString(
@@ -333,12 +378,14 @@ class SettingsStore(override val di: DI) : DIAware {
).apply()
}
- private val _swipeAsRead = MutableStateFlow(
- swipeAsReadFromString(
- sp.getStringNonNull(PREF_SWIPE_AS_READ, SwipeAsRead.ONLY_FROM_END.name),
- ),
- )
+ private val _swipeAsRead =
+ MutableStateFlow(
+ swipeAsReadFromString(
+ sp.getStringNonNull(PREF_SWIPE_AS_READ, SwipeAsRead.ONLY_FROM_END.name),
+ ),
+ )
val swipeAsRead = _swipeAsRead.asStateFlow()
+
fun setSwipeAsRead(value: SwipeAsRead) {
_swipeAsRead.value = value
sp.edit().putString(
@@ -348,13 +395,14 @@ class SettingsStore(override val di: DI) : DIAware {
}
val blockListPreference: Flow>
- get() = blocklistDao.getGlobPatterns()
- .mapLatest { patterns ->
- patterns.map { pattern ->
- // Remove start and ending *
- pattern.dropEnds(1, 1)
+ get() =
+ blocklistDao.getGlobPatterns()
+ .mapLatest { patterns ->
+ patterns.map { pattern ->
+ // Remove start and ending *
+ pattern.dropEnds(1, 1)
+ }
}
- }
suspend fun removeBlocklistPattern(value: String) {
blocklistDao.deletePattern(value)
@@ -377,6 +425,7 @@ class SettingsStore(override val di: DI) : DIAware {
)
}
val syncFrequency = _syncFrequency.asStateFlow()
+
fun setSyncFrequency(value: SyncFrequency) {
_syncFrequency.value = value
sp.edit().putString(PREF_SYNC_FREQ, "${value.minutes}").apply()
@@ -393,8 +442,9 @@ class SettingsStore(override val di: DI) : DIAware {
}
if (shouldSync) {
- val constraints = Constraints.Builder()
- .setRequiresCharging(syncOnlyWhenCharging.value)
+ val constraints =
+ Constraints.Builder()
+ .setRequiresCharging(syncOnlyWhenCharging.value)
if (syncOnlyOnWifi.value) {
constraints.setRequiredNetworkType(NetworkType.UNMETERED)
@@ -404,15 +454,17 @@ class SettingsStore(override val di: DI) : DIAware {
val timeInterval = syncFrequency.value.minutes
- val workRequestBuilder = PeriodicWorkRequestBuilder(
- timeInterval,
- TimeUnit.MINUTES,
- )
+ val workRequestBuilder =
+ PeriodicWorkRequestBuilder(
+ timeInterval,
+ TimeUnit.MINUTES,
+ )
- val syncWork = workRequestBuilder
- .setConstraints(constraints.build())
- .addTag("feeder")
- .build()
+ val syncWork =
+ workRequestBuilder
+ .setConstraints(constraints.build())
+ .addTag("feeder")
+ .build()
workManager.enqueueUniquePeriodicWork(
UNIQUE_PERIODIC_NAME,
@@ -646,49 +698,56 @@ fun String.dropEnds(
)
}
-fun linkOpenerFromString(value: String): LinkOpener = when (value) {
- PREF_VAL_OPEN_WITH_BROWSER -> LinkOpener.DEFAULT_BROWSER
- else -> LinkOpener.CUSTOM_TAB
-}
+fun linkOpenerFromString(value: String): LinkOpener =
+ when (value) {
+ PREF_VAL_OPEN_WITH_BROWSER -> LinkOpener.DEFAULT_BROWSER
+ else -> LinkOpener.CUSTOM_TAB
+ }
-fun itemOpenerFromString(value: String) = when (value) {
- PREF_VAL_OPEN_WITH_BROWSER -> ItemOpener.DEFAULT_BROWSER
- PREF_VAL_OPEN_WITH_WEBVIEW,
- PREF_VAL_OPEN_WITH_CUSTOM_TAB,
- -> ItemOpener.CUSTOM_TAB
+fun itemOpenerFromString(value: String) =
+ when (value) {
+ PREF_VAL_OPEN_WITH_BROWSER -> ItemOpener.DEFAULT_BROWSER
+ PREF_VAL_OPEN_WITH_WEBVIEW,
+ PREF_VAL_OPEN_WITH_CUSTOM_TAB,
+ -> ItemOpener.CUSTOM_TAB
- else -> ItemOpener.READER
-}
+ else -> ItemOpener.READER
+ }
-fun sortingOptionsFromString(value: String): SortingOptions = try {
- SortingOptions.valueOf(value.uppercase())
-} catch (_: Exception) {
- SortingOptions.NEWEST_FIRST
-}
+fun sortingOptionsFromString(value: String): SortingOptions =
+ try {
+ SortingOptions.valueOf(value.uppercase())
+ } catch (_: Exception) {
+ SortingOptions.NEWEST_FIRST
+ }
-fun themeOptionsFromString(value: String) = try {
- ThemeOptions.valueOf(value.uppercase())
-} catch (_: Exception) {
- ThemeOptions.SYSTEM
-}
+fun themeOptionsFromString(value: String) =
+ try {
+ ThemeOptions.valueOf(value.uppercase())
+ } catch (_: Exception) {
+ ThemeOptions.SYSTEM
+ }
-fun darkThemePreferenceFromString(value: String): DarkThemePreferences = try {
- DarkThemePreferences.valueOf(value.uppercase())
-} catch (_: Exception) {
- DarkThemePreferences.BLACK
-}
+fun darkThemePreferenceFromString(value: String): DarkThemePreferences =
+ try {
+ DarkThemePreferences.valueOf(value.uppercase())
+ } catch (_: Exception) {
+ DarkThemePreferences.BLACK
+ }
-fun swipeAsReadFromString(value: String): SwipeAsRead = try {
- SwipeAsRead.valueOf(value.uppercase())
-} catch (_: Exception) {
- SwipeAsRead.ONLY_FROM_END
-}
+fun swipeAsReadFromString(value: String): SwipeAsRead =
+ try {
+ SwipeAsRead.valueOf(value.uppercase())
+ } catch (_: Exception) {
+ SwipeAsRead.ONLY_FROM_END
+ }
-fun feedItemStyleFromString(value: String) = try {
- FeedItemStyle.valueOf(value.uppercase())
-} catch (_: Exception) {
- FeedItemStyle.CARD
-}
+fun feedItemStyleFromString(value: String) =
+ try {
+ FeedItemStyle.valueOf(value.uppercase())
+ } catch (_: Exception) {
+ FeedItemStyle.CARD
+ }
fun syncFrequencyFromString(value: String) =
SyncFrequency.values()
diff --git a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SyncRemoteStore.kt b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SyncRemoteStore.kt
index 6d646ff37b..8dabc35552 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/archmodel/SyncRemoteStore.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/archmodel/SyncRemoteStore.kt
@@ -13,13 +13,13 @@ import com.nononsenseapps.feeder.db.room.SyncDeviceDao
import com.nononsenseapps.feeder.db.room.SyncRemote
import com.nononsenseapps.feeder.db.room.SyncRemoteDao
import com.nononsenseapps.feeder.db.room.generateDeviceName
-import java.net.URL
-import java.time.Instant
-import java.time.temporal.ChronoUnit
import kotlinx.coroutines.flow.Flow
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.net.URL
+import java.time.Instant
+import java.time.temporal.ChronoUnit
class SyncRemoteStore(override val di: DI) : DIAware {
private val dao: SyncRemoteDao by instance()
@@ -83,7 +83,10 @@ class SyncRemoteStore(override val di: DI) : DIAware {
readStatusDao.deleteReadStatusSyncForItem(feedItemId)
}
- suspend fun addRemoteReadMark(feedUrl: URL, articleGuid: String) {
+ suspend fun addRemoteReadMark(
+ feedUrl: URL,
+ articleGuid: String,
+ ) {
// Ignores duplicates
remoteReadMarkDao.insert(
RemoteReadMark(
@@ -100,22 +103,21 @@ class SyncRemoteStore(override val di: DI) : DIAware {
remoteReadMarkDao.deleteStaleRemoteReadMarks(now.minus(7, ChronoUnit.DAYS))
}
- suspend fun getRemoteReadMarksReadyToBeApplied() =
- remoteReadMarkDao.getRemoteReadMarksReadyToBeApplied()
+ suspend fun getRemoteReadMarksReadyToBeApplied() = remoteReadMarkDao.getRemoteReadMarksReadyToBeApplied()
- suspend fun getGuidsWhichAreSyncedAsReadInFeed(feedUrl: URL) =
- remoteReadMarkDao.getGuidsWhichAreSyncedAsReadInFeed(feedUrl = feedUrl)
+ suspend fun getGuidsWhichAreSyncedAsReadInFeed(feedUrl: URL) = remoteReadMarkDao.getGuidsWhichAreSyncedAsReadInFeed(feedUrl = feedUrl)
suspend fun replaceWithDefaultSyncRemote() {
dao.replaceWithDefaultSyncRemote()
}
private suspend fun createDefaultSyncRemote(): SyncRemote {
- val remote = SyncRemote(
- id = 1L,
- deviceName = generateDeviceName(),
- secretKey = AesCbcWithIntegrity.generateKey().toString(),
- )
+ val remote =
+ SyncRemote(
+ id = 1L,
+ deviceName = generateDeviceName(),
+ secretKey = AesCbcWithIntegrity.generateKey().toString(),
+ )
dao.insert(remote)
return remote
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareComponentActivity.kt b/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareComponentActivity.kt
index 09debda53d..e14cb9c071 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareComponentActivity.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareComponentActivity.kt
@@ -18,11 +18,12 @@ abstract class DIAwareComponentActivity : ComponentActivity(), DIAware {
extend(parentDI)
bind() with provider { menuInflater }
bind() with instance(this@DIAwareComponentActivity)
- bind() with singleton {
- ActivityLauncher(
- this@DIAwareComponentActivity,
- di.direct.instance(),
- )
- }
+ bind() with
+ singleton {
+ ActivityLauncher(
+ this@DIAwareComponentActivity,
+ di.direct.instance(),
+ )
+ }
}
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareViewModel.kt b/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareViewModel.kt
index d0515b0aaa..e4586e40d8 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareViewModel.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/base/DIAwareViewModel.kt
@@ -10,7 +10,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.savedstate.SavedStateRegistryOwner
-import java.lang.reflect.InvocationTargetException
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.bind
@@ -18,6 +17,7 @@ import org.kodein.di.compose.LocalDI
import org.kodein.di.direct
import org.kodein.di.factory
import org.kodein.di.instance
+import java.lang.reflect.InvocationTargetException
/**
* A view model which is also kodein aware. Construct any deriving class by using the getViewModel()
@@ -49,19 +49,21 @@ class DIAwareViewModelFactory(
}
inline fun DI.Builder.bindWithActivityViewModelScope() {
- bind() with factory { activity: DIAwareComponentActivity ->
- val factory = DIAwareViewModelFactory(activity.di)
+ bind() with
+ factory { activity: DIAwareComponentActivity ->
+ val factory = DIAwareViewModelFactory(activity.di)
- ViewModelProvider(activity, factory).get(T::class.java)
- }
+ ViewModelProvider(activity, factory).get(T::class.java)
+ }
}
inline fun DI.Builder.bindWithComposableViewModelScope() {
- bind() with factory { activity: DIAwareComponentActivity ->
- val factory = DIAwareSavedStateViewModelFactory(activity.di, activity)
+ bind() with
+ factory { activity: DIAwareComponentActivity ->
+ val factory = DIAwareSavedStateViewModelFactory(activity.di, activity)
- ViewModelProvider(activity, factory).get(T::class.java)
- }
+ ViewModelProvider(activity, factory).get(T::class.java)
+ }
}
class DIAwareSavedStateViewModelFactory(
@@ -92,9 +94,7 @@ class DIAwareSavedStateViewModelFactory(
}
@Composable
-inline fun SavedStateRegistryOwner.diAwareViewModel(
- key: String? = null,
-): T {
+inline fun SavedStateRegistryOwner.diAwareViewModel(key: String? = null): T {
val factory = DIAwareSavedStateViewModelFactory(LocalDI.current, this)
return viewModel(
@@ -105,9 +105,7 @@ inline fun SavedStateRegistryOwner.diAwareViewMod
}
@Composable
-inline fun NavBackStackEntry.diAwareViewModel(
- key: String? = null,
-): T {
+inline fun NavBackStackEntry.diAwareViewModel(key: String? = null): T {
val factory = DIAwareSavedStateViewModelFactory(LocalDI.current, this, arguments)
return viewModel(
diff --git a/app/src/main/java/com/nononsenseapps/feeder/blob/Blob.kt b/app/src/main/java/com/nononsenseapps/feeder/blob/Blob.kt
index 9ab37bff3b..b6e9141c40 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/blob/Blob.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/blob/Blob.kt
@@ -7,24 +7,36 @@ import java.io.OutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
-fun blobFile(itemId: Long, filesDir: File): File =
- File(filesDir, "$itemId.txt.gz")
+fun blobFile(
+ itemId: Long,
+ filesDir: File,
+): File = File(filesDir, "$itemId.txt.gz")
@Throws(IOException::class)
-fun blobInputStream(itemId: Long, filesDir: File): InputStream =
- GZIPInputStream(blobFile(itemId = itemId, filesDir = filesDir).inputStream())
+fun blobInputStream(
+ itemId: Long,
+ filesDir: File,
+): InputStream = GZIPInputStream(blobFile(itemId = itemId, filesDir = filesDir).inputStream())
@Throws(IOException::class)
-fun blobOutputStream(itemId: Long, filesDir: File): OutputStream =
- GZIPOutputStream(blobFile(itemId = itemId, filesDir = filesDir).outputStream())
+fun blobOutputStream(
+ itemId: Long,
+ filesDir: File,
+): OutputStream = GZIPOutputStream(blobFile(itemId = itemId, filesDir = filesDir).outputStream())
-fun blobFullFile(itemId: Long, filesDir: File): File =
- File(filesDir, "$itemId.full.html.gz")
+fun blobFullFile(
+ itemId: Long,
+ filesDir: File,
+): File = File(filesDir, "$itemId.full.html.gz")
@Throws(IOException::class)
-fun blobFullInputStream(itemId: Long, filesDir: File): InputStream =
- GZIPInputStream(blobFullFile(itemId = itemId, filesDir = filesDir).inputStream())
+fun blobFullInputStream(
+ itemId: Long,
+ filesDir: File,
+): InputStream = GZIPInputStream(blobFullFile(itemId = itemId, filesDir = filesDir).inputStream())
@Throws(IOException::class)
-fun blobFullOutputStream(itemId: Long, filesDir: File): OutputStream =
- GZIPOutputStream(blobFullFile(itemId = itemId, filesDir = filesDir).outputStream())
+fun blobFullOutputStream(
+ itemId: Long,
+ filesDir: File,
+): OutputStream = GZIPOutputStream(blobFullFile(itemId = itemId, filesDir = filesDir).outputStream())
diff --git a/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RSSContentProvider.kt b/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RSSContentProvider.kt
index ae65d26f4f..04f870d1c3 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RSSContentProvider.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RSSContentProvider.kt
@@ -22,36 +22,45 @@ class RSSContentProvider : ContentProvider(), DIAware {
private val feedDao: FeedDao by instance()
private val feedItemDao: FeedItemDao by instance()
- private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
- addURI(AUTHORITY, RssContentProviderContract.feedsUriPathList, URI_FEED_LIST)
- addURI(
- AUTHORITY,
- RssContentProviderContract.articlesUriPathList,
- URI_ARTICLE_LIST,
- )
- addURI(
- AUTHORITY,
- RssContentProviderContract.articlesUriPathItem,
- URI_ARTICLE_IN_FEED_LIST,
- )
- }
+ private val uriMatcher =
+ UriMatcher(UriMatcher.NO_MATCH).apply {
+ addURI(AUTHORITY, RssContentProviderContract.feedsUriPathList, URI_FEED_LIST)
+ addURI(
+ AUTHORITY,
+ RssContentProviderContract.articlesUriPathList,
+ URI_ARTICLE_LIST,
+ )
+ addURI(
+ AUTHORITY,
+ RssContentProviderContract.articlesUriPathItem,
+ URI_ARTICLE_IN_FEED_LIST,
+ )
+ }
override fun onCreate(): Boolean {
return true
}
- override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int {
+ override fun delete(
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array?,
+ ): Int {
throw UnsupportedOperationException("Not implemented")
}
- override fun getType(uri: Uri): String? = when (uriMatcher.match(uri)) {
- URI_FEED_LIST -> RssContentProviderContract.feedsMimeTypeList
- URI_ARTICLE_LIST -> RssContentProviderContract.articlesMimeTypeList
- URI_ARTICLE_IN_FEED_LIST -> RssContentProviderContract.articlesMimeTypeItem
- else -> null
- }
+ override fun getType(uri: Uri): String? =
+ when (uriMatcher.match(uri)) {
+ URI_FEED_LIST -> RssContentProviderContract.feedsMimeTypeList
+ URI_ARTICLE_LIST -> RssContentProviderContract.articlesMimeTypeList
+ URI_ARTICLE_IN_FEED_LIST -> RssContentProviderContract.articlesMimeTypeItem
+ else -> null
+ }
- override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ override fun insert(
+ uri: Uri,
+ values: ContentValues?,
+ ): Uri? {
throw UnsupportedOperationException("Not implemented")
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RssContentProviderContract.kt b/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RssContentProviderContract.kt
index 68b53e6f09..5111717d80 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RssContentProviderContract.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/contentprovider/RssContentProviderContract.kt
@@ -7,10 +7,11 @@ object RssContentProviderContract {
/**
* Columns available via the content provider
*/
- val feedsColumns = listOf(
- "id",
- "title",
- )
+ val feedsColumns =
+ listOf(
+ "id",
+ "title",
+ )
const val articlesMimeTypeList = "vnd.android.cursor.dir/vnd.rssprovider.items"
const val articlesUriPathList = "articles"
@@ -20,9 +21,10 @@ object RssContentProviderContract {
/**
* Columns available via the content provider
*/
- val articlesColumns = listOf(
- "id",
- "title",
- "text",
- )
+ val articlesColumns =
+ listOf(
+ "id",
+ "title",
+ "text",
+ )
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrity.kt b/app/src/main/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrity.kt
index 9fea9d2494..ba41b407e4 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrity.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/crypto/AesCbcWithIntegrity.kt
@@ -145,24 +145,30 @@ object AesCbcWithIntegrity {
* or a suitable RNG is not available
*/
@Throws(GeneralSecurityException::class)
- fun generateKeyFromPassword(password: String, salt: ByteArray): SecretKeys {
+ fun generateKeyFromPassword(
+ password: String,
+ salt: ByteArray,
+ ): SecretKeys {
// Get enough random bytes for both the AES key and the HMAC key:
- val keySpec: KeySpec = PBEKeySpec(
- password.toCharArray(),
- salt,
- PBE_ITERATION_COUNT,
- AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS,
- )
- val keyFactory = SecretKeyFactory
- .getInstance(PBE_ALGORITHM)
+ val keySpec: KeySpec =
+ PBEKeySpec(
+ password.toCharArray(),
+ salt,
+ PBE_ITERATION_COUNT,
+ AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS,
+ )
+ val keyFactory =
+ SecretKeyFactory
+ .getInstance(PBE_ALGORITHM)
val keyBytes = keyFactory.generateSecret(keySpec).encoded
// Split the random bytes into two parts:
val confidentialityKeyBytes = keyBytes.copyOfRange(0, AES_KEY_LENGTH_BITS / 8)
- val integrityKeyBytes = keyBytes.copyOfRange(
- AES_KEY_LENGTH_BITS / 8,
- AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8,
- )
+ val integrityKeyBytes =
+ keyBytes.copyOfRange(
+ AES_KEY_LENGTH_BITS / 8,
+ AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8,
+ )
// Generate the AES key
val confidentialityKey: SecretKey = SecretKeySpec(confidentialityKeyBytes, CIPHER)
@@ -180,7 +186,10 @@ object AesCbcWithIntegrity {
* @throws GeneralSecurityException
*/
@Throws(GeneralSecurityException::class)
- fun generateKeyFromPassword(password: String, salt: String): SecretKeys {
+ fun generateKeyFromPassword(
+ password: String,
+ salt: String,
+ ): SecretKeys {
return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS))
}
@@ -228,6 +237,7 @@ object AesCbcWithIntegrity {
* Encryption
* -----------------------------------------------------------------
*/
+
/**
* Generates a random IV and encrypts this plain text with the given key. Then attaches
* a hashed MAC, which is contained in the CipherTextIvMac class.
@@ -245,11 +255,12 @@ object AesCbcWithIntegrity {
plaintext: String,
secretKeys: SecretKeys,
encoding: Charset = Charsets.UTF_8,
- ): String = encrypt(
- plaintext = plaintext,
- secretKeys = secretKeys,
- encoding = encoding,
- ).toString()
+ ): String =
+ encrypt(
+ plaintext = plaintext,
+ secretKeys = secretKeys,
+ encoding = encoding,
+ ).toString()
/**
* Generates a random IV and encrypts this plain text with the given key. Then attaches
@@ -282,7 +293,10 @@ object AesCbcWithIntegrity {
* @throws GeneralSecurityException if AES is not implemented on this system
*/
@Throws(GeneralSecurityException::class)
- fun encrypt(plaintext: ByteArray, secretKeys: SecretKeys): CipherTextIvMac {
+ fun encrypt(
+ plaintext: ByteArray,
+ secretKeys: SecretKeys,
+ ): CipherTextIvMac {
var iv = generateIv()
val aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION)
aesCipherForEncryption.init(
@@ -307,6 +321,7 @@ object AesCbcWithIntegrity {
* Decryption
* -----------------------------------------------------------------
*/
+
/**
* AES CBC decrypt.
*
@@ -354,7 +369,10 @@ object AesCbcWithIntegrity {
* @throws GeneralSecurityException if MACs don't match or AES is not implemented
*/
@Throws(GeneralSecurityException::class)
- fun decrypt(civ: CipherTextIvMac, secretKeys: SecretKeys): ByteArray {
+ fun decrypt(
+ civ: CipherTextIvMac,
+ secretKeys: SecretKeys,
+ ): ByteArray {
val ivCipherConcat = CipherTextIvMac.ivCipherConcat(civ.iv, civ.cipherText)
val computedMac = generateMac(ivCipherConcat, secretKeys.integrityKey)
return if (constantTimeEq(computedMac, civ.mac)) {
@@ -375,6 +393,7 @@ object AesCbcWithIntegrity {
* Helper Code
* -----------------------------------------------------------------
*/
+
/**
* Generate the mac based on HMAC_ALGORITHM
* @param integrityKey The key used for hmac
@@ -384,7 +403,10 @@ object AesCbcWithIntegrity {
* @throws InvalidKeyException
*/
@Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
- fun generateMac(byteCipherText: ByteArray, integrityKey: SecretKey): ByteArray {
+ fun generateMac(
+ byteCipherText: ByteArray,
+ integrityKey: SecretKey,
+ ): ByteArray {
// Now compute the mac for later integrity checking
val sha256HMAC = Mac.getInstance(HMAC_ALGORITHM)
sha256HMAC.init(integrityKey)
@@ -397,7 +419,10 @@ object AesCbcWithIntegrity {
* @param b
* @return true iff the arrays are exactly equal.
*/
- private fun constantTimeEq(a: ByteArray, b: ByteArray): Boolean {
+ private fun constantTimeEq(
+ a: ByteArray,
+ b: ByteArray,
+ ): Boolean {
if (a.size != b.size) {
return false
}
@@ -422,14 +447,16 @@ class SecretKeys(
* @return base64(confidentialityKey):base64(integrityKey)
*/
override fun toString(): String {
- val a = Base64.encodeToString(
- confidentialityKey.encoded,
- AesCbcWithIntegrity.BASE64_FLAGS,
- )
- val b = Base64.encodeToString(
- integrityKey.encoded,
- AesCbcWithIntegrity.BASE64_FLAGS,
- )
+ val a =
+ Base64.encodeToString(
+ confidentialityKey.encoded,
+ AesCbcWithIntegrity.BASE64_FLAGS,
+ )
+ val b =
+ Base64.encodeToString(
+ integrityKey.encoded,
+ AesCbcWithIntegrity.BASE64_FLAGS,
+ )
return "$a:$b"
}
@@ -528,7 +555,10 @@ class CipherTextIvMac {
* @param cipherText the cipherText to append
* @return iv:cipherText, a new byte array.
*/
- fun ivCipherConcat(iv: ByteArray, cipherText: ByteArray): ByteArray {
+ fun ivCipherConcat(
+ iv: ByteArray,
+ cipherText: ByteArray,
+ ): ByteArray {
val combined = ByteArray(iv.size + cipherText.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(cipherText, 0, combined, iv.size, cipherText.size)
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/AppDatabase.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/AppDatabase.kt
index 5084efd925..00904acc62 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/AppDatabase.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/AppDatabase.kt
@@ -55,12 +55,19 @@ private const val LOG_TAG = "FEEDER_APPDB"
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun feedDao(): FeedDao
+
abstract fun feedItemDao(): FeedItemDao
+
abstract fun blocklistDao(): BlocklistDao
+
abstract fun syncRemoteDao(): SyncRemoteDao
+
abstract fun readStatusSyncedDao(): ReadStatusSyncedDao
+
abstract fun remoteReadMarkDao(): RemoteReadMarkDao
+
abstract fun remoteFeedDao(): RemoteFeedDao
+
abstract fun syncDeviceDao(): SyncDeviceDao
companion object {
@@ -91,34 +98,35 @@ abstract class AppDatabase : RoomDatabase() {
}
// 17-20 were never part of any release, just made for easier testing
-fun getAllMigrations(di: DI) = arrayOf(
- MIGRATION_5_7,
- MIGRATION_6_7,
- MIGRATION_7_8,
- MIGRATION_8_9,
- MIGRATION_9_10,
- MIGRATION_10_11,
- MIGRATION_11_12,
- MIGRATION_12_13,
- MIGRATION_13_14,
- MIGRATION_14_15,
- MIGRATION_15_16,
- MIGRATION_16_17,
- MIGRATION_17_18,
- MIGRATION_18_19,
- MIGRATION_19_20,
- MIGRATION_20_21,
- MIGRATION_21_22,
- MIGRATION_22_23,
- MigrationFrom23To24(di),
- MigrationFrom24To25(di),
- MigrationFrom25To26(di),
- MigrationFrom26To27(di),
- MigrationFrom27To28(di),
- MigrationFrom28To29(di),
- MigrationFrom29To30(di),
- MigrationFrom30To31(di),
-)
+fun getAllMigrations(di: DI) =
+ arrayOf(
+ MIGRATION_5_7,
+ MIGRATION_6_7,
+ MIGRATION_7_8,
+ MIGRATION_8_9,
+ MIGRATION_9_10,
+ MIGRATION_10_11,
+ MIGRATION_11_12,
+ MIGRATION_12_13,
+ MIGRATION_13_14,
+ MIGRATION_14_15,
+ MIGRATION_15_16,
+ MIGRATION_16_17,
+ MIGRATION_17_18,
+ MIGRATION_18_19,
+ MIGRATION_19_20,
+ MIGRATION_20_21,
+ MIGRATION_21_22,
+ MIGRATION_22_23,
+ MigrationFrom23To24(di),
+ MigrationFrom24To25(di),
+ MigrationFrom25To26(di),
+ MigrationFrom26To27(di),
+ MigrationFrom27To28(di),
+ MigrationFrom28To29(di),
+ MigrationFrom29To30(di),
+ MigrationFrom30To31(di),
+ )
/*
* 6 represents legacy database
@@ -128,7 +136,7 @@ class MigrationFrom30To31(override val di: DI) : Migration(30, 31), DIAware {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
- alter table feed_items add column word_count_full integer not null default 0
+ alter table feed_items add column word_count_full integer not null default 0
""".trimIndent(),
)
}
@@ -138,7 +146,7 @@ class MigrationFrom29To30(override val di: DI) : Migration(29, 30), DIAware {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
- alter table feed_items add column word_count integer not null default 0
+ alter table feed_items add column word_count integer not null default 0
""".trimIndent(),
)
}
@@ -148,7 +156,7 @@ class MigrationFrom28To29(override val di: DI) : Migration(28, 29), DIAware {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
- alter table feed_items add column enclosure_type text
+ alter table feed_items add column enclosure_type text
""".trimIndent(),
)
}
@@ -158,7 +166,7 @@ class MigrationFrom27To28(override val di: DI) : Migration(27, 28), DIAware {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
- alter table feeds add column site_fetched integer not null default 0
+ alter table feeds add column site_fetched integer not null default 0
""".trimIndent(),
)
}
@@ -260,8 +268,9 @@ class MigrationFrom23To24(override val di: DI) : Migration(23, 24), DIAware {
""".trimIndent(),
)
- val blocks = sharedPrefs.getStringSet("pref_block_list_values", null)
- ?: emptySet()
+ val blocks =
+ sharedPrefs.getStringSet("pref_block_list_values", null)
+ ?: emptySet()
if (blocks.isNotEmpty()) {
// ('*foo*'), ('*bar*'), ('*car*')
@@ -331,30 +340,30 @@ object MIGRATION_20_21 : Migration(20, 21) {
)
database.execSQL(
"""
- CREATE TABLE IF NOT EXISTS `remote_feed` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sync_remote` INTEGER NOT NULL, `url` TEXT NOT NULL, FOREIGN KEY(`sync_remote`) REFERENCES `sync_remote`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
+ CREATE TABLE IF NOT EXISTS `remote_feed` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sync_remote` INTEGER NOT NULL, `url` TEXT NOT NULL, FOREIGN KEY(`sync_remote`) REFERENCES `sync_remote`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
""".trimIndent(),
)
database.execSQL(
"""
- CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_feed_sync_remote_url` ON `remote_feed` (`sync_remote`, `url`)
+ CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_feed_sync_remote_url` ON `remote_feed` (`sync_remote`, `url`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_remote_feed_url` ON `remote_feed` (`url`)
+ CREATE INDEX IF NOT EXISTS `index_remote_feed_url` ON `remote_feed` (`url`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_remote_feed_sync_remote` ON `remote_feed` (`sync_remote`)
+ CREATE INDEX IF NOT EXISTS `index_remote_feed_sync_remote` ON `remote_feed` (`sync_remote`)
""".trimIndent(),
)
// And generate encryption key
database.execSQL(
"""
- UPDATE sync_remote
- SET secret_key = ?
- WHERE id IS 1
+ UPDATE sync_remote
+ SET secret_key = ?
+ WHERE id IS 1
""".trimIndent(),
arrayOf(AesCbcWithIntegrity.generateKey().toString()),
)
@@ -378,17 +387,17 @@ object MIGRATION_19_20 : Migration(19, 20) {
)
database.execSQL(
"""
- CREATE TABLE IF NOT EXISTS `sync_device` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sync_remote` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `device_name` TEXT NOT NULL, FOREIGN KEY(`sync_remote`) REFERENCES `sync_remote`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
+ CREATE TABLE IF NOT EXISTS `sync_device` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `sync_remote` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `device_name` TEXT NOT NULL, FOREIGN KEY(`sync_remote`) REFERENCES `sync_remote`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
""".trimIndent(),
)
database.execSQL(
"""
- CREATE UNIQUE INDEX IF NOT EXISTS `index_sync_device_sync_remote_device_id` ON `sync_device` (`sync_remote`, `device_id`)
+ CREATE UNIQUE INDEX IF NOT EXISTS `index_sync_device_sync_remote_device_id` ON `sync_device` (`sync_remote`, `device_id`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_sync_device_sync_remote` ON `sync_device` (`sync_remote`)
+ CREATE INDEX IF NOT EXISTS `index_sync_device_sync_remote` ON `sync_device` (`sync_remote`)
""".trimIndent(),
)
}
@@ -404,22 +413,22 @@ object MIGRATION_18_19 : Migration(18, 19) {
)
database.execSQL(
"""
- CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_read_mark_sync_remote_feed_url_guid` ON `remote_read_mark` (`sync_remote`, `feed_url`, `guid`)
+ CREATE UNIQUE INDEX IF NOT EXISTS `index_remote_read_mark_sync_remote_feed_url_guid` ON `remote_read_mark` (`sync_remote`, `feed_url`, `guid`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_remote_read_mark_feed_url_guid` ON `remote_read_mark` (`feed_url`, `guid`)
+ CREATE INDEX IF NOT EXISTS `index_remote_read_mark_feed_url_guid` ON `remote_read_mark` (`feed_url`, `guid`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_remote_read_mark_sync_remote` ON `remote_read_mark` (`sync_remote`)
+ CREATE INDEX IF NOT EXISTS `index_remote_read_mark_sync_remote` ON `remote_read_mark` (`sync_remote`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_remote_read_mark_timestamp` ON `remote_read_mark` (`timestamp`)
+ CREATE INDEX IF NOT EXISTS `index_remote_read_mark_timestamp` ON `remote_read_mark` (`timestamp`)
""".trimIndent(),
)
}
@@ -435,17 +444,17 @@ object MIGRATION_17_18 : Migration(17, 18) {
)
database.execSQL(
"""
- CREATE UNIQUE INDEX IF NOT EXISTS `index_read_status_synced_feed_item_sync_remote` ON `read_status_synced` (`feed_item`, `sync_remote`)
+ CREATE UNIQUE INDEX IF NOT EXISTS `index_read_status_synced_feed_item_sync_remote` ON `read_status_synced` (`feed_item`, `sync_remote`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_read_status_synced_feed_item` ON `read_status_synced` (`feed_item`);
+ CREATE INDEX IF NOT EXISTS `index_read_status_synced_feed_item` ON `read_status_synced` (`feed_item`);
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX IF NOT EXISTS `index_read_status_synced_sync_remote` ON `read_status_synced` (`sync_remote`);
+ CREATE INDEX IF NOT EXISTS `index_read_status_synced_sync_remote` ON `read_status_synced` (`sync_remote`);
""".trimIndent(),
)
}
@@ -629,73 +638,77 @@ object MIGRATION_5_7 : Migration(5, 7) {
}
}
-private fun legacyMigration(database: SupportSQLiteDatabase, version: Int) {
+private fun legacyMigration(
+ database: SupportSQLiteDatabase,
+ version: Int,
+) {
// Create new tables and indices
// Feeds
database.execSQL(
"""
- CREATE TABLE IF NOT EXISTS `feeds` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `custom_title` TEXT NOT NULL, `url` TEXT NOT NULL, `tag` TEXT NOT NULL, `notify` INTEGER NOT NULL, `image_url` TEXT)
+ CREATE TABLE IF NOT EXISTS `feeds` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `custom_title` TEXT NOT NULL, `url` TEXT NOT NULL, `tag` TEXT NOT NULL, `notify` INTEGER NOT NULL, `image_url` TEXT)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE UNIQUE INDEX `index_Feed_url` ON `feeds` (`url`)
+ CREATE UNIQUE INDEX `index_Feed_url` ON `feeds` (`url`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE UNIQUE INDEX `index_Feed_id_url_title` ON `feeds` (`id`, `url`, `title`)
+ CREATE UNIQUE INDEX `index_Feed_id_url_title` ON `feeds` (`id`, `url`, `title`)
""".trimIndent(),
)
// Items
database.execSQL(
"""
- CREATE TABLE IF NOT EXISTS `feed_items` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `guid` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `plain_title` TEXT NOT NULL, `plain_snippet` TEXT NOT NULL, `image_url` TEXT, `enclosure_link` TEXT, `author` TEXT, `pub_date` TEXT, `link` TEXT, `unread` INTEGER NOT NULL, `notified` INTEGER NOT NULL, `feed_id` INTEGER, FOREIGN KEY(`feed_id`) REFERENCES `feeds`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
+ CREATE TABLE IF NOT EXISTS `feed_items` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `guid` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `plain_title` TEXT NOT NULL, `plain_snippet` TEXT NOT NULL, `image_url` TEXT, `enclosure_link` TEXT, `author` TEXT, `pub_date` TEXT, `link` TEXT, `unread` INTEGER NOT NULL, `notified` INTEGER NOT NULL, `feed_id` INTEGER, FOREIGN KEY(`feed_id`) REFERENCES `feeds`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )
""".trimIndent(),
)
database.execSQL(
"""
- CREATE UNIQUE INDEX `index_feed_item_guid_feed_id` ON `feed_items` (`guid`, `feed_id`)
+ CREATE UNIQUE INDEX `index_feed_item_guid_feed_id` ON `feed_items` (`guid`, `feed_id`)
""".trimIndent(),
)
database.execSQL(
"""
- CREATE INDEX `index_feed_item_feed_id` ON `feed_items` (`feed_id`)
+ CREATE INDEX `index_feed_item_feed_id` ON `feed_items` (`feed_id`)
""".trimIndent(),
)
// Migrate to new tables
database.query(
"""
- SELECT _id, title, url, tag, customtitle, notify ${if (version == 6) ", imageUrl" else ""}
- FROM Feed
+ SELECT _id, title, url, tag, customtitle, notify ${if (version == 6) ", imageUrl" else ""}
+ FROM Feed
""".trimIndent(),
).use { cursor ->
cursor.forEach { _ ->
val oldFeedId = cursor.getLong(0)
- val newFeedId = database.insert(
- "feeds",
- SQLiteDatabase.CONFLICT_FAIL,
- contentValues {
- setString("title" to cursor.getString(1))
- setString("custom_title" to cursor.getString(4))
- setString("url" to cursor.getString(2))
- setString("tag" to cursor.getString(3))
- setInt("notify" to cursor.getInt(5))
- if (version == 6) {
- setString("image_url" to cursor.getString(6))
- }
- },
- )
+ val newFeedId =
+ database.insert(
+ "feeds",
+ SQLiteDatabase.CONFLICT_FAIL,
+ contentValues {
+ setString("title" to cursor.getString(1))
+ setString("custom_title" to cursor.getString(4))
+ setString("url" to cursor.getString(2))
+ setString("tag" to cursor.getString(3))
+ setInt("notify" to cursor.getInt(5))
+ if (version == 6) {
+ setString("image_url" to cursor.getString(6))
+ }
+ },
+ )
database.query(
"""
- SELECT title, description, plainTitle, plainSnippet, imageUrl, link, author,
- pubdate, unread, feed, enclosureLink, notified, guid
- FROM FeedItem
- WHERE feed = $oldFeedId
+ SELECT title, description, plainTitle, plainSnippet, imageUrl, link, author,
+ pubdate, unread, feed, enclosureLink, notified, guid
+ FROM FeedItem
+ WHERE feed = $oldFeedId
""".trimIndent(),
).use { cursor ->
database.inTransaction {
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/BlocklistEntry.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/BlocklistEntry.kt
index 677aaa6040..b4c70c96e4 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/BlocklistEntry.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/BlocklistEntry.kt
@@ -14,11 +14,13 @@ import com.nononsenseapps.feeder.db.COL_ID
Index(value = [COL_GLOB_PATTERN], unique = true),
],
)
-data class BlocklistEntry @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_GLOB_PATTERN) var globPattern: String = "",
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class BlocklistEntry
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_GLOB_PATTERN) var globPattern: String = "",
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/Converters.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/Converters.kt
index 7146e3f3d9..07da38fbae 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/Converters.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/Converters.kt
@@ -7,7 +7,6 @@ import java.time.Instant
import java.time.ZonedDateTime
class Converters {
-
@TypeConverter
fun dateTimeFromString(value: String?): ZonedDateTime? {
var dt: ZonedDateTime? = null
@@ -21,16 +20,13 @@ class Converters {
}
@TypeConverter
- fun stringFromDateTime(value: ZonedDateTime?): String? =
- value?.toString()
+ fun stringFromDateTime(value: ZonedDateTime?): String? = value?.toString()
@TypeConverter
- fun stringFromURL(value: URL?): String? =
- value?.toString()
+ fun stringFromURL(value: URL?): String? = value?.toString()
@TypeConverter
- fun urlFromString(value: String?): URL? =
- value?.let { sloppyLinkToStrictURLNoThrows(it) }
+ fun urlFromString(value: String?): URL? = value?.let { sloppyLinkToStrictURLNoThrows(it) }
@TypeConverter
fun instantFromLong(value: Long?): Instant? =
@@ -41,6 +37,5 @@ class Converters {
}
@TypeConverter
- fun longFromInstant(value: Instant?): Long? =
- value?.toEpochMilli()
+ fun longFromInstant(value: Instant?): Long? = value?.toEpochMilli()
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/Feed.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/Feed.kt
index 3ff76e3dfb..afc6fecf03 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/Feed.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/Feed.kt
@@ -33,28 +33,30 @@ const val OPEN_ARTICLE_WITH_APPLICATION_DEFAULT = ""
Index(value = [COL_ID, COL_URL, COL_TITLE], unique = true),
],
)
-data class Feed @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_TITLE) var title: String = "",
- @ColumnInfo(name = COL_CUSTOM_TITLE) var customTitle: String = "",
- @ColumnInfo(name = COL_URL) var url: URL = URL("http://"),
- @ColumnInfo(name = COL_TAG) var tag: String = "",
- @ColumnInfo(name = COL_NOTIFY) var notify: Boolean = false,
- @ColumnInfo(name = COL_IMAGEURL) var imageUrl: URL? = null,
- @ColumnInfo(name = COL_LASTSYNC, typeAffinity = ColumnInfo.INTEGER) var lastSync: Instant = Instant.EPOCH,
- @ColumnInfo(name = COL_RESPONSEHASH) var responseHash: Int = 0,
- @ColumnInfo(name = COL_FULLTEXT_BY_DEFAULT) var fullTextByDefault: Boolean = false,
- @ColumnInfo(name = COL_OPEN_ARTICLES_WITH) var openArticlesWith: String = OPEN_ARTICLE_WITH_APPLICATION_DEFAULT,
- @ColumnInfo(name = COL_ALTERNATE_ID) var alternateId: Boolean = false,
- @ColumnInfo(name = COL_CURRENTLY_SYNCING) var currentlySyncing: Boolean = false,
- // Only update this field when user modifies the feed
- @ColumnInfo(name = COL_WHEN_MODIFIED) var whenModified: Instant = Instant.EPOCH,
- @ColumnInfo(name = COL_SITE_FETCHED) var siteFetched: Instant = Instant.EPOCH,
-) {
- constructor() : this(id = ID_UNSET)
+data class Feed
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_TITLE) var title: String = "",
+ @ColumnInfo(name = COL_CUSTOM_TITLE) var customTitle: String = "",
+ @ColumnInfo(name = COL_URL) var url: URL = URL("http://"),
+ @ColumnInfo(name = COL_TAG) var tag: String = "",
+ @ColumnInfo(name = COL_NOTIFY) var notify: Boolean = false,
+ @ColumnInfo(name = COL_IMAGEURL) var imageUrl: URL? = null,
+ @ColumnInfo(name = COL_LASTSYNC, typeAffinity = ColumnInfo.INTEGER) var lastSync: Instant = Instant.EPOCH,
+ @ColumnInfo(name = COL_RESPONSEHASH) var responseHash: Int = 0,
+ @ColumnInfo(name = COL_FULLTEXT_BY_DEFAULT) var fullTextByDefault: Boolean = false,
+ @ColumnInfo(name = COL_OPEN_ARTICLES_WITH) var openArticlesWith: String = OPEN_ARTICLE_WITH_APPLICATION_DEFAULT,
+ @ColumnInfo(name = COL_ALTERNATE_ID) var alternateId: Boolean = false,
+ @ColumnInfo(name = COL_CURRENTLY_SYNCING) var currentlySyncing: Boolean = false,
+ // Only update this field when user modifies the feed
+ @ColumnInfo(name = COL_WHEN_MODIFIED) var whenModified: Instant = Instant.EPOCH,
+ @ColumnInfo(name = COL_SITE_FETCHED) var siteFetched: Instant = Instant.EPOCH,
+ ) {
+ constructor() : this(id = ID_UNSET)
- val displayTitle: String
- get() = (customTitle.ifBlank { title })
-}
+ val displayTitle: String
+ get() = (customTitle.ifBlank { title })
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedDao.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedDao.kt
index c219bc1764..34e2ff7dc2 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedDao.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedDao.kt
@@ -13,13 +13,12 @@ import com.nononsenseapps.feeder.db.COL_ID
import com.nononsenseapps.feeder.db.COL_TAG
import com.nononsenseapps.feeder.db.COL_TITLE
import com.nononsenseapps.feeder.model.FeedUnreadCount
+import kotlinx.coroutines.flow.Flow
import java.net.URL
import java.time.Instant
-import kotlinx.coroutines.flow.Flow
@Dao
interface FeedDao {
-
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFeed(feed: Feed): Long
@@ -70,13 +69,19 @@ interface FeedDao {
AND last_sync < :staleTime
""",
)
- suspend fun loadFeedIfStale(feedId: Long, staleTime: Long): Feed?
+ suspend fun loadFeedIfStale(
+ feedId: Long,
+ staleTime: Long,
+ ): Feed?
@Query("SELECT * FROM feeds WHERE tag IS :tag")
suspend fun loadFeeds(tag: String): List
@Query("SELECT * FROM feeds WHERE tag IS :tag AND last_sync < :staleTime")
- suspend fun loadFeedsIfStale(tag: String, staleTime: Long): List
+ suspend fun loadFeedsIfStale(
+ tag: String,
+ staleTime: Long,
+ ): List
@Query("SELECT * FROM feeds")
suspend fun loadFeeds(): List
@@ -115,10 +120,16 @@ interface FeedDao {
fun loadFlowOfFeedsWithUnreadCounts(): Flow>
@Query("UPDATE feeds SET notify = :notify WHERE id IS :id")
- suspend fun setNotify(id: Long, notify: Boolean)
+ suspend fun setNotify(
+ id: Long,
+ notify: Boolean,
+ )
@Query("UPDATE feeds SET notify = :notify WHERE tag IS :tag")
- suspend fun setNotify(tag: String, notify: Boolean)
+ suspend fun setNotify(
+ tag: String,
+ notify: Boolean,
+ )
@Query("UPDATE feeds SET notify = :notify")
suspend fun setAllNotify(notify: Boolean)
@@ -161,7 +172,10 @@ interface FeedDao {
WHERE id IS :feedId
""",
)
- suspend fun setCurrentlySyncingOn(feedId: Long, syncing: Boolean)
+ suspend fun setCurrentlySyncingOn(
+ feedId: Long,
+ syncing: Boolean,
+ )
@Query(
"""
@@ -170,7 +184,11 @@ interface FeedDao {
WHERE id IS :feedId
""",
)
- suspend fun setCurrentlySyncingOn(feedId: Long, syncing: Boolean, lastSync: Instant)
+ suspend fun setCurrentlySyncingOn(
+ feedId: Long,
+ syncing: Boolean,
+ lastSync: Instant,
+ )
@Query(
"""
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedForSettings.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedForSettings.kt
index 44e3137f3d..e3d0d0aa00 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedForSettings.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedForSettings.kt
@@ -6,10 +6,12 @@ import com.nononsenseapps.feeder.db.COL_ID
import com.nononsenseapps.feeder.db.COL_NOTIFY
import com.nononsenseapps.feeder.db.COL_TITLE
-data class FeedForSettings @Ignore constructor(
- @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_TITLE) var title: String = "",
- @ColumnInfo(name = COL_NOTIFY) var notify: Boolean = false,
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class FeedForSettings
+ @Ignore
+ constructor(
+ @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_TITLE) var title: String = "",
+ @ColumnInfo(name = COL_NOTIFY) var notify: Boolean = false,
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItem.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItem.kt
index d1bd8ade3b..73d13f4d15 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItem.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItem.kt
@@ -62,140 +62,145 @@ private val patternWhitespace = "\\s+".toRegex()
),
],
)
-data class FeedItem @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- override var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_GUID) var guid: String = "",
- @Deprecated("This is never different from plainTitle", replaceWith = ReplaceWith("plainTitle"))
- @ColumnInfo(name = COL_TITLE)
- var title: String = "",
- @ColumnInfo(name = COL_PLAINTITLE) var plainTitle: String = "",
- @ColumnInfo(name = COL_PLAINSNIPPET) var plainSnippet: String = "",
- @ColumnInfo(name = COL_IMAGEURL) var imageUrl: String? = null,
- @ColumnInfo(name = COL_ENCLOSURELINK) var enclosureLink: String? = null,
- @ColumnInfo(name = COL_ENCLOSURE_TYPE) var enclosureType: String? = null,
- @ColumnInfo(name = COL_AUTHOR) var author: String? = null,
- @ColumnInfo(
- name = COL_PUBDATE,
- typeAffinity = ColumnInfo.TEXT,
- ) override var pubDate: ZonedDateTime? = null,
- @ColumnInfo(name = COL_LINK) override var link: String? = null,
- @Deprecated(
- "This column has been 'removed' but sqlite doesn't support drop column.",
- replaceWith = ReplaceWith("readTime"),
- )
- @ColumnInfo(name = "unread")
- var oldUnread: Boolean = true,
- @ColumnInfo(name = COL_NOTIFIED) var notified: Boolean = false,
- @ColumnInfo(name = COL_FEEDID) var feedId: Long? = null,
- @ColumnInfo(
- name = COL_FIRSTSYNCEDTIME,
- typeAffinity = ColumnInfo.INTEGER,
- ) var firstSyncedTime: Instant = Instant.EPOCH,
- @ColumnInfo(
- name = COL_PRIMARYSORTTIME,
- typeAffinity = ColumnInfo.INTEGER,
- ) override var primarySortTime: Instant = Instant.EPOCH,
- @Deprecated("This column has been 'removed' but sqlite doesn't support drop column.")
- @ColumnInfo(name = "pinned")
- var oldPinned: Boolean = false,
- @ColumnInfo(name = COL_BOOKMARKED) var bookmarked: Boolean = false,
- @ColumnInfo(name = COL_FULLTEXT_DOWNLOADED) var fullTextDownloaded: Boolean = false,
- @ColumnInfo(
- name = COL_READ_TIME,
- typeAffinity = ColumnInfo.INTEGER,
- ) var readTime: Instant? = null,
- @ColumnInfo(name = COL_WORD_COUNT) var wordCount: Int = 0,
- @ColumnInfo(name = COL_WORD_COUNT_FULL) var wordCountFull: Int = 0,
-) : FeedItemForFetching, FeedItemCursor {
-
- constructor() : this(id = ID_UNSET)
-
- val unread: Boolean
- get() = readTime == null
-
- fun updateFromParsedEntry(
- entry: Item,
- entryGuid: String,
- feed: com.nononsenseapps.jsonfeed.Feed,
- ) {
- val converter = HtmlToPlainTextConverter()
- // Be careful about nulls.
- val plainText = converter.convert(
- entry.content_html
- ?: entry.content_text
- ?: "",
+data class FeedItem
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ override var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_GUID) var guid: String = "",
+ @Deprecated("This is never different from plainTitle", replaceWith = ReplaceWith("plainTitle"))
+ @ColumnInfo(name = COL_TITLE)
+ var title: String = "",
+ @ColumnInfo(name = COL_PLAINTITLE) var plainTitle: String = "",
+ @ColumnInfo(name = COL_PLAINSNIPPET) var plainSnippet: String = "",
+ @ColumnInfo(name = COL_IMAGEURL) var imageUrl: String? = null,
+ @ColumnInfo(name = COL_ENCLOSURELINK) var enclosureLink: String? = null,
+ @ColumnInfo(name = COL_ENCLOSURE_TYPE) var enclosureType: String? = null,
+ @ColumnInfo(name = COL_AUTHOR) var author: String? = null,
+ @ColumnInfo(
+ name = COL_PUBDATE,
+ typeAffinity = ColumnInfo.TEXT,
+ ) override var pubDate: ZonedDateTime? = null,
+ @ColumnInfo(name = COL_LINK) override var link: String? = null,
+ @Deprecated(
+ "This column has been 'removed' but sqlite doesn't support drop column.",
+ replaceWith = ReplaceWith("readTime"),
)
- this.wordCount = estimateWordCount(plainText)
-
- val summary: String = (
- entry.summary
- ?: entry.content_text
- ?: plainText
- ).take(MAX_SNIPPET_LENGTH)
-
- // Make double sure no base64 images are used as thumbnails
- val safeImage = when {
- entry.image?.startsWith("data") == true -> null
- else -> entry.image
- }
+ @ColumnInfo(name = "unread")
+ var oldUnread: Boolean = true,
+ @ColumnInfo(name = COL_NOTIFIED) var notified: Boolean = false,
+ @ColumnInfo(name = COL_FEEDID) var feedId: Long? = null,
+ @ColumnInfo(
+ name = COL_FIRSTSYNCEDTIME,
+ typeAffinity = ColumnInfo.INTEGER,
+ ) var firstSyncedTime: Instant = Instant.EPOCH,
+ @ColumnInfo(
+ name = COL_PRIMARYSORTTIME,
+ typeAffinity = ColumnInfo.INTEGER,
+ ) override var primarySortTime: Instant = Instant.EPOCH,
+ @Deprecated("This column has been 'removed' but sqlite doesn't support drop column.")
+ @ColumnInfo(name = "pinned")
+ var oldPinned: Boolean = false,
+ @ColumnInfo(name = COL_BOOKMARKED) var bookmarked: Boolean = false,
+ @ColumnInfo(name = COL_FULLTEXT_DOWNLOADED) var fullTextDownloaded: Boolean = false,
+ @ColumnInfo(
+ name = COL_READ_TIME,
+ typeAffinity = ColumnInfo.INTEGER,
+ ) var readTime: Instant? = null,
+ @ColumnInfo(name = COL_WORD_COUNT) var wordCount: Int = 0,
+ @ColumnInfo(name = COL_WORD_COUNT_FULL) var wordCountFull: Int = 0,
+ ) : FeedItemForFetching, FeedItemCursor {
+ constructor() : this(id = ID_UNSET)
+
+ val unread: Boolean
+ get() = readTime == null
+
+ fun updateFromParsedEntry(
+ entry: Item,
+ entryGuid: String,
+ feed: com.nononsenseapps.jsonfeed.Feed,
+ ) {
+ val converter = HtmlToPlainTextConverter()
+ // Be careful about nulls.
+ val plainText =
+ converter.convert(
+ entry.content_html
+ ?: entry.content_text
+ ?: "",
+ )
+ this.wordCount = estimateWordCount(plainText)
+
+ val summary: String =
+ (
+ entry.summary
+ ?: entry.content_text
+ ?: plainText
+ ).take(MAX_SNIPPET_LENGTH)
+
+ // Make double sure no base64 images are used as thumbnails
+ val safeImage =
+ when {
+ entry.image?.startsWith("data") == true -> null
+ else -> entry.image
+ }
- val absoluteImage = when {
- feed.feed_url != null && safeImage != null -> {
- relativeLinkIntoAbsolute(sloppyLinkToStrictURL(feed.feed_url), safeImage)
- }
+ val absoluteImage =
+ when {
+ feed.feed_url != null && safeImage != null -> {
+ relativeLinkIntoAbsolute(sloppyLinkToStrictURL(feed.feed_url), safeImage)
+ }
- else -> safeImage
- }
+ else -> safeImage
+ }
- this.guid = entryGuid
- entry.title?.let { this.plainTitle = it.take(MAX_TITLE_LENGTH) }
- @Suppress("DEPRECATION")
- this.title = this.plainTitle
- this.plainSnippet = summary
-
- this.imageUrl = absoluteImage
- val firstEnclosure = entry.attachments?.firstOrNull()
- this.enclosureLink = firstEnclosure?.url
- this.enclosureType = firstEnclosure?.mime_type?.lowercase()
-
- this.author = entry.author?.name ?: feed.author?.name
- this.link = entry.url
-
- this.pubDate =
- try {
- // Allow an actual pubdate to be updated
- ZonedDateTime.parse(entry.date_published)
- } catch (t: Throwable) {
- // If a pubdate is missing, then don't update if one is already set
- this.pubDate ?: ZonedDateTime.now(ZoneOffset.UTC)
- }
- primarySortTime = minOf(firstSyncedTime, pubDate?.toInstant() ?: firstSyncedTime)
- }
+ this.guid = entryGuid
+ entry.title?.let { this.plainTitle = it.take(MAX_TITLE_LENGTH) }
+ @Suppress("DEPRECATION")
+ this.title = this.plainTitle
+ this.plainSnippet = summary
+
+ this.imageUrl = absoluteImage
+ val firstEnclosure = entry.attachments?.firstOrNull()
+ this.enclosureLink = firstEnclosure?.url
+ this.enclosureType = firstEnclosure?.mime_type?.lowercase()
- val enclosureFilename: String?
- get() {
- enclosureLink?.let { enclosureLink ->
- var fname: String? = null
+ this.author = entry.author?.name ?: feed.author?.name
+ this.link = entry.url
+
+ this.pubDate =
try {
- fname = URI(enclosureLink).path.split("/").last()
- } catch (_: Exception) {
+ // Allow an actual pubdate to be updated
+ ZonedDateTime.parse(entry.date_published)
+ } catch (t: Throwable) {
+ // If a pubdate is missing, then don't update if one is already set
+ this.pubDate ?: ZonedDateTime.now(ZoneOffset.UTC)
}
- return if (fname.isNullOrEmpty()) {
- null
- } else {
- fname
+ primarySortTime = minOf(firstSyncedTime, pubDate?.toInstant() ?: firstSyncedTime)
+ }
+
+ val enclosureFilename: String?
+ get() {
+ enclosureLink?.let { enclosureLink ->
+ var fname: String? = null
+ try {
+ fname = URI(enclosureLink).path.split("/").last()
+ } catch (_: Exception) {
+ }
+ return if (fname.isNullOrEmpty()) {
+ null
+ } else {
+ fname
+ }
}
+ return null
}
- return null
- }
- val domain: String?
- get() {
- return (enclosureLink ?: link)?.host()
- }
-}
+ val domain: String?
+ get() {
+ return (enclosureLink ?: link)?.host()
+ }
+ }
interface FeedItemForFetching {
val id: Long
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemDao.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemDao.kt
index e09fde312a..4152224ead 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemDao.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemDao.kt
@@ -17,9 +17,9 @@ import com.nononsenseapps.feeder.db.COL_URL
import com.nononsenseapps.feeder.db.FEEDS_TABLE_NAME
import com.nononsenseapps.feeder.model.PreviewItem
import com.nononsenseapps.feeder.model.previewColumns
+import kotlinx.coroutines.flow.Flow
import java.net.URL
import java.time.Instant
-import kotlinx.coroutines.flow.Flow
@Dao
interface FeedItemDao {
@@ -52,7 +52,10 @@ interface FeedItemDao {
where id = :id
""",
)
- suspend fun updateWordCountFull(id: Long, wordCount: Int)
+ suspend fun updateWordCountFull(
+ id: Long,
+ wordCount: Int,
+ )
@Query(
"""
@@ -62,10 +65,16 @@ interface FeedItemDao {
LIMIT -1 OFFSET :keepCount
""",
)
- suspend fun getItemsToBeCleanedFromFeed(feedId: Long, keepCount: Int): List
+ suspend fun getItemsToBeCleanedFromFeed(
+ feedId: Long,
+ keepCount: Int,
+ ): List
@Query("SELECT * FROM feed_items WHERE guid IS :guid AND feed_id IS :feedId")
- suspend fun loadFeedItem(guid: String, feedId: Long?): FeedItem?
+ suspend fun loadFeedItem(
+ guid: String,
+ feedId: Long?,
+ ): FeedItem?
@Query("SELECT * FROM feed_items WHERE id IS :id")
suspend fun loadFeedItem(id: Long): FeedItem?
@@ -269,7 +278,10 @@ interface FeedItemDao {
suspend fun markAllAsRead(readTime: Instant = Instant.now())
@Query("UPDATE feed_items SET read_time = coalesce(read_time, :readTime), notified = 1 WHERE feed_id IS :feedId")
- suspend fun markAllAsRead(feedId: Long?, readTime: Instant = Instant.now())
+ suspend fun markAllAsRead(
+ feedId: Long?,
+ readTime: Instant = Instant.now(),
+ )
@Query(
"""
@@ -282,25 +294,43 @@ interface FeedItemDao {
WHERE tag IS :tag
)""",
)
- suspend fun markAllAsRead(tag: String, readTime: Instant = Instant.now())
+ suspend fun markAllAsRead(
+ tag: String,
+ readTime: Instant = Instant.now(),
+ )
@Query("UPDATE feed_items SET read_time = coalesce(read_time, :readTime), notified = 1 WHERE id IS :id")
- suspend fun markAsRead(id: Long, readTime: Instant = Instant.now())
+ suspend fun markAsRead(
+ id: Long,
+ readTime: Instant = Instant.now(),
+ )
@Query("UPDATE feed_items SET read_time = null WHERE id IS :id")
suspend fun markAsUnread(id: Long)
@Query("UPDATE feed_items SET read_time = coalesce(read_time, :readTime), notified = 1 WHERE id IN (:ids)")
- suspend fun markAsRead(ids: List, readTime: Instant = Instant.now())
+ suspend fun markAsRead(
+ ids: List,
+ readTime: Instant = Instant.now(),
+ )
@Query("UPDATE feed_items SET bookmarked = :bookmarked WHERE id IS :id")
- suspend fun setBookmarked(id: Long, bookmarked: Boolean)
+ suspend fun setBookmarked(
+ id: Long,
+ bookmarked: Boolean,
+ )
@Query("UPDATE feed_items SET notified = :notified WHERE id IN (:ids)")
- suspend fun markAsNotified(ids: List, notified: Boolean = true)
+ suspend fun markAsNotified(
+ ids: List,
+ notified: Boolean = true,
+ )
@Query("UPDATE feed_items SET notified = :notified WHERE id IS :id")
- suspend fun markAsNotified(id: Long, notified: Boolean = true)
+ suspend fun markAsNotified(
+ id: Long,
+ notified: Boolean = true,
+ )
@Query(
"""
@@ -313,16 +343,25 @@ interface FeedItemDao {
WHERE tag IS :tag
)""",
)
- suspend fun markTagAsNotified(tag: String, notified: Boolean = true)
+ suspend fun markTagAsNotified(
+ tag: String,
+ notified: Boolean = true,
+ )
@Query("UPDATE feed_items SET notified = :notified")
suspend fun markAllAsNotified(notified: Boolean = true)
@Query("UPDATE feed_items SET read_time = coalesce(read_time, :readTime), notified = 1 WHERE id IS :id")
- suspend fun markAsReadAndNotified(id: Long, readTime: Instant = Instant.now())
+ suspend fun markAsReadAndNotified(
+ id: Long,
+ readTime: Instant = Instant.now(),
+ )
@Query("UPDATE feed_items SET read_time = :readTime, notified = 1 WHERE id IS :id")
- suspend fun markAsReadAndNotifiedAndOverwriteReadTime(id: Long, readTime: Instant = Instant.now())
+ suspend fun markAsReadAndNotifiedAndOverwriteReadTime(
+ id: Long,
+ readTime: Instant = Instant.now(),
+ )
@Query(
"""
@@ -384,7 +423,10 @@ interface FeedItemDao {
and NOT EXISTS (SELECT 1 FROM blocklist WHERE lower(fi.plain_title) GLOB blocklist.glob_pattern)
""",
)
- fun getFeedItemCount(minReadTime: Instant, bookmarked: Boolean): Flow
+ fun getFeedItemCount(
+ minReadTime: Instant,
+ bookmarked: Boolean,
+ ): Flow
@Query(
"""
@@ -397,7 +439,11 @@ interface FeedItemDao {
AND NOT EXISTS (SELECT 1 FROM blocklist WHERE lower(fi.plain_title) GLOB blocklist.glob_pattern)
""",
)
- fun getFeedItemCount(tag: String, minReadTime: Instant, bookmarked: Boolean): Flow
+ fun getFeedItemCount(
+ tag: String,
+ minReadTime: Instant,
+ bookmarked: Boolean,
+ ): Flow
@Query(
"""
@@ -409,7 +455,11 @@ interface FeedItemDao {
AND NOT EXISTS (SELECT 1 FROM blocklist WHERE lower(fi.plain_title) GLOB blocklist.glob_pattern)
""",
)
- fun getFeedItemCount(feedId: Long, minReadTime: Instant, bookmarked: Boolean): Flow
+ fun getFeedItemCount(
+ feedId: Long,
+ minReadTime: Instant,
+ bookmarked: Boolean,
+ ): Flow
@Query(
"""
@@ -430,7 +480,10 @@ interface FeedItemDao {
where f.url IS :feedUrl AND fi.guid IS :articleGuid
""",
)
- suspend fun getItemWith(feedUrl: URL, articleGuid: String): Long?
+ suspend fun getItemWith(
+ feedUrl: URL,
+ articleGuid: String,
+ ): Long?
companion object {
// These are backed by a database index
@@ -445,14 +498,16 @@ suspend fun FeedItemDao.upsertFeedItems(
itemsWithText: List>,
block: suspend (FeedItem, String) -> Unit,
) {
- val updatedItems = itemsWithText.filter { (item, _) ->
- item.id > ID_UNSET
- }
+ val updatedItems =
+ itemsWithText.filter { (item, _) ->
+ item.id > ID_UNSET
+ }
updateFeedItems(updatedItems.map { (item, _) -> item })
- val insertedItems = itemsWithText.filter { (item, _) ->
- item.id <= ID_UNSET
- }
+ val insertedItems =
+ itemsWithText.filter { (item, _) ->
+ item.id <= ID_UNSET
+ }
val insertedIds = insertFeedItems(insertedItems.map { (item, _) -> item })
updatedItems.forEach { (item, text) ->
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemForReadMark.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemForReadMark.kt
index 0c4c6cb29a..c0ca429e43 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemForReadMark.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemForReadMark.kt
@@ -8,11 +8,13 @@ import com.nononsenseapps.feeder.db.COL_GUID
import com.nononsenseapps.feeder.db.COL_ID
import java.net.URL
-data class FeedItemForReadMark @Ignore constructor(
- @ColumnInfo(name = COL_ID) override var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_FEEDID) override var feedId: Long = ID_UNSET,
- @ColumnInfo(name = COL_GUID) override var guid: String = "",
- @ColumnInfo(name = COL_FEEDURL) override var feedUrl: URL = URL("http://"),
-) : ReadStatusFeedItem {
- constructor() : this(id = ID_UNSET)
-}
+data class FeedItemForReadMark
+ @Ignore
+ constructor(
+ @ColumnInfo(name = COL_ID) override var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_FEEDID) override var feedId: Long = ID_UNSET,
+ @ColumnInfo(name = COL_GUID) override var guid: String = "",
+ @ColumnInfo(name = COL_FEEDURL) override var feedUrl: URL = URL("http://"),
+ ) : ReadStatusFeedItem {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemIdWithLink.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemIdWithLink.kt
index 9a9a56dd48..2108d12978 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemIdWithLink.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemIdWithLink.kt
@@ -5,9 +5,11 @@ import androidx.room.Ignore
import com.nononsenseapps.feeder.db.COL_ID
import com.nononsenseapps.feeder.db.COL_LINK
-data class FeedItemIdWithLink @Ignore constructor(
- @ColumnInfo(name = COL_ID) override var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_LINK) override var link: String? = null,
-) : FeedItemForFetching {
- constructor() : this(id = ID_UNSET)
-}
+data class FeedItemIdWithLink
+ @Ignore
+ constructor(
+ @ColumnInfo(name = COL_ID) override var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_LINK) override var link: String? = null,
+ ) : FeedItemForFetching {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemWithFeed.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemWithFeed.kt
index fe2ff822fb..b29ab4674e 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemWithFeed.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItemWithFeed.kt
@@ -47,54 +47,56 @@ const val feedItemColumnsWithFeed = """
$COL_WORD_COUNT_FULL
"""
-data class FeedItemWithFeed @Ignore constructor(
- override var id: Long = ID_UNSET,
- var guid: String = "",
- @Deprecated("This is never different from plainTitle", replaceWith = ReplaceWith("plainTitle"))
- var title: String = "",
- @ColumnInfo(name = COL_PLAINTITLE) var plainTitle: String = "",
- @ColumnInfo(name = COL_PLAINSNIPPET) var plainSnippet: String = "",
- @ColumnInfo(name = COL_IMAGEURL) var imageUrl: String? = null,
- @ColumnInfo(name = COL_ENCLOSURELINK) var enclosureLink: String? = null,
- @ColumnInfo(name = COL_ENCLOSURE_TYPE) var enclosureType: String? = null,
- var author: String? = null,
- @ColumnInfo(name = COL_PUBDATE) var pubDate: ZonedDateTime? = null,
- override var link: String? = null,
- var tag: String = "",
- @ColumnInfo(name = COL_READ_TIME) var readTime: Instant? = null,
- @ColumnInfo(name = COL_FEEDID) var feedId: Long? = null,
- @ColumnInfo(name = COL_FEEDTITLE) var feedTitle: String = "",
- @ColumnInfo(name = COL_FEEDCUSTOMTITLE) var feedCustomTitle: String = "",
- @ColumnInfo(name = COL_FEEDURL) var feedUrl: URL = sloppyLinkToStrictURLNoThrows(""),
- @ColumnInfo(name = COL_FULLTEXT_BY_DEFAULT) var fullTextByDefault: Boolean = false,
- @ColumnInfo(name = COL_BOOKMARKED) var bookmarked: Boolean = false,
- @ColumnInfo(name = COL_WORD_COUNT) var wordCount: Int = 0,
- @ColumnInfo(name = COL_WORD_COUNT_FULL) var wordCountFull: Int = 0,
-) : FeedItemForFetching {
- constructor() : this(id = ID_UNSET)
+data class FeedItemWithFeed
+ @Ignore
+ constructor(
+ override var id: Long = ID_UNSET,
+ var guid: String = "",
+ @Deprecated("This is never different from plainTitle", replaceWith = ReplaceWith("plainTitle"))
+ var title: String = "",
+ @ColumnInfo(name = COL_PLAINTITLE) var plainTitle: String = "",
+ @ColumnInfo(name = COL_PLAINSNIPPET) var plainSnippet: String = "",
+ @ColumnInfo(name = COL_IMAGEURL) var imageUrl: String? = null,
+ @ColumnInfo(name = COL_ENCLOSURELINK) var enclosureLink: String? = null,
+ @ColumnInfo(name = COL_ENCLOSURE_TYPE) var enclosureType: String? = null,
+ var author: String? = null,
+ @ColumnInfo(name = COL_PUBDATE) var pubDate: ZonedDateTime? = null,
+ override var link: String? = null,
+ var tag: String = "",
+ @ColumnInfo(name = COL_READ_TIME) var readTime: Instant? = null,
+ @ColumnInfo(name = COL_FEEDID) var feedId: Long? = null,
+ @ColumnInfo(name = COL_FEEDTITLE) var feedTitle: String = "",
+ @ColumnInfo(name = COL_FEEDCUSTOMTITLE) var feedCustomTitle: String = "",
+ @ColumnInfo(name = COL_FEEDURL) var feedUrl: URL = sloppyLinkToStrictURLNoThrows(""),
+ @ColumnInfo(name = COL_FULLTEXT_BY_DEFAULT) var fullTextByDefault: Boolean = false,
+ @ColumnInfo(name = COL_BOOKMARKED) var bookmarked: Boolean = false,
+ @ColumnInfo(name = COL_WORD_COUNT) var wordCount: Int = 0,
+ @ColumnInfo(name = COL_WORD_COUNT_FULL) var wordCountFull: Int = 0,
+ ) : FeedItemForFetching {
+ constructor() : this(id = ID_UNSET)
- val feedDisplayTitle: String
- get() = feedCustomTitle.ifBlank { feedTitle }
+ val feedDisplayTitle: String
+ get() = feedCustomTitle.ifBlank { feedTitle }
- val enclosureFilename: String?
- get() {
- enclosureLink?.let { enclosureLink ->
- var fname: String? = null
- try {
- fname = URI(enclosureLink).path.split("/").last()
- } catch (_: Exception) {
- }
- return if (fname.isNullOrEmpty()) {
- null
- } else {
- fname
+ val enclosureFilename: String?
+ get() {
+ enclosureLink?.let { enclosureLink ->
+ var fname: String? = null
+ try {
+ fname = URI(enclosureLink).path.split("/").last()
+ } catch (_: Exception) {
+ }
+ return if (fname.isNullOrEmpty()) {
+ null
+ } else {
+ fname
+ }
}
+ return null
}
- return null
- }
- val domain: String?
- get() {
- return (enclosureLink ?: link)?.host()
- }
-}
+ val domain: String?
+ get() {
+ return (enclosureLink ?: link)?.host()
+ }
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedTitle.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedTitle.kt
index 2ca6765ef1..163bacfd51 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedTitle.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/FeedTitle.kt
@@ -6,13 +6,15 @@ import com.nononsenseapps.feeder.db.COL_CUSTOM_TITLE
import com.nononsenseapps.feeder.db.COL_ID
import com.nononsenseapps.feeder.db.COL_TITLE
-data class FeedTitle @Ignore constructor(
- @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_TITLE) var title: String = "",
- @ColumnInfo(name = COL_CUSTOM_TITLE) var customTitle: String = "",
-) {
- constructor() : this(id = ID_UNSET)
+data class FeedTitle
+ @Ignore
+ constructor(
+ @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_TITLE) var title: String = "",
+ @ColumnInfo(name = COL_CUSTOM_TITLE) var customTitle: String = "",
+ ) {
+ constructor() : this(id = ID_UNSET)
- val displayTitle: String
- get() = (customTitle.ifBlank { title })
-}
+ val displayTitle: String
+ get() = (customTitle.ifBlank { title })
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/ReadStatusSynced.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/ReadStatusSynced.kt
index ad91b2dd6a..bda3ac1473 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/ReadStatusSynced.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/ReadStatusSynced.kt
@@ -31,15 +31,17 @@ import java.net.URL
),
],
)
-data class ReadStatusSynced @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = "sync_remote") var sync_remote: Long = ID_UNSET,
- @ColumnInfo(name = "feed_item") var feed_item: Long = ID_UNSET,
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class ReadStatusSynced
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = "sync_remote") var sync_remote: Long = ID_UNSET,
+ @ColumnInfo(name = "feed_item") var feed_item: Long = ID_UNSET,
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
interface ReadStatusFeedItem {
val id: Long
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteFeed.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteFeed.kt
index 937f81f5be..ed39bd0cec 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteFeed.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteFeed.kt
@@ -26,12 +26,14 @@ import java.net.URL
),
],
)
-data class RemoteFeed @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = "sync_remote") var syncRemote: Long = ID_UNSET,
- @ColumnInfo(name = COL_URL) var url: URL = URL("http://"),
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class RemoteFeed
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = "sync_remote") var syncRemote: Long = ID_UNSET,
+ @ColumnInfo(name = COL_URL) var url: URL = URL("http://"),
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMark.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMark.kt
index be266f4497..1ff6c30a6a 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMark.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMark.kt
@@ -29,18 +29,20 @@ import java.time.Instant
),
],
)
-data class RemoteReadMark @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = "sync_remote") var sync_remote: Long = ID_UNSET,
- @ColumnInfo(name = COL_FEEDURL) var feedUrl: URL = URL("http://"),
- @ColumnInfo(name = COL_GUID) var guid: String = "",
- @ColumnInfo(
- name = "timestamp",
- typeAffinity = ColumnInfo.INTEGER,
- ) var timestamp: Instant = Instant.EPOCH,
-) {
- @Suppress("unused")
- constructor() : this(id = ID_UNSET)
-}
+data class RemoteReadMark
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = "sync_remote") var sync_remote: Long = ID_UNSET,
+ @ColumnInfo(name = COL_FEEDURL) var feedUrl: URL = URL("http://"),
+ @ColumnInfo(name = COL_GUID) var guid: String = "",
+ @ColumnInfo(
+ name = "timestamp",
+ typeAffinity = ColumnInfo.INTEGER,
+ ) var timestamp: Instant = Instant.EPOCH,
+ ) {
+ @Suppress("unused")
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMarkDao.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMarkDao.kt
index b7a5ed1446..93a1bb0e89 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMarkDao.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/RemoteReadMarkDao.kt
@@ -48,9 +48,11 @@ interface RemoteReadMarkDao {
suspend fun getGuidsWhichAreSyncedAsReadInFeed(feedUrl: URL): List
}
-data class RemoteReadMarkReadyToBeApplied @Ignore constructor(
- @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
- @ColumnInfo(name = "feed_item_id") var feedItemId: Long = ID_UNSET,
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class RemoteReadMarkReadyToBeApplied
+ @Ignore
+ constructor(
+ @ColumnInfo(name = COL_ID) var id: Long = ID_UNSET,
+ @ColumnInfo(name = "feed_item_id") var feedItemId: Long = ID_UNSET,
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncDevice.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncDevice.kt
index eed2a6e2a1..8f7ee9cd54 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncDevice.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncDevice.kt
@@ -24,13 +24,15 @@ import com.nononsenseapps.feeder.db.SYNC_DEVICE_TABLE_NAME
),
],
)
-data class SyncDevice @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = "sync_remote") var syncRemote: Long = ID_UNSET,
- @ColumnInfo(name = "device_id") var deviceId: Long = ID_UNSET,
- @ColumnInfo(name = "device_name") var deviceName: String = "",
-) {
- constructor() : this(id = ID_UNSET)
-}
+data class SyncDevice
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = "sync_remote") var syncRemote: Long = ID_UNSET,
+ @ColumnInfo(name = "device_id") var deviceId: Long = ID_UNSET,
+ @ColumnInfo(name = "device_name") var deviceName: String = "",
+ ) {
+ constructor() : this(id = ID_UNSET)
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemote.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemote.kt
index 36ea7bc757..e847990b19 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemote.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemote.kt
@@ -22,35 +22,39 @@ import kotlin.random.Random
@Entity(
tableName = SYNC_REMOTE_TABLE_NAME,
)
-data class SyncRemote @Ignore constructor(
- @PrimaryKey(autoGenerate = true)
- @ColumnInfo(name = COL_ID)
- var id: Long = ID_UNSET,
- @ColumnInfo(name = COL_URL) var url: URL = URL(DEFAULT_SERVER_ADDRESS),
- @ColumnInfo(name = COL_SYNC_CHAIN_ID) var syncChainId: String = "",
- @ColumnInfo(
- name = COL_LATEST_MESSAGE_TIMESTAMP,
- typeAffinity = ColumnInfo.INTEGER,
- ) var latestMessageTimestamp: Instant = Instant.EPOCH,
- @ColumnInfo(name = COL_DEVICE_ID) var deviceId: Long = 0L,
- @ColumnInfo(name = COL_DEVICE_NAME) var deviceName: String = generateDeviceName(),
- @ColumnInfo(name = COL_SECRET_KEY) var secretKey: String = "",
- @ColumnInfo(name = COL_LAST_FEEDS_REMOTE_HASH) var lastFeedsRemoteHash: Int = 0,
-) {
- constructor() : this(id = ID_UNSET)
+data class SyncRemote
+ @Ignore
+ constructor(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ var id: Long = ID_UNSET,
+ @ColumnInfo(name = COL_URL) var url: URL = URL(DEFAULT_SERVER_ADDRESS),
+ @ColumnInfo(name = COL_SYNC_CHAIN_ID) var syncChainId: String = "",
+ @ColumnInfo(
+ name = COL_LATEST_MESSAGE_TIMESTAMP,
+ typeAffinity = ColumnInfo.INTEGER,
+ ) var latestMessageTimestamp: Instant = Instant.EPOCH,
+ @ColumnInfo(name = COL_DEVICE_ID) var deviceId: Long = 0L,
+ @ColumnInfo(name = COL_DEVICE_NAME) var deviceName: String = generateDeviceName(),
+ @ColumnInfo(name = COL_SECRET_KEY) var secretKey: String = "",
+ @ColumnInfo(name = COL_LAST_FEEDS_REMOTE_HASH) var lastFeedsRemoteHash: Int = 0,
+ ) {
+ constructor() : this(id = ID_UNSET)
- fun hasSyncChain(): Boolean = syncChainId.length == 64
- fun notHasSyncChain() = !hasSyncChain()
-}
+ fun hasSyncChain(): Boolean = syncChainId.length == 64
+
+ fun notHasSyncChain() = !hasSyncChain()
+ }
private const val DEFAULT_SERVER_HOST = "feeder-sync.nononsenseapps.com"
private const val DEFAULT_SERVER_PORT = 443
const val DEFAULT_SERVER_ADDRESS = "https://$DEFAULT_SERVER_HOST:$DEFAULT_SERVER_PORT"
-val DEPRECATED_SYNC_HOSTS = listOf(
- "feederapp.nononsenseapps.com",
- "feeder-sync.nononsenseapps.workers.dev",
-)
+val DEPRECATED_SYNC_HOSTS =
+ listOf(
+ "feederapp.nononsenseapps.com",
+ "feeder-sync.nononsenseapps.workers.dev",
+ )
fun generateDeviceName(): String {
val manufacturer = Build.MANUFACTURER ?: ""
diff --git a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemoteDao.kt b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemoteDao.kt
index 205dbb436a..3e4d092145 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemoteDao.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/db/room/SyncRemoteDao.kt
@@ -9,8 +9,8 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.nononsenseapps.feeder.crypto.AesCbcWithIntegrity
-import java.time.Instant
import kotlinx.coroutines.flow.Flow
+import java.time.Instant
@Dao
interface SyncRemoteDao {
diff --git a/app/src/main/java/com/nononsenseapps/feeder/di/AndroidModule.kt b/app/src/main/java/com/nononsenseapps/feeder/di/AndroidModule.kt
index 867cd3b968..1923b15f41 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/di/AndroidModule.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/di/AndroidModule.kt
@@ -5,6 +5,7 @@ import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.singleton
-val androidModule = DI.Module(name = "android module") {
- bind() with singleton { AndroidSystemStore(di) }
-}
+val androidModule =
+ DI.Module(name = "android module") {
+ bind() with singleton { AndroidSystemStore(di) }
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/di/ArchModelModule.kt b/app/src/main/java/com/nononsenseapps/feeder/di/ArchModelModule.kt
index d1cd907771..6df1d559b2 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/di/ArchModelModule.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/di/ArchModelModule.kt
@@ -23,23 +23,24 @@ import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.singleton
-val archModelModule = DI.Module(name = "arch models") {
- bind() with singleton { Repository(di) }
- bind() with singleton { SessionStore() }
- bind() with singleton { SettingsStore(di) }
- bind() with singleton { FeedStore(di) }
- bind() with singleton { FeedItemStore(di) }
- bind() with singleton { SyncRemoteStore(di) }
- bind() with singleton { OPMLImporter(di) }
+val archModelModule =
+ DI.Module(name = "arch models") {
+ bind() with singleton { Repository(di) }
+ bind() with singleton { SessionStore() }
+ bind() with singleton { SettingsStore(di) }
+ bind() with singleton { FeedStore(di) }
+ bind() with singleton { FeedItemStore(di) }
+ bind() with singleton { SyncRemoteStore(di) }
+ bind() with singleton { OPMLImporter(di) }
- bindWithActivityViewModelScope()
- bindWithActivityViewModelScope()
- bindWithActivityViewModelScope()
+ bindWithActivityViewModelScope()
+ bindWithActivityViewModelScope()
+ bindWithActivityViewModelScope()
- bindWithComposableViewModelScope()
- bindWithComposableViewModelScope()
- bindWithComposableViewModelScope()
- bindWithComposableViewModelScope()
- bindWithComposableViewModelScope()
- bindWithComposableViewModelScope()
-}
+ bindWithComposableViewModelScope()
+ bindWithComposableViewModelScope()
+ bindWithComposableViewModelScope()
+ bindWithComposableViewModelScope()
+ bindWithComposableViewModelScope()
+ bindWithComposableViewModelScope()
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/di/NetworkModule.kt b/app/src/main/java/com/nononsenseapps/feeder/di/NetworkModule.kt
index f849511516..f28e1154be 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/di/NetworkModule.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/di/NetworkModule.kt
@@ -15,13 +15,14 @@ import org.kodein.di.instance
import org.kodein.di.provider
import org.kodein.di.singleton
-val networkModule = DI.Module(name = "network") {
- // Parsers can carry state so safer to use providers
- bind>() with provider { feedAdapter() }
- bind() with provider { JsonFeedParser(instance(), instance()) }
- bind() with provider { FeedParser(di) }
- // These don't have state issues
- bind() with singleton { SyncRestClient(di) }
- bind() with singleton { RssLocalSync(di) }
- bind() with singleton { FullTextParser(di) }
-}
+val networkModule =
+ DI.Module(name = "network") {
+ // Parsers can carry state so safer to use providers
+ bind>() with provider { feedAdapter() }
+ bind() with provider { JsonFeedParser(instance(), instance()) }
+ bind() with provider { FeedParser(di) }
+ // These don't have state issues
+ bind() with singleton { SyncRestClient(di) }
+ bind() with singleton { RssLocalSync(di) }
+ bind() with singleton { FullTextParser(di) }
+ }
diff --git a/app/src/main/java/com/nononsenseapps/feeder/model/FeedParser.kt b/app/src/main/java/com/nononsenseapps/feeder/model/FeedParser.kt
index 6e27dad490..5483c16628 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/model/FeedParser.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/model/FeedParser.kt
@@ -13,12 +13,6 @@ import com.nononsenseapps.jsonfeed.Feed
import com.nononsenseapps.jsonfeed.JsonFeedParser
import com.rometools.rome.io.SyndFeedInput
import com.rometools.rome.io.XmlReader
-import java.io.IOException
-import java.net.MalformedURLException
-import java.net.URL
-import java.net.URLDecoder
-import java.util.Locale
-import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
@@ -34,6 +28,12 @@ import org.jsoup.nodes.Document
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.instance
+import java.io.IOException
+import java.net.MalformedURLException
+import java.net.URL
+import java.net.URLDecoder
+import java.util.Locale
+import java.util.concurrent.TimeUnit
private const val YOUTUBE_CHANNEL_ID_ATTR = "data-channel-external-id"
@@ -81,22 +81,24 @@ class FeedParser(override val di: DI) : DIAware {
html: String,
baseUrl: URL? = null,
): String? {
- val doc = html.byteInputStream().use {
- Jsoup.parse(it, "UTF-8", baseUrl?.toString() ?: "")
- }
+ val doc =
+ html.byteInputStream().use {
+ Jsoup.parse(it, "UTF-8", baseUrl?.toString() ?: "")
+ }
return (
doc.getElementsByAttributeValue("rel", "apple-touch-icon") +
doc.getElementsByAttributeValue("rel", "icon") +
doc.getElementsByAttributeValue("rel", "shortcut icon")
- )
+ )
.filter { it.hasAttr("href") }
.firstNotNullOfOrNull { e ->
when {
- baseUrl != null -> relativeLinkIntoAbsolute(
- base = baseUrl,
- link = e.attr("href"),
- )
+ baseUrl != null ->
+ relativeLinkIntoAbsolute(
+ base = baseUrl,
+ link = e.attr("href"),
+ )
else -> sloppyLinkToStrictURLOrNull(e.attr("href"))?.toString()
}
@@ -110,83 +112,90 @@ class FeedParser(override val di: DI) : DIAware {
html: String,
baseUrl: URL? = null,
): List {
- val doc = html.byteInputStream().use {
- Jsoup.parse(it, "UTF-8", "")
- }
-
- val feeds = doc.getElementsByAttributeValue("rel", "alternate")
- ?.filter { it.hasAttr("href") && it.hasAttr("type") }
- ?.filter {
- val t = it.attr("type").lowercase(Locale.getDefault())
- when {
- t.contains("application/atom") -> true
- t.contains("application/rss") -> true
- // Youtube for example has alternate links with application/json+oembed type.
- t == "application/json" -> true
- else -> false
- }
+ val doc =
+ html.byteInputStream().use {
+ Jsoup.parse(it, "UTF-8", "")
}
- ?.filter {
- val l = it.attr("href").lowercase(Locale.getDefault())
- try {
- if (baseUrl != null) {
- relativeLinkIntoAbsoluteOrThrow(base = baseUrl, link = l)
- } else {
- URL(l)
+
+ val feeds =
+ doc.getElementsByAttributeValue("rel", "alternate")
+ ?.filter { it.hasAttr("href") && it.hasAttr("type") }
+ ?.filter {
+ val t = it.attr("type").lowercase(Locale.getDefault())
+ when {
+ t.contains("application/atom") -> true
+ t.contains("application/rss") -> true
+ // Youtube for example has alternate links with application/json+oembed type.
+ t == "application/json" -> true
+ else -> false
}
- true
- } catch (_: MalformedURLException) {
- false
}
- }
- ?.mapNotNull { e ->
- when {
- baseUrl != null -> {
- try {
- AlternateLink(
- type = e.attr("type"),
- link = relativeLinkIntoAbsoluteOrThrow(
- base = baseUrl,
- link = e.attr("href"),
- ),
- )
- } catch (e: Exception) {
- null
+ ?.filter {
+ val l = it.attr("href").lowercase(Locale.getDefault())
+ try {
+ if (baseUrl != null) {
+ relativeLinkIntoAbsoluteOrThrow(base = baseUrl, link = l)
+ } else {
+ URL(l)
}
+ true
+ } catch (_: MalformedURLException) {
+ false
}
+ }
+ ?.mapNotNull { e ->
+ when {
+ baseUrl != null -> {
+ try {
+ AlternateLink(
+ type = e.attr("type"),
+ link =
+ relativeLinkIntoAbsoluteOrThrow(
+ base = baseUrl,
+ link = e.attr("href"),
+ ),
+ )
+ } catch (e: Exception) {
+ null
+ }
+ }
- else -> sloppyLinkToStrictURLOrNull(e.attr("href"))?.let { l ->
- AlternateLink(
- type = e.attr("type"),
- link = l,
- )
+ else ->
+ sloppyLinkToStrictURLOrNull(e.attr("href"))?.let { l ->
+ AlternateLink(
+ type = e.attr("type"),
+ link = l,
+ )
+ }
}
- }
- } ?: emptyList()
+ } ?: emptyList()
return when {
feeds.isNotEmpty() -> feeds
- baseUrl?.host == "www.youtube.com" || baseUrl?.host == "youtube.com" -> findFeedLinksForYoutube(
- doc,
- )
+ baseUrl?.host == "www.youtube.com" || baseUrl?.host == "youtube.com" ->
+ findFeedLinksForYoutube(
+ doc,
+ )
else -> emptyList()
}
}
private fun findFeedLinksForYoutube(doc: Document): List {
- val channelId: String? = doc.body()?.getElementsByAttribute(YOUTUBE_CHANNEL_ID_ATTR)
- ?.firstOrNull()
- ?.attr(YOUTUBE_CHANNEL_ID_ATTR)
+ val channelId: String? =
+ doc.body()?.getElementsByAttribute(YOUTUBE_CHANNEL_ID_ATTR)
+ ?.firstOrNull()
+ ?.attr(YOUTUBE_CHANNEL_ID_ATTR)
return when (channelId) {
null -> emptyList()
- else -> listOf(
- AlternateLink(
- type = "atom",
- link = URL("https://www.youtube.com/feeds/videos.xml?channel_id=$channelId"),
- ),
- )
+ else ->
+ listOf(
+ AlternateLink(
+ type = "atom",
+ link = URL("https://www.youtube.com/feeds/videos.xml?channel_id=$channelId"),
+ ),
+ )
}
}
@@ -223,13 +232,14 @@ class FeedParser(override val di: DI) : DIAware {
responseBody: ResponseBody,
): Either {
return when (responseBody.contentType()?.subtype?.contains("json")) {
- true -> Either.catching(
- onCatch = { t ->
- JsonFeedParseError(url = url.toString(), throwable = t)
- },
- ) {
- jsonFeedParser.parseJson(responseBody)
- }
+ true ->
+ Either.catching(
+ onCatch = { t ->
+ JsonFeedParseError(url = url.toString(), throwable = t)
+ },
+ ) {
+ jsonFeedParser.parseJson(responseBody)
+ }
else -> parseRssAtom(url, responseBody)
}
@@ -252,13 +262,14 @@ class FeedParser(override val di: DI) : DIAware {
contentType: MediaType?,
): Either {
return when (contentType?.subtype?.contains("json")) {
- true -> Either.catching(
- onCatch = { t ->
- JsonFeedParseError(url = url.toString(), throwable = t)
- },
- ) {
- jsonFeedParser.parseJson(body)
- }
+ true ->
+ Either.catching(
+ onCatch = { t ->
+ JsonFeedParseError(url = url.toString(), throwable = t)
+ },
+ ) {
+ jsonFeedParser.parseJson(body)
+ }
else -> parseRssAtom(url, body)
}.map { feed ->
@@ -277,22 +288,23 @@ class FeedParser(override val di: DI) : DIAware {
responseBody: ResponseBody,
): Either {
val contentType = responseBody.contentType()
- val validMimeType = when (contentType?.type) {
- "application" -> {
- when {
- contentType.subtype.contains("xml") -> true
- else -> false
+ val validMimeType =
+ when (contentType?.type) {
+ "application" -> {
+ when {
+ contentType.subtype.contains("xml") -> true
+ else -> false
+ }
}
- }
- "text" -> {
- // So many sites on the internet return mimetype text/html for rss feeds...
- // So try to parse it despite it being wrong
- true
- }
+ "text" -> {
+ // So many sites on the internet return mimetype text/html for rss feeds...
+ // So try to parse it despite it being wrong
+ true
+ }
- else -> false
- }
+ else -> false
+ }
if (!validMimeType) {
return Either.Left(
UnsupportedContentType(url = url.toString(), mimeType = contentType.toString()),
@@ -305,33 +317,38 @@ class FeedParser(override val di: DI) : DIAware {
},
) {
responseBody.byteStream().use { bs ->
- val feed = XmlReader(bs, true, responseBody.contentType()?.charset()?.name()).use {
- SyndFeedInput()
- .apply {
- isPreserveWireFeed = true
- }
- .build(it)
- }
+ val feed =
+ XmlReader(bs, true, responseBody.contentType()?.charset()?.name()).use {
+ SyndFeedInput()
+ .apply {
+ isPreserveWireFeed = true
+ }
+ .build(it)
+ }
feed.asFeed(baseUrl = url)
}
}
}
@Throws(FeedParsingError::class)
- internal fun parseRssAtom(baseUrl: URL, body: String): Either {
+ internal fun parseRssAtom(
+ baseUrl: URL,
+ body: String,
+ ): Either {
return Either.catching(
onCatch = { t ->
RSSParseError(url = baseUrl.toString(), throwable = t)
},
) {
body.byteInputStream().use { bs ->
- val feed = XmlReader(bs, true).use {
- SyndFeedInput()
- .apply {
- isPreserveWireFeed = true
- }
- .build(it)
- }
+ val feed =
+ XmlReader(bs, true).use {
+ SyndFeedInput()
+ .apply {
+ isPreserveWireFeed = true
+ }
+ .build(it)
+ }
feed.asFeed(baseUrl = baseUrl)
}
}
@@ -348,67 +365,70 @@ suspend fun OkHttpClient.getResponse(
url: URL,
forceNetwork: Boolean = false,
): Response {
- val request = Request.Builder()
- .url(url)
- .cacheControl(
- CacheControl.Builder()
- // The time between cache re-validations
- .maxAge(
- if (forceNetwork) {
- 0
- } else {
- // Matches fastest sync schedule
- 15
- },
- TimeUnit.MINUTES,
- )
- .build(),
- )
- .build()
+ val request =
+ Request.Builder()
+ .url(url)
+ .cacheControl(
+ CacheControl.Builder()
+ // The time between cache re-validations
+ .maxAge(
+ if (forceNetwork) {
+ 0
+ } else {
+ // Matches fastest sync schedule
+ 15
+ },
+ TimeUnit.MINUTES,
+ )
+ .build(),
+ )
+ .build()
@Suppress("BlockingMethodInNonBlockingContext")
- val clientToUse = if (url.userInfo?.isNotBlank() == true) {
- val parts = url.userInfo.split(':')
- val user = parts.first()
- val pass = if (parts.size > 1) {
- parts[1]
- } else {
- ""
- }
- val decodedUser = URLDecoder.decode(user, "UTF-8")
- val decodedPass = URLDecoder.decode(pass, "UTF-8")
- val credentials = Credentials.basic(decodedUser, decodedPass)
- newBuilder()
- .authenticator { _, response ->
- when {
- response.request.header("Authorization") != null -> {
- null
- }
+ val clientToUse =
+ if (url.userInfo?.isNotBlank() == true) {
+ val parts = url.userInfo.split(':')
+ val user = parts.first()
+ val pass =
+ if (parts.size > 1) {
+ parts[1]
+ } else {
+ ""
+ }
+ val decodedUser = URLDecoder.decode(user, "UTF-8")
+ val decodedPass = URLDecoder.decode(pass, "UTF-8")
+ val credentials = Credentials.basic(decodedUser, decodedPass)
+ newBuilder()
+ .authenticator { _, response ->
+ when {
+ response.request.header("Authorization") != null -> {
+ null
+ }
- else -> {
- response.request.newBuilder()
- .header("Authorization", credentials)
- .build()
+ else -> {
+ response.request.newBuilder()
+ .header("Authorization", credentials)
+ .build()
+ }
}
}
- }
- .proxyAuthenticator { _, response ->
- when {
- response.request.header("Proxy-Authorization") != null -> {
- null
- }
+ .proxyAuthenticator { _, response ->
+ when {
+ response.request.header("Proxy-Authorization") != null -> {
+ null
+ }
- else -> {
- response.request.newBuilder()
- .header("Proxy-Authorization", credentials)
- .build()
+ else -> {
+ response.request.newBuilder()
+ .header("Proxy-Authorization", credentials)
+ .build()
+ }
}
}
- }
- .build()
- } else {
- this
- }
+ .build()
+ } else {
+ this
+ }
return withContext(IO) {
clientToUse.newCall(request).execute()
@@ -440,21 +460,23 @@ suspend fun OkHttpClient.curl(url: URL): Either {
)
}
- else -> Either.Left(
- UnsupportedContentType(
- url = url.toString(),
- mimeType = contentType.toString(),
- ),
- )
+ else ->
+ Either.Left(
+ UnsupportedContentType(
+ url = url.toString(),
+ mimeType = contentType.toString(),
+ ),
+ )
}
}
- else -> Either.Left(
- UnsupportedContentType(
- url = url.toString(),
- mimeType = contentType.toString(),
- ),
- )
+ else ->
+ Either.Left(
+ UnsupportedContentType(
+ url = url.toString(),
+ mimeType = contentType.toString(),
+ ),
+ )
}
}
}
diff --git a/app/src/main/java/com/nononsenseapps/feeder/model/FeedUnreadCount.kt b/app/src/main/java/com/nononsenseapps/feeder/model/FeedUnreadCount.kt
index 3613836744..ab96e5005a 100644
--- a/app/src/main/java/com/nononsenseapps/feeder/model/FeedUnreadCount.kt
+++ b/app/src/main/java/com/nononsenseapps/feeder/model/FeedUnreadCount.kt
@@ -8,71 +8,72 @@ import com.nononsenseapps.feeder.db.room.ID_UNSET
import com.nononsenseapps.feeder.util.sloppyLinkToStrictURLNoThrows
import java.net.URL
-data class FeedUnreadCount @Ignore constructor(
- var id: Long = ID_UNSET,
- var title: String = "",
- var url: URL = sloppyLinkToStrictURLNoThrows(""),
- var tag: String = "",
- @ColumnInfo(name = "custom_title")
- var customTitle: String = "",
- var notify: Boolean = false,
- @ColumnInfo(name = COL_CURRENTLY_SYNCING) var currentlySyncing: Boolean = false,
- @ColumnInfo(name = "image_url") var imageUrl: URL? = null,
- @ColumnInfo(name = "unread_count") var unreadCount: Int = 0,
+data class FeedUnreadCount
+ @Ignore
+ constructor(
+ var id: Long = ID_UNSET,
+ var title: String = "",
+ var url: URL = sloppyLinkToStrictURLNoThrows(""),
+ var tag: String = "",
+ @ColumnInfo(name = "custom_title")
+ var customTitle: String = "",
+ var notify: Boolean = false,
+ @ColumnInfo(name = COL_CURRENTLY_SYNCING) var currentlySyncing: Boolean = false,
+ @ColumnInfo(name = "image_url") var imageUrl: URL? = null,
+ @ColumnInfo(name = "unread_count") var unreadCount: Int = 0,
+ ) : Comparable {
+ constructor() : this(id = ID_UNSET)
-) : Comparable