Merge upstream redesign (#714)
* merge toolbar fragment * Fix store screenshot generator * Fix alert color * Fix #609 * Fix crash * bigger hitbox for chips * support mastodon languages * merge ui utils * merge stuff * fix icon * ensure 48dp touch target * init local prefs, add helper function for enum values * update compose action layout * merge compose-adj files * update extended footer * fix poll wrong option checked closes sk22#641 * no border when disabled closes sk22#640 * Fix #610 * Minor fixes * Fix alert color * Fix #609 * Fix crash * Fix #610 * Minor fixes * add resources * more compatible mastodon language * fix html parser * mark as read on refresh * update tab bar * tweak m3 buttons * update compose-adj files * tweak and update styles * m3 expand button * flag icon should be 18dp, actually * More minor fixes closes #612 * More minor fixes closes #612 * Bump version * fix no create status event when redrafting * add material 3 assets * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Italian) * New translations strings.xml (Greek) * New translations strings.xml (Italian) * New translations strings.xml (Thai) * New translations strings.xml (Thai) * New translations strings.xml (Italian) * New translations strings.xml (Thai) * use new buttons for profile fragment * merge compose fragment * merge all the styles! oh dear * New translations full_description.txt (Indonesian) * New translations full_description.txt (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations full_description.txt (Chinese Simplified) * Fix #615 * Minor fixes * Fix #611 * A bunch of crash fixes * New translations strings.xml (Greek) * Make the default server configurable * Pass the system timezone to server when signing up * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Japanese) * Fix #615 * Minor fixes * Fix #611 * A bunch of crash fixes * Make the default server configurable * Pass the system timezone to server when signing up * oops. accidentally pasted the commit message in the code * Remove unused code that caused a crash for some users ¯\_(ツ)_/¯ * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * Remove unused code that caused a crash for some users ¯\_(ツ)_/¯ * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Turkish) * New translations strings.xml (Belarusian) * prepare merging profile fragment * merge profile fragment * New translations strings.xml (Belarusian) * New translations strings.xml (Greek) * fix icon padding * apply post header changes * minor margin tweaks * fix footer buttons * fix header announcement buttons * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * New translations strings.xml (Japanese) * New translations full_description.txt (Japanese) * New translations strings.xml (Icelandic) * New translations strings.xml (Icelandic) * New translations strings.xml (Icelandic) * fix replying * New translations strings.xml (Icelandic) * fix translate button * fix more button visibility * fix counts label styling * fix disabled boost button opacity * fix tab layouts * fix notification icon color crash * New translations strings.xml (Greek) * implement elevation listener in home tab * fix elevation and listener in home tab * add elevation scroll listener to notifications * New translations strings.xml (Scottish Gaelic) * Add editorconfig So that PRs like #625 don't happen again * Crash fix * 🤔 * New translations strings.xml (Greek) * New translations strings.xml (Japanese) * New translations strings.xml (French) * New translations strings.xml (French) * New translations strings.xml (French) * fix notification elevation and integrate divider * 🤔 * Crash fix * Add editorconfig So that PRs like #625 don't happen again * New translations strings.xml (Turkish) * save interactions in cache * New translations strings.xml (Turkish) * merge new discover/search * New translations strings.xml (Bengali) * New translations strings.xml (Scottish Gaelic) * New translations strings.xml (Bengali) * merge new settings fragments * fix no auth callback always being executed * allow opening server info from profile closes sk22#593 * fix hide boosts icon color closes sk22#676 * New translations strings.xml (Turkish) * New translations strings.xml (Turkish) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (German) * New translations strings.xml (German) * New translations strings.xml (Turkish) * update fedinuke list from source; doesn't contain any modifications regarding a recent issue * New translations strings.xml (Turkish) * remove unused class * fix crash * darken m3 outline color a bit * use m3 outline again * fix misalignment closes sk22#682 * New translations strings.xml (Turkish) * New translations full_description.txt (Turkish) * New translations short_description.txt (Turkish) * fix crash * fix metadata sorting * show pronouns in header/account lists * fix broken divider line closes sk22#679 * trim pronouns * improve pronoun display * New translations strings.xml (French) * New translations strings.xml (Japanese) * fix broken federated timeline closes sk22#685 * fix broken -1 fallback behavior closes sk22#681 * don't display nothing if server about request fails closes sk22#678 * New translations strings.xml (Ukrainian) * migrate global prefs to local prefs * do confirm unfollow by default * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations full_description.txt (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Russian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Ukrainian) * New translations strings.xml (Vietnamese) * New translations full_description.txt (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * make sure list in prefs are always mutable and nut null * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * New translations strings.xml (Russian) * fix pronouns edge case * add back fix for stretched images closes sk22#636 * fix null pointer on missing default posting language * fix default posting language not being applied * bigger username hitbox closes sk22#688 * fix rtl header username alignment closes sk22#689 * New translations strings.xml (Ukrainian) * New translations strings.xml (Ukrainian) * hopefully fix crashes closes sk22#692 * New translations strings.xml (Ukrainian) * New translations full_description.txt (Ukrainian) * fix pronoun crash * New translations strings.xml (Persian) * New translations strings.xml (Ukrainian) * re-add true black mode * asterisk can be a pronoun * New translations strings.xml (Persian) * true black mode fixes and clean-ups * material 3 button background for switcher * darker tab bar selected background * better align follow/following button widths * restore rainbow refresh colors * fix search transition * fix min width issue with switcher button * fix no elevation when true black is enabled in light theme * use statusForContent to determine spoilerRevealed closes sk22#694 * New translations strings.xml (Persian) * New translations strings.xml (Persian) * New translations strings.xml (Persian) * New translations strings.xml (Persian) * New translations strings.xml (Persian) * New translations strings.xml (Persian) * fix profile tab bar in true black theme * fix m3 default button style closes sk22#697 * prettier role badges closes sk22#663 * fix translate button spacing closes sk22#655 * use m3 switches in dialogs closes sk22#653 * implement color palette switcher * fix color palettes being overwritten * add display and notification settings * clean up code * per-account single notification setting * add missing items to notification types * add prefix replies setting * add show replies/boosts and reply visibility * add load/see new posts settings * fix spectator mode missing spoiler padding * add a bunch of display settings * update fedinuke * add content type settings * add settings for local-onlu * add missing settings items * fix visibility button icon tint * hopefully fix some crashes * normalize padding above edit text * apparently, some people don't like pills closes sk22#706 * fix play button color closes sk22#705
This commit is contained in:
parent
3cfea0e660
commit
7677ad39ca
744 changed files with 24873 additions and 13485 deletions
279
mastodon/.editorconfig
Normal file
279
mastodon/.editorconfig
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = false
|
||||||
|
max_line_length = 300
|
||||||
|
tab_width = 4
|
||||||
|
ij_continuation_indent_size = 8
|
||||||
|
ij_formatter_off_tag = @formatter:off
|
||||||
|
ij_formatter_on_tag = @formatter:on
|
||||||
|
ij_formatter_tags_enabled = false
|
||||||
|
ij_smart_tabs = false
|
||||||
|
ij_visual_guides = none
|
||||||
|
ij_wrap_on_typing = false
|
||||||
|
|
||||||
|
[*.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_align_types_in_multi_catch = true
|
||||||
|
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_add_space = false
|
||||||
|
ij_java_block_comment_at_first_column = true
|
||||||
|
ij_java_builder_methods = none
|
||||||
|
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_not_wrap_after_single_annotation_in_parameter = 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.**,|,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_builder_methods_indents = false
|
||||||
|
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_add_space_on_reformat = 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_multi_catch_types_wrap = normal
|
||||||
|
ij_java_names_count_to_use_import_on_demand = 99
|
||||||
|
ij_java_new_line_after_lparen_in_annotation = false
|
||||||
|
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_annotation = false
|
||||||
|
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 = false
|
||||||
|
ij_java_space_before_catch_left_brace = false
|
||||||
|
ij_java_space_before_catch_parentheses = false
|
||||||
|
ij_java_space_before_class_left_brace = false
|
||||||
|
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 = false
|
||||||
|
ij_java_space_before_else_keyword = false
|
||||||
|
ij_java_space_before_else_left_brace = false
|
||||||
|
ij_java_space_before_finally_keyword = false
|
||||||
|
ij_java_space_before_finally_left_brace = false
|
||||||
|
ij_java_space_before_for_left_brace = false
|
||||||
|
ij_java_space_before_for_parentheses = false
|
||||||
|
ij_java_space_before_for_semicolon = false
|
||||||
|
ij_java_space_before_if_left_brace = false
|
||||||
|
ij_java_space_before_if_parentheses = false
|
||||||
|
ij_java_space_before_method_call_parentheses = false
|
||||||
|
ij_java_space_before_method_left_brace = false
|
||||||
|
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 = false
|
||||||
|
ij_java_space_before_switch_parentheses = false
|
||||||
|
ij_java_space_before_synchronized_left_brace = false
|
||||||
|
ij_java_space_before_synchronized_parentheses = false
|
||||||
|
ij_java_space_before_try_left_brace = false
|
||||||
|
ij_java_space_before_try_parentheses = false
|
||||||
|
ij_java_space_before_type_parameter_list = false
|
||||||
|
ij_java_space_before_while_keyword = false
|
||||||
|
ij_java_space_before_while_left_brace = false
|
||||||
|
ij_java_space_before_while_parentheses = false
|
||||||
|
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 = false
|
||||||
|
ij_java_spaces_around_annotation_eq = true
|
||||||
|
ij_java_spaces_around_assignment_operators = false
|
||||||
|
ij_java_spaces_around_bitwise_operators = false
|
||||||
|
ij_java_spaces_around_equality_operators = false
|
||||||
|
ij_java_spaces_around_lambda_arrow = false
|
||||||
|
ij_java_spaces_around_logical_operators = true
|
||||||
|
ij_java_spaces_around_method_ref_dbl_colon = false
|
||||||
|
ij_java_spaces_around_multiplicative_operators = false
|
||||||
|
ij_java_spaces_around_relational_operators = false
|
||||||
|
ij_java_spaces_around_shift_operators = false
|
||||||
|
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
|
||||||
|
|
||||||
|
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
|
||||||
|
ij_continuation_indent_size = 4
|
||||||
|
ij_xml_align_attributes = false
|
||||||
|
ij_xml_align_text = false
|
||||||
|
ij_xml_attribute_wrap = normal
|
||||||
|
ij_xml_block_comment_add_space = false
|
||||||
|
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
|
|
@ -15,8 +15,8 @@ android {
|
||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 95
|
versionCode 96
|
||||||
versionName "1.2.3+fork.95"
|
versionName "2.0.1+fork.96"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,8 @@ dependencies {
|
||||||
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.8'
|
implementation 'me.grishka.litex:palette:1.0.0'
|
||||||
|
implementation 'me.grishka.appkit:appkit:1.2.9'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
@ -78,8 +79,8 @@ dependencies {
|
||||||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.4.1-alpha05'
|
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||||
@LargeTest
|
@LargeTest
|
||||||
public class StoreScreenshotsGenerator{
|
public class StoreScreenshotsGenerator{
|
||||||
private static final String PHOTO_FILE="IMG_1010.jpg";
|
private static final String PHOTO_FILE="IMG_1010.jpg";
|
||||||
|
private static final long LOAD_WAIT_TIMEOUT=20_000;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ActivityScenarioRule<MainActivity> activityScenarioRule=new ActivityScenarioRule<>(MainActivity.class);
|
public ActivityScenarioRule<MainActivity> activityScenarioRule=new ActivityScenarioRule<>(MainActivity.class);
|
||||||
|
@ -84,14 +85,14 @@ public class StoreScreenshotsGenerator{
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(AccountSessionManager.getInstance().getLastActiveAccountID());
|
AccountSession session=AccountSessionManager.getInstance().getAccount(AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||||
MastodonApp.context.deleteDatabase(session.getID()+".db");
|
MastodonApp.context.deleteDatabase(session.getID()+".db");
|
||||||
|
|
||||||
onView(isRoot()).perform(waitId(R.id.more, 5000));
|
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT));
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
takeScreenshot("HomeTimeline");
|
takeScreenshot("HomeTimeline");
|
||||||
|
|
||||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK;
|
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK;
|
||||||
activityScenarioRule.getScenario().recreate();
|
activityScenarioRule.getScenario().recreate();
|
||||||
|
|
||||||
onView(isRoot()).perform(waitId(R.id.more, 5000));
|
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT));
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
takeScreenshot("HomeTimeline_Dark");
|
takeScreenshot("HomeTimeline_Dark");
|
||||||
|
|
||||||
|
@ -100,8 +101,8 @@ public class StoreScreenshotsGenerator{
|
||||||
|
|
||||||
activityScenarioRule.getScenario().onActivity(activity->UiUtils.openProfileByID(activity, session.getID(), args.getString("profileAccountID")));
|
activityScenarioRule.getScenario().onActivity(activity->UiUtils.openProfileByID(activity, session.getID(), args.getString("profileAccountID")));
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
onView(isRoot()).perform(waitId(R.id.avatar_border, 5000)); // wait for profile to load
|
onView(isRoot()).perform(waitId(R.id.avatar_border, LOAD_WAIT_TIMEOUT)); // wait for profile to load
|
||||||
onView(isRoot()).perform(waitId(R.id.more, 5000)); // wait for timeline to load
|
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT)); // wait for timeline to load
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
takeScreenshot("Profile");
|
takeScreenshot("Profile");
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,22 @@ package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.AccountField;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class UiUtilsTest {
|
public class UiUtilsTest {
|
||||||
|
@ -103,4 +110,152 @@ public class UiUtilsTest {
|
||||||
"somewhere.else"
|
"somewhere.else"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final String[] args = new String[] { "Megalodon", "♡" };
|
||||||
|
|
||||||
|
private String gen(String format, CharSequence... args) {
|
||||||
|
return UiUtils.generateFormattedString(format, args).toString();
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void generateFormattedString() {
|
||||||
|
assertEquals(
|
||||||
|
"ordered substitution",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen("%s reacted with %s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"1 2 3 4 5",
|
||||||
|
gen("%s %s %s %s %s", "1", "2", "3", "4", "5")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"indexed substitution",
|
||||||
|
"with ♡ was reacted by Megalodon",
|
||||||
|
gen("with %2$s was reacted by %1$s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"indexed substitution, in order",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen("%1$s reacted with %2$s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"indexed substitution, 0-based",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen("%0$s reacted with %1$s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"indexed substitution, 5 items",
|
||||||
|
"5 4 3 2 1",
|
||||||
|
gen("%5$s %4$s %3$s %2$s %1$s", "1", "2", "3", "4", "5")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"one argument missing",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen("reacted with %s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"multiple arguments missing",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen("reacted with", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"multiple arguments missing, numbers in expeced positions",
|
||||||
|
"1 2 x 3 4 5",
|
||||||
|
gen("%s x %s", "1", "2", "3", "4", "5")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"one leading and trailing space",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen(" reacted with ", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"multiple leading and trailing spaces",
|
||||||
|
"Megalodon reacted with ♡",
|
||||||
|
gen(" reacted with ", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"invalid format produces expected invalid result",
|
||||||
|
"Megalodon reacted with % s ♡",
|
||||||
|
gen("reacted with % s", args)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"plain string as format, all arguments get added",
|
||||||
|
"a x b c",
|
||||||
|
gen("x", new String[] { "a", "b", "c" })
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("empty input produces empty output", "", gen(""));
|
||||||
|
|
||||||
|
// not supported:
|
||||||
|
// assertEquals("a b a", gen("%1$s %2$s %2$s %1$s", new String[] { "a", "b", "c" }));
|
||||||
|
// assertEquals("x", gen("%s %1$s %2$s %1$s %s", new String[] { "a", "b", "c" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountField makeField(String name, String value) {
|
||||||
|
AccountField f = new AccountField();
|
||||||
|
f.name = name;
|
||||||
|
f.value = value;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Account fakeAccount(AccountField... fields) {
|
||||||
|
Account a = new Account();
|
||||||
|
a.fields = Arrays.asList(fields);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void extractPronouns() {
|
||||||
|
assertEquals("they", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("name and pronouns", "https://pronouns.site"),
|
||||||
|
makeField("pronouns", "they"),
|
||||||
|
makeField("pronouns something", "bla bla")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertTrue(UiUtils.extractPronouns(MastodonApp.context, fakeAccount()).isEmpty());
|
||||||
|
|
||||||
|
assertEquals("it/its", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("pronouns pronouns pronouns", "hi hi hi"),
|
||||||
|
makeField("pronouns", "it/its"),
|
||||||
|
makeField("the pro's nouns", "professional")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertEquals("she/he", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("my name is", "jeanette shork, apparently"),
|
||||||
|
makeField("my pronouns are", "she/he")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertEquals("they/them", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("pronouns", "https://pronouns.cc/pronouns/they/them")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
Context german = UiUtils.getLocalizedContext(MastodonApp.context, Locale.GERMAN);
|
||||||
|
|
||||||
|
assertEquals("sie/ihr", UiUtils.extractPronouns(german, fakeAccount(
|
||||||
|
makeField("pronomen lauten", "sie/ihr"),
|
||||||
|
makeField("pronouns are", "she/her"),
|
||||||
|
makeField("die pronomen", "stehen oben")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertEquals("er/ihm", UiUtils.extractPronouns(german, fakeAccount(
|
||||||
|
makeField("die pronomen", "stehen unten"),
|
||||||
|
makeField("pronomen sind", "er/ihm"),
|
||||||
|
makeField("pronouns are", "he/him")
|
||||||
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("pronouns", "-- * (asterisk) --")
|
||||||
|
)).orElseThrow());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package org.joinmastodon.android.utils;
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
import static org.joinmastodon.android.model.Filter.FilterAction.*;
|
import static org.joinmastodon.android.model.FilterAction.*;
|
||||||
import static org.joinmastodon.android.model.Filter.FilterContext.*;
|
import static org.joinmastodon.android.model.FilterContext.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ import java.util.List;
|
||||||
|
|
||||||
public class StatusFilterPredicateTest {
|
public class StatusFilterPredicateTest {
|
||||||
|
|
||||||
private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter();
|
private static final LegacyFilter hideMeFilter = new LegacyFilter(), warnMeFilter = new LegacyFilter();
|
||||||
private static final List<Filter> allFilters = List.of(hideMeFilter, warnMeFilter);
|
private static final List<LegacyFilter> allFilters = List.of(hideMeFilter, warnMeFilter);
|
||||||
|
|
||||||
private static final Status
|
private static final Status
|
||||||
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
||||||
|
|
|
@ -100,8 +100,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||||
public void maybeCheckForUpdates(){
|
public void maybeCheckForUpdates(){
|
||||||
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
||||||
return;
|
return;
|
||||||
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
|
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", 0);
|
||||||
if(timeSinceLastCheck>=CHECK_PERIOD){
|
if(timeSinceLastCheck>CHECK_PERIOD || forceUpdate){
|
||||||
setState(UpdateState.CHECKING);
|
setState(UpdateState.CHECKING);
|
||||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||||
curForkNumber=Integer.parseInt(matcher.group(4));
|
curForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||||
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||||
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
if(newVersion>curVersion || newForkNumber>curForkNumber || forceUpdate){
|
||||||
|
forceUpdate=false;
|
||||||
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
|
@ -323,6 +324,15 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(){
|
||||||
|
getPrefs().edit().clear().apply();
|
||||||
|
File apk=getUpdateApkFile();
|
||||||
|
if(apk.exists())
|
||||||
|
apk.delete();
|
||||||
|
state=UpdateState.NO_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||||
android:largeHeap="true">
|
android:largeHeap="true">
|
||||||
|
|
||||||
|
@ -37,6 +36,18 @@
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="https" android:host="mastodon.social" android:pathPrefix="/@"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="https" android:host="mastodon.online" android:pathPrefix="/@"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".PanicResponderActivity"
|
android:name=".PanicResponderActivity"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
13bells.com
|
13bells.com
|
||||||
|
1611.social
|
||||||
4aem.com
|
4aem.com
|
||||||
aethy.com
|
adachi.party
|
||||||
anime.website
|
anime.website
|
||||||
annihilation.social
|
annihilation.social
|
||||||
anon-kenkai.com
|
anon-kenkai.com
|
||||||
|
@ -9,14 +10,17 @@ bae.st
|
||||||
bajax.us
|
bajax.us
|
||||||
banepo.st
|
banepo.st
|
||||||
baraag.net
|
baraag.net
|
||||||
|
bassam.social
|
||||||
beefyboys.win
|
beefyboys.win
|
||||||
beepboop.ga
|
beepboop.ga
|
||||||
berserker.town
|
berserker.town
|
||||||
bikeshed.party
|
bikeshed.party
|
||||||
boks.moe
|
boks.moe
|
||||||
|
boymoder.biz
|
||||||
brainsoap.net
|
brainsoap.net
|
||||||
breastmilk.club
|
breastmilk.club
|
||||||
brighteon.social
|
brighteon.social
|
||||||
|
bungle.online
|
||||||
cawfee.club
|
cawfee.club
|
||||||
clew.lol
|
clew.lol
|
||||||
clubcyberia.co
|
clubcyberia.co
|
||||||
|
@ -25,8 +29,8 @@ comfyboy.club
|
||||||
contrapointsfan.club
|
contrapointsfan.club
|
||||||
cum.camp
|
cum.camp
|
||||||
cum.salon
|
cum.salon
|
||||||
cybercriminal.eu
|
|
||||||
darknight-coffee.org
|
darknight-coffee.org
|
||||||
|
decayable.ink
|
||||||
dembased.xyz
|
dembased.xyz
|
||||||
desupost.soy
|
desupost.soy
|
||||||
detroitriotcity.com
|
detroitriotcity.com
|
||||||
|
@ -40,7 +44,6 @@ fluf.club
|
||||||
foxfam.club
|
foxfam.club
|
||||||
freak.university
|
freak.university
|
||||||
freeatlantis.com
|
freeatlantis.com
|
||||||
freecumextremist.com
|
|
||||||
freedomstrike.org
|
freedomstrike.org
|
||||||
freesoftwareextremist.com
|
freesoftwareextremist.com
|
||||||
freespeech.group
|
freespeech.group
|
||||||
|
@ -59,7 +62,6 @@ goyim.app
|
||||||
goyslop.cafe
|
goyslop.cafe
|
||||||
haeder.net
|
haeder.net
|
||||||
handholding.io
|
handholding.io
|
||||||
hidamari.apartments
|
|
||||||
hitchhiker.social
|
hitchhiker.social
|
||||||
hunk.city
|
hunk.city
|
||||||
iddqd.social
|
iddqd.social
|
||||||
|
@ -75,14 +77,13 @@ leftychan.net
|
||||||
lewdieheaven.com
|
lewdieheaven.com
|
||||||
liberdon.com
|
liberdon.com
|
||||||
ligma.pro
|
ligma.pro
|
||||||
lizards.live
|
|
||||||
lolicon.rocks
|
lolicon.rocks
|
||||||
lolison.top
|
lolison.top
|
||||||
lovingexpressions.net
|
lovingexpressions.net
|
||||||
lucasvl.nl
|
|
||||||
mahodou.moe
|
mahodou.moe
|
||||||
makemysarcophagus.com
|
makemysarcophagus.com
|
||||||
maladaptive.art
|
maladaptive.art
|
||||||
|
marsey.moe
|
||||||
masochi.st
|
masochi.st
|
||||||
mastinator.com
|
mastinator.com
|
||||||
merovingian.club
|
merovingian.club
|
||||||
|
@ -105,7 +106,6 @@ nobodyhasthe.biz
|
||||||
nukem.biz
|
nukem.biz
|
||||||
obo.sh
|
obo.sh
|
||||||
onionfarms.org
|
onionfarms.org
|
||||||
outpoa.st
|
|
||||||
pawlicker.com
|
pawlicker.com
|
||||||
pawoo.net
|
pawoo.net
|
||||||
pedo.school
|
pedo.school
|
||||||
|
@ -121,6 +121,7 @@ poast.tv
|
||||||
poster.place
|
poster.place
|
||||||
prospeech.space
|
prospeech.space
|
||||||
quodverum.com
|
quodverum.com
|
||||||
|
r18.social
|
||||||
rakket.app
|
rakket.app
|
||||||
rapemeat.solutions
|
rapemeat.solutions
|
||||||
rdrama.cc
|
rdrama.cc
|
||||||
|
@ -132,7 +133,6 @@ schwartzwelt.xyz
|
||||||
seal.cafe
|
seal.cafe
|
||||||
shigusegubu.club
|
shigusegubu.club
|
||||||
shitpost.cloud
|
shitpost.cloud
|
||||||
shitposter.club
|
|
||||||
shota.house
|
shota.house
|
||||||
silliness.observer
|
silliness.observer
|
||||||
skinheads.eu
|
skinheads.eu
|
||||||
|
@ -149,7 +149,6 @@ sonichu.com
|
||||||
spinster.xyz
|
spinster.xyz
|
||||||
springbo.cc
|
springbo.cc
|
||||||
starnix.network
|
starnix.network
|
||||||
stereophonic.space
|
|
||||||
strelizia.net
|
strelizia.net
|
||||||
syspxl.xyz
|
syspxl.xyz
|
||||||
tastingtraffic.net
|
tastingtraffic.net
|
||||||
|
|
51
mastodon/src/main/assets/server_about_template.htm
Normal file
51
mastodon/src/main/assets/server_about_template.htm
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
background: {{colorSurface}};
|
||||||
|
padding: 16px 16px 0 16px;
|
||||||
|
margin: 0;
|
||||||
|
color: {{colorOnSurface}};
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
-webkit-tap-highlight-color: {{colorPrimaryTransparent}};
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
text-decoration: none;
|
||||||
|
color: {{colorPrimary}};
|
||||||
|
}
|
||||||
|
p, h1, h2, h3, h4, h5, h6, ul, ol{
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
h1, h2{
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
h3, h4, h5, h6{
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
b, strong{
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
ul, ol{
|
||||||
|
padding-inline-start: 16px;
|
||||||
|
}
|
||||||
|
ul>li, ol>li{
|
||||||
|
padding-inline-start: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{content}}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -31,7 +31,6 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -57,6 +56,7 @@ public class AudioPlayerService extends Service{
|
||||||
private static HashSet<Callback> callbacks=new HashSet<>();
|
private static HashSet<Callback> callbacks=new HashSet<>();
|
||||||
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged;
|
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged;
|
||||||
private boolean resumeAfterAudioFocusGain;
|
private boolean resumeAfterAudioFocusGain;
|
||||||
|
private boolean isBuffering=true;
|
||||||
|
|
||||||
private BroadcastReceiver receiver=new BroadcastReceiver(){
|
private BroadcastReceiver receiver=new BroadcastReceiver(){
|
||||||
@Override
|
@Override
|
||||||
|
@ -176,6 +176,7 @@ public class AudioPlayerService extends Service{
|
||||||
player.setOnErrorListener(this::onPlayerError);
|
player.setOnErrorListener(this::onPlayerError);
|
||||||
player.setOnCompletionListener(this::onPlayerCompletion);
|
player.setOnCompletionListener(this::onPlayerCompletion);
|
||||||
player.setOnSeekCompleteListener(this::onPlayerSeekCompleted);
|
player.setOnSeekCompleteListener(this::onPlayerSeekCompleted);
|
||||||
|
player.setOnInfoListener(this::onPlayerInfo);
|
||||||
try{
|
try{
|
||||||
player.setDataSource(this, Uri.parse(attachment.url));
|
player.setDataSource(this, Uri.parse(attachment.url));
|
||||||
player.prepareAsync();
|
player.prepareAsync();
|
||||||
|
@ -187,7 +188,9 @@ public class AudioPlayerService extends Service{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onPlayerPrepared(MediaPlayer mp){
|
private void onPlayerPrepared(MediaPlayer mp){
|
||||||
|
Log.i(TAG, "onPlayerPrepared");
|
||||||
playerReady=true;
|
playerReady=true;
|
||||||
|
isBuffering=false;
|
||||||
player.start();
|
player.start();
|
||||||
updateSessionState(false);
|
updateSessionState(false);
|
||||||
}
|
}
|
||||||
|
@ -205,6 +208,21 @@ public class AudioPlayerService extends Service{
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onPlayerInfo(MediaPlayer mp, int what, int extra){
|
||||||
|
switch(what){
|
||||||
|
case MediaPlayer.MEDIA_INFO_BUFFERING_START -> {
|
||||||
|
isBuffering=true;
|
||||||
|
updateSessionState(false);
|
||||||
|
}
|
||||||
|
case MediaPlayer.MEDIA_INFO_BUFFERING_END -> {
|
||||||
|
isBuffering=false;
|
||||||
|
updateSessionState(false);
|
||||||
|
}
|
||||||
|
default -> Log.i(TAG, "onPlayerInfo() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void onAudioFocusChanged(int change){
|
private void onAudioFocusChanged(int change){
|
||||||
switch(change){
|
switch(change){
|
||||||
case AudioManager.AUDIOFOCUS_LOSS -> {
|
case AudioManager.AUDIOFOCUS_LOSS -> {
|
||||||
|
@ -212,7 +230,7 @@ public class AudioPlayerService extends Service{
|
||||||
pause(false);
|
pause(false);
|
||||||
}
|
}
|
||||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
||||||
resumeAfterAudioFocusGain=true;
|
resumeAfterAudioFocusGain=isPlaying();
|
||||||
pause(false);
|
pause(false);
|
||||||
}
|
}
|
||||||
case AudioManager.AUDIOFOCUS_GAIN -> {
|
case AudioManager.AUDIOFOCUS_GAIN -> {
|
||||||
|
@ -232,12 +250,16 @@ public class AudioPlayerService extends Service{
|
||||||
|
|
||||||
private void updateSessionState(boolean removeNotification){
|
private void updateSessionState(boolean removeNotification){
|
||||||
session.setPlaybackState(new PlaybackState.Builder()
|
session.setPlaybackState(new PlaybackState.Builder()
|
||||||
.setState(player.isPlaying() ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED, player.getCurrentPosition(), 1f)
|
.setState(switch(getPlayState()){
|
||||||
|
case PLAYING -> PlaybackState.STATE_PLAYING;
|
||||||
|
case PAUSED -> PlaybackState.STATE_PAUSED;
|
||||||
|
case BUFFERING -> PlaybackState.STATE_BUFFERING;
|
||||||
|
}, player.getCurrentPosition(), 1f)
|
||||||
.setActions(PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
|
.setActions(PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
|
||||||
.build());
|
.build());
|
||||||
updateNotification(!player.isPlaying(), removeNotification);
|
updateNotification(!player.isPlaying(), removeNotification);
|
||||||
for(Callback cb:callbacks)
|
for(Callback cb:callbacks)
|
||||||
cb.onPlayStateChanged(attachment.id, player.isPlaying(), player.getCurrentPosition());
|
cb.onPlayStateChanged(attachment.id, getPlayState(), player.getCurrentPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateNotification(boolean dismissable, boolean removeNotification){
|
private void updateNotification(boolean dismissable, boolean removeNotification){
|
||||||
|
@ -310,6 +332,12 @@ public class AudioPlayerService extends Service{
|
||||||
return attachment.id;
|
return attachment.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PlayState getPlayState(){
|
||||||
|
if(isBuffering)
|
||||||
|
return PlayState.BUFFERING;
|
||||||
|
return player.isPlaying() ? PlayState.PLAYING : PlayState.PAUSED;
|
||||||
|
}
|
||||||
|
|
||||||
public static void registerCallback(Callback cb){
|
public static void registerCallback(Callback cb){
|
||||||
callbacks.add(cb);
|
callbacks.add(cb);
|
||||||
}
|
}
|
||||||
|
@ -333,7 +361,13 @@ public class AudioPlayerService extends Service{
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Callback{
|
public interface Callback{
|
||||||
void onPlayStateChanged(String attachmentID, boolean playing, int position);
|
void onPlayStateChanged(String attachmentID, PlayState state, int position);
|
||||||
void onPlaybackStopped(String attachmentID);
|
void onPlaybackStopped(String attachmentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PlayState{
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
BUFFERING
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,136 +4,113 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
|
private static final String TAG="GlobalUserPreferences";
|
||||||
|
|
||||||
public static boolean playGifs;
|
public static boolean playGifs;
|
||||||
public static boolean useCustomTabs;
|
public static boolean useCustomTabs;
|
||||||
|
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
|
||||||
|
public static ThemePreference theme;
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
public static boolean trueBlackTheme;
|
public static boolean trueBlackTheme;
|
||||||
public static boolean showReplies;
|
|
||||||
public static boolean showBoosts;
|
|
||||||
public static boolean loadNewPosts;
|
public static boolean loadNewPosts;
|
||||||
public static boolean showNewPostsButton;
|
public static boolean showNewPostsButton;
|
||||||
public static boolean showInteractionCounts;
|
public static boolean toolbarMarquee;
|
||||||
public static boolean alwaysExpandContentWarnings;
|
|
||||||
public static boolean disableMarquee;
|
|
||||||
public static boolean disableSwipe;
|
public static boolean disableSwipe;
|
||||||
public static boolean voteButtonForSingleChoice;
|
public static boolean voteButtonForSingleChoice;
|
||||||
public static boolean enableDeleteNotifications;
|
public static boolean enableDeleteNotifications;
|
||||||
public static boolean translateButtonOpenedOnly;
|
public static boolean translateButtonOpenedOnly;
|
||||||
public static boolean uniformNotificationIcon;
|
public static boolean uniformNotificationIcon;
|
||||||
public static boolean reduceMotion;
|
public static boolean reduceMotion;
|
||||||
public static boolean keepOnlyLatestNotification;
|
|
||||||
public static boolean disableAltTextReminder;
|
|
||||||
public static boolean showAltIndicator;
|
public static boolean showAltIndicator;
|
||||||
public static boolean showNoAltIndicator;
|
public static boolean showNoAltIndicator;
|
||||||
public static boolean enablePreReleases;
|
public static boolean enablePreReleases;
|
||||||
public static PrefixRepliesMode prefixReplies;
|
public static PrefixRepliesMode prefixReplies;
|
||||||
public static boolean bottomEncoding;
|
|
||||||
public static boolean collapseLongPosts;
|
public static boolean collapseLongPosts;
|
||||||
public static boolean spectatorMode;
|
public static boolean spectatorMode;
|
||||||
public static boolean autoHideFab;
|
public static boolean autoHideFab;
|
||||||
public static boolean replyLineAboveHeader;
|
|
||||||
public static boolean compactReblogReplyLine;
|
public static boolean compactReblogReplyLine;
|
||||||
public static boolean confirmBeforeReblog;
|
|
||||||
public static boolean allowRemoteLoading;
|
public static boolean allowRemoteLoading;
|
||||||
public static boolean forwardReportDefault;
|
public static boolean forwardReportDefault;
|
||||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||||
public static String publishButtonText;
|
|
||||||
public static ThemePreference theme;
|
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
public static boolean disableM3PillActiveIndicator;
|
||||||
public static Map<String, List<String>> recentLanguages;
|
|
||||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
|
||||||
public static Set<String> accountsWithLocalOnlySupport;
|
|
||||||
public static Set<String> accountsInGlitchMode;
|
|
||||||
public static Set<String> accountsWithContentTypesEnabled;
|
|
||||||
public static Map<String, ContentType> accountsDefaultContentTypes;
|
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
|
||||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
|
||||||
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pleroma
|
|
||||||
*/
|
|
||||||
public static String replyVisibility;
|
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
public static <T> T fromJson(String json, Type type, T orElse){
|
||||||
if (json == null) return orElse;
|
if(json==null) return orElse;
|
||||||
try { return gson.fromJson(json, type); }
|
try{
|
||||||
catch (JsonSyntaxException ignored) { return orElse; }
|
T value=gson.fromJson(json, type);
|
||||||
|
return value==null ? orElse : value;
|
||||||
|
}catch(JsonSyntaxException ignored){
|
||||||
|
return orElse;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeAccount(String accountId) {
|
public static <T extends Enum<T>> T enumValue(Class<T> enumType, String name) {
|
||||||
recentLanguages.remove(accountId);
|
try { return Enum.valueOf(enumType, name); }
|
||||||
pinnedTimelines.remove(accountId);
|
catch (NullPointerException npe) { return null; }
|
||||||
accountsInGlitchMode.remove(accountId);
|
|
||||||
accountsWithLocalOnlySupport.remove(accountId);
|
|
||||||
accountsWithContentTypesEnabled.remove(accountId);
|
|
||||||
accountsDefaultContentTypes.remove(accountId);
|
|
||||||
save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load(){
|
public static void load(){
|
||||||
SharedPreferences prefs=getPrefs();
|
SharedPreferences prefs=getPrefs();
|
||||||
|
|
||||||
playGifs=prefs.getBoolean("playGifs", true);
|
playGifs=prefs.getBoolean("playGifs", true);
|
||||||
useCustomTabs=prefs.getBoolean("useCustomTabs", true);
|
useCustomTabs=prefs.getBoolean("useCustomTabs", true);
|
||||||
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
|
altTextReminders=prefs.getBoolean("altTextReminders", true);
|
||||||
|
confirmUnfollow=prefs.getBoolean("confirmUnfollow", true);
|
||||||
|
confirmBoost=prefs.getBoolean("confirmBoost", false);
|
||||||
|
confirmDeletePost=prefs.getBoolean("confirmDeletePost", true);
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
trueBlackTheme=prefs.getBoolean("trueBlackTheme", false);
|
trueBlackTheme=prefs.getBoolean("trueBlackTheme", false);
|
||||||
showReplies=prefs.getBoolean("showReplies", true);
|
|
||||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
|
||||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||||
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
||||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
toolbarMarquee=prefs.getBoolean("toolbarMarquee", true);
|
||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
|
||||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
||||||
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
||||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||||
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
|
||||||
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
|
||||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||||
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
||||||
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
||||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
|
||||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
|
||||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||||
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
|
||||||
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
|
||||||
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
|
||||||
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
|
||||||
replyVisibility=prefs.getString("replyVisibility", null);
|
|
||||||
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
|
||||||
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
|
||||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||||
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
||||||
|
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
|
||||||
|
|
||||||
if (prefs.contains("prefixRepliesWithRe")) {
|
if (prefs.contains("prefixRepliesWithRe")) {
|
||||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||||
|
@ -150,27 +127,30 @@ public class GlobalUserPreferences{
|
||||||
// invalid color name or color was previously saved as integer
|
// invalid color name or color was previously saved as integer
|
||||||
color=ColorPreference.PINK;
|
color=ColorPreference.PINK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(){
|
public static void save(){
|
||||||
getPrefs().edit()
|
getPrefs().edit()
|
||||||
.putBoolean("playGifs", playGifs)
|
.putBoolean("playGifs", playGifs)
|
||||||
.putBoolean("useCustomTabs", useCustomTabs)
|
.putBoolean("useCustomTabs", useCustomTabs)
|
||||||
.putBoolean("showReplies", showReplies)
|
.putInt("theme", theme.ordinal())
|
||||||
.putBoolean("showBoosts", showBoosts)
|
.putBoolean("altTextReminders", altTextReminders)
|
||||||
|
.putBoolean("confirmUnfollow", confirmUnfollow)
|
||||||
|
.putBoolean("confirmBoost", confirmBoost)
|
||||||
|
.putBoolean("confirmDeletePost", confirmDeletePost)
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
.putBoolean("loadNewPosts", loadNewPosts)
|
.putBoolean("loadNewPosts", loadNewPosts)
|
||||||
.putBoolean("showNewPostsButton", showNewPostsButton)
|
.putBoolean("showNewPostsButton", showNewPostsButton)
|
||||||
.putBoolean("trueBlackTheme", trueBlackTheme)
|
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
.putBoolean("toolbarMarquee", toolbarMarquee)
|
||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
|
||||||
.putBoolean("disableMarquee", disableMarquee)
|
|
||||||
.putBoolean("disableSwipe", disableSwipe)
|
.putBoolean("disableSwipe", disableSwipe)
|
||||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||||
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
||||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||||
.putBoolean("reduceMotion", reduceMotion)
|
.putBoolean("reduceMotion", reduceMotion)
|
||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
|
||||||
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
|
||||||
.putBoolean("showAltIndicator", showAltIndicator)
|
.putBoolean("showAltIndicator", showAltIndicator)
|
||||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||||
.putBoolean("enablePreReleases", enablePreReleases)
|
.putBoolean("enablePreReleases", enablePreReleases)
|
||||||
|
@ -179,25 +159,64 @@ public class GlobalUserPreferences{
|
||||||
.putBoolean("spectatorMode", spectatorMode)
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
.putBoolean("autoHideFab", autoHideFab)
|
.putBoolean("autoHideFab", autoHideFab)
|
||||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||||
.putString("publishButtonText", publishButtonText)
|
|
||||||
.putBoolean("bottomEncoding", bottomEncoding)
|
|
||||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
|
||||||
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
|
||||||
.putInt("theme", theme.ordinal())
|
|
||||||
.putString("color", color.name())
|
.putString("color", color.name())
|
||||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
|
||||||
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
|
|
||||||
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
|
||||||
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
|
||||||
.putString("replyVisibility", replyVisibility)
|
|
||||||
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
|
||||||
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
|
||||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||||
|
.putBoolean("disableM3PillActiveIndicator", disableM3PillActiveIndicator)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void migrateToUpstreamVersion61(){
|
||||||
|
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
||||||
|
|
||||||
|
Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||||
|
Type pinnedTimelinesType = new TypeToken<Map<String, ArrayList<TimelineDefinition>>>() {}.getType();
|
||||||
|
Type recentLanguagesType = new TypeToken<Map<String, ArrayList<String>>>() {}.getType();
|
||||||
|
|
||||||
|
// migrate global preferences
|
||||||
|
SharedPreferences prefs=getPrefs();
|
||||||
|
altTextReminders=!prefs.getBoolean("disableAltTextReminder", false);
|
||||||
|
confirmBoost=prefs.getBoolean("confirmBeforeReblog", false);
|
||||||
|
toolbarMarquee=!prefs.getBoolean("disableMarquee", false);
|
||||||
|
|
||||||
|
save();
|
||||||
|
|
||||||
|
// migrate local preferences
|
||||||
|
AccountSessionManager asm=AccountSessionManager.getInstance();
|
||||||
|
// reset: Set<String> accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
||||||
|
Map<String, ContentType> accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||||
|
Map<String, ArrayList<TimelineDefinition>> pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
||||||
|
Set<String> accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
||||||
|
Set<String> accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
||||||
|
Map<String, ArrayList<String>> recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
||||||
|
|
||||||
|
for(AccountSession session : asm.getLoggedInAccounts()){
|
||||||
|
String accountID=session.getID();
|
||||||
|
AccountLocalPreferences localPrefs=session.getLocalPreferences();
|
||||||
|
localPrefs.revealCWs=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
|
localPrefs.recentLanguages=recentLanguages.get(accountID);
|
||||||
|
// reset: localPrefs.contentTypesEnabled=accountsWithContentTypesEnabled.contains(accountID);
|
||||||
|
localPrefs.defaultContentType=accountsDefaultContentTypes.getOrDefault(accountID, ContentType.PLAIN);
|
||||||
|
localPrefs.showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||||
|
localPrefs.timelines=pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID));
|
||||||
|
localPrefs.localOnlySupported=accountsWithLocalOnlySupport.contains(accountID);
|
||||||
|
localPrefs.glitchInstance=accountsInGlitchMode.contains(accountID);
|
||||||
|
localPrefs.publishButtonText=prefs.getString("publishButtonText", null);
|
||||||
|
localPrefs.keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
|
localPrefs.showReplies=prefs.getBoolean("showReplies", true);
|
||||||
|
localPrefs.showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
|
|
||||||
|
if(session.getInstance().map(Instance::isAkkoma).orElse(false)){
|
||||||
|
localPrefs.timelineReplyVisibility=prefs.getString("replyVisibility", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
localPrefs.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.edit().putInt("migrationLevel", 61).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public enum ColorPreference{
|
public enum ColorPreference{
|
||||||
MATERIAL3,
|
MATERIAL3,
|
||||||
PINK,
|
PINK,
|
||||||
|
@ -206,7 +225,20 @@ public class GlobalUserPreferences{
|
||||||
BLUE,
|
BLUE,
|
||||||
BROWN,
|
BROWN,
|
||||||
RED,
|
RED,
|
||||||
YELLOW
|
YELLOW;
|
||||||
|
|
||||||
|
public @StringRes int getName() {
|
||||||
|
return switch(this){
|
||||||
|
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||||
|
case PINK -> R.string.sk_color_palette_pink;
|
||||||
|
case PURPLE -> R.string.sk_color_palette_purple;
|
||||||
|
case GREEN -> R.string.sk_color_palette_green;
|
||||||
|
case BLUE -> R.string.sk_color_palette_blue;
|
||||||
|
case BROWN -> R.string.sk_color_palette_brown;
|
||||||
|
case RED -> R.string.sk_color_palette_red;
|
||||||
|
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ThemePreference{
|
public enum ThemePreference{
|
||||||
|
|
|
@ -5,13 +5,16 @@ import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
|
@ -21,6 +24,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
@ -28,6 +32,9 @@ import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,6 +80,8 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||||
showFragmentForNotification(notification, session.getID());
|
showFragmentForNotification(notification, session.getID());
|
||||||
} else if (intent.getBooleanExtra("compose", false)){
|
} else if (intent.getBooleanExtra("compose", false)){
|
||||||
showCompose();
|
showCompose();
|
||||||
|
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||||
|
handleURL(intent.getData(), null);
|
||||||
} else {
|
} else {
|
||||||
showFragmentClearingBackStack(fragment);
|
showFragmentClearingBackStack(fragment);
|
||||||
maybeRequestNotificationsPermission();
|
maybeRequestNotificationsPermission();
|
||||||
|
@ -111,11 +120,55 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||||
}
|
}
|
||||||
}else if(intent.getBooleanExtra("compose", false)){
|
}else if(intent.getBooleanExtra("compose", false)){
|
||||||
showCompose();
|
showCompose();
|
||||||
|
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||||
|
handleURL(intent.getData(), null);
|
||||||
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
|
||||||
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleURL(Uri uri, String accountID){
|
||||||
|
if(uri==null)
|
||||||
|
return;
|
||||||
|
if(!"https".equals(uri.getScheme()) && !"http".equals(uri.getScheme()))
|
||||||
|
return;
|
||||||
|
AccountSession session;
|
||||||
|
if(accountID==null)
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
else
|
||||||
|
session=AccountSessionManager.get(accountID);
|
||||||
|
if(session==null || !session.activated)
|
||||||
|
return;
|
||||||
|
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||||
|
new GetSearchResults(q, null, true)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults result){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
if(result.statuses!=null && !result.statuses.isEmpty()){
|
||||||
|
args.putParcelable("status", Parcels.wrap(result.statuses.get(0)));
|
||||||
|
Nav.go(MainActivity.this, ThreadFragment.class, args);
|
||||||
|
}else if(result.accounts!=null && !result.accounts.isEmpty()){
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(result.accounts.get(0)));
|
||||||
|
Nav.go(MainActivity.this, ProfileFragment.class, args);
|
||||||
|
}else{
|
||||||
|
Toast.makeText(MainActivity.this, fromSearch ? R.string.no_search_results : R.string.link_not_supported, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
error.showToast(MainActivity.this);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(this, progressText, true)
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
private void showFragmentForNotification(Notification notification, String accountID){
|
private void showFragmentForNotification(Notification notification, String accountID){
|
||||||
try{
|
try{
|
||||||
notification.postprocess();
|
notification.postprocess();
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.joinmastodon.android;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
|
|
||||||
|
@ -28,5 +29,8 @@ public class MastodonApp extends Application{
|
||||||
|
|
||||||
PushSubscriptionManager.tryRegisterFCM();
|
PushSubscriptionManager.tryRegisterFCM();
|
||||||
GlobalUserPreferences.load();
|
GlobalUserPreferences.load();
|
||||||
|
if(BuildConfig.DEBUG){
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Mention;
|
import org.joinmastodon.android.model.Mention;
|
||||||
import org.joinmastodon.android.model.NotificationAction;
|
import org.joinmastodon.android.model.NotificationAction;
|
||||||
|
@ -38,7 +37,9 @@ import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -57,6 +58,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||||
|
|
||||||
private static final int SUMMARY_ID = 791;
|
private static final int SUMMARY_ID = 791;
|
||||||
private static int notificationId = 0;
|
private static int notificationId = 0;
|
||||||
|
private static Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
|
@ -88,9 +90,12 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||||
Log.w(TAG, "onReceive: account for id '"+pushAccountID+"' not found");
|
Log.w(TAG, "onReceive: account for id '"+pushAccountID+"' not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(account.getLocalPreferences().getNotificationsPauseEndTime()>System.currentTimeMillis()){
|
||||||
|
Log.i(TAG, "onReceive: dropping notification because user has paused notifications for this account");
|
||||||
|
return;
|
||||||
|
}
|
||||||
String accountID=account.getID();
|
String accountID=account.getID();
|
||||||
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
||||||
E.post(new NotificationReceivedEvent(accountID, pn.notificationId+""));
|
|
||||||
new GetNotificationByID(pn.notificationId+"")
|
new GetNotificationByID(pn.notificationId+"")
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,7 +150,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||||
|
|
||||||
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
AccountSession session=AccountSessionManager.get(accountID);
|
||||||
|
Account self=session.self;
|
||||||
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
Notification.Builder builder;
|
Notification.Builder builder;
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||||
|
@ -215,7 +221,21 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||||
builder.setSubText(accountName);
|
builder.setSubText(accountName);
|
||||||
}
|
}
|
||||||
|
|
||||||
int id = GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++;
|
int id;
|
||||||
|
if(session.getLocalPreferences().keepOnlyLatestNotification){
|
||||||
|
if(notificationIdsForAccounts.containsKey(accountID)){
|
||||||
|
// we overwrite the existing notification
|
||||||
|
id=notificationIdsForAccounts.get(accountID);
|
||||||
|
}else{
|
||||||
|
// there's no existing notification, so we increment
|
||||||
|
id=notificationId++;
|
||||||
|
// and store the notification id for this account
|
||||||
|
notificationIdsForAccounts.put(accountID, id);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// we don't want to overwrite anything, therefore incrementing
|
||||||
|
id=notificationId++;
|
||||||
|
}
|
||||||
|
|
||||||
if (notification != null){
|
if (notification != null){
|
||||||
switch (pn.notificationType){
|
switch (pn.notificationType){
|
||||||
|
|
|
@ -13,22 +13,20 @@ import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
@ -43,6 +41,8 @@ public class CacheController{
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
private DatabaseHelper db;
|
private DatabaseHelper db;
|
||||||
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
private final Runnable databaseCloseRunnable=this::closeDatabase;
|
||||||
|
private boolean loadingNotifications;
|
||||||
|
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
|
||||||
|
|
||||||
private static final int POST_FLAG_GAP_AFTER=1;
|
private static final int POST_FLAG_GAP_AFTER=1;
|
||||||
|
|
||||||
|
@ -58,7 +58,6 @@ public class CacheController{
|
||||||
cancelDelayedClose();
|
cancelDelayedClose();
|
||||||
databaseThread.postRunnable(()->{
|
databaseThread.postRunnable(()->{
|
||||||
try{
|
try{
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
|
||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
SQLiteDatabase db=getOrOpenDatabase();
|
||||||
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||||
|
@ -66,18 +65,16 @@ public class CacheController{
|
||||||
ArrayList<Status> result=new ArrayList<>();
|
ArrayList<Status> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
String newMaxID;
|
String newMaxID;
|
||||||
outer:
|
|
||||||
do{
|
do{
|
||||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||||
status.postprocess();
|
status.postprocess();
|
||||||
int flags=cursor.getInt(1);
|
int flags=cursor.getInt(1);
|
||||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||||
newMaxID=status.id;
|
newMaxID=status.id;
|
||||||
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
|
|
||||||
continue outer;
|
|
||||||
result.add(status);
|
result.add(status);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,11 +82,13 @@ public class CacheController{
|
||||||
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
|
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new GetHomeTimeline(maxID, null, count, null)
|
new GetHomeTimeline(maxID, null, count, null, AccountSessionManager.get(accountID).getLocalPreferences().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
ArrayList<Status> filtered=new ArrayList<>(result);
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
|
||||||
|
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
putHomeTimeline(result, maxID==null);
|
putHomeTimeline(result, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,12 +125,39 @@ public class CacheController{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<CacheablePaginatedResponse<List<Notification>>> callback){
|
public void updateStatus(Status status) {
|
||||||
|
runOnDbThread((db)->{
|
||||||
|
ContentValues statusUpdate=new ContentValues(1);
|
||||||
|
statusUpdate.put("json", MastodonAPIController.gson.toJson(status));
|
||||||
|
db.update("home_timeline", statusUpdate, "id = ?", new String[] { status.id });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateNotification(Notification notification) {
|
||||||
|
runOnDbThread((db)->{
|
||||||
|
ContentValues notificationUpdate=new ContentValues(1);
|
||||||
|
notificationUpdate.put("json", MastodonAPIController.gson.toJson(notification));
|
||||||
|
String[] notificationArgs = new String[] { notification.id };
|
||||||
|
db.update("notifications_all", notificationUpdate, "id = ?", notificationArgs);
|
||||||
|
db.update("notifications_mentions", notificationUpdate, "id = ?", notificationArgs);
|
||||||
|
db.update("notifications_posts", notificationUpdate, "id = ?", notificationArgs);
|
||||||
|
|
||||||
|
ContentValues statusUpdate=new ContentValues(1);
|
||||||
|
statusUpdate.put("json", MastodonAPIController.gson.toJson(notification.status));
|
||||||
|
db.update("home_timeline", statusUpdate, "id = ?", new String[] { notification.status.id });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
|
||||||
cancelDelayedClose();
|
cancelDelayedClose();
|
||||||
databaseThread.postRunnable(()->{
|
databaseThread.postRunnable(()->{
|
||||||
try{
|
try{
|
||||||
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(accountID);
|
if(!onlyMentions && !onlyPosts && loadingNotifications){
|
||||||
List<Filter> filters=accountSession.wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
|
synchronized(pendingNotificationsCallbacks){
|
||||||
|
pendingNotificationsCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
SQLiteDatabase db=getOrOpenDatabase();
|
||||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||||
|
@ -140,42 +166,56 @@ public class CacheController{
|
||||||
ArrayList<Notification> result=new ArrayList<>();
|
ArrayList<Notification> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
String newMaxID;
|
String newMaxID;
|
||||||
outer:
|
|
||||||
do{
|
do{
|
||||||
Notification ntf=MastodonAPIController.gson.fromJson(cursor.getString(0), Notification.class);
|
Notification ntf=MastodonAPIController.gson.fromJson(cursor.getString(0), Notification.class);
|
||||||
ntf.postprocess();
|
ntf.postprocess();
|
||||||
newMaxID=ntf.id;
|
newMaxID=ntf.id;
|
||||||
if(ntf.status!=null){
|
|
||||||
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
|
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
result.add(ntf);
|
result.add(ntf);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
AccountSessionManager.get(accountID).filterStatusContainingObjects(result, n->n.status, FilterContext.NOTIFICATIONS);
|
||||||
|
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
|
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
if(!onlyMentions && !onlyPosts)
|
||||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
|
loadingNotifications=true;
|
||||||
|
boolean isAkkoma = AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
|
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), isAkkoma)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> result){
|
public void onSuccess(List<Notification> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
ArrayList<Notification> filtered=new ArrayList<>(result);
|
||||||
if(ntf.status!=null){
|
AccountSessionManager.get(accountID).filterStatusContainingObjects(filtered, n->n.status, FilterContext.NOTIFICATIONS);
|
||||||
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
|
PaginatedResponse<List<Notification>> res=new PaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id);
|
||||||
}
|
callback.onSuccess(res);
|
||||||
return true;
|
|
||||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
|
||||||
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
||||||
|
if(!onlyMentions){
|
||||||
|
loadingNotifications=false;
|
||||||
|
synchronized(pendingNotificationsCallbacks){
|
||||||
|
for(Callback<PaginatedResponse<List<Notification>>> cb:pendingNotificationsCallbacks){
|
||||||
|
cb.onSuccess(res);
|
||||||
|
}
|
||||||
|
pendingNotificationsCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
callback.onError(error);
|
callback.onError(error);
|
||||||
|
if(!onlyMentions){
|
||||||
|
loadingNotifications=false;
|
||||||
|
synchronized(pendingNotificationsCallbacks){
|
||||||
|
for(Callback<PaginatedResponse<List<Notification>>> cb:pendingNotificationsCallbacks){
|
||||||
|
cb.onError(error);
|
||||||
|
}
|
||||||
|
pendingNotificationsCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
@ -327,7 +367,7 @@ public class CacheController{
|
||||||
createRecentSearchesTable(db);
|
createRecentSearchesTable(db);
|
||||||
}
|
}
|
||||||
if(oldVersion<3){
|
if(oldVersion<3){
|
||||||
// MEGALODON-SPECIFIC
|
// MEGALODON
|
||||||
createPostsNotificationsTable(db);
|
createPostsNotificationsTable(db);
|
||||||
}
|
}
|
||||||
if(oldVersion<4){
|
if(oldVersion<4){
|
||||||
|
|
|
@ -117,6 +117,9 @@ public class MastodonAPIController{
|
||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=call;
|
req.okhttpCall=call;
|
||||||
}
|
}
|
||||||
|
if(req.timeout>0){
|
||||||
|
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
||||||
|
@ -153,13 +156,17 @@ public class MastodonAPIController{
|
||||||
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson);
|
||||||
if(req.respTypeToken!=null)
|
if(req.respTypeToken!=null)
|
||||||
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
|
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
|
||||||
else
|
else if(req.respClass!=null)
|
||||||
respObj=gson.fromJson(respJson, req.respClass);
|
respObj=gson.fromJson(respJson, req.respClass);
|
||||||
|
else
|
||||||
|
respObj=null;
|
||||||
}else{
|
}else{
|
||||||
if(req.respTypeToken!=null)
|
if(req.respTypeToken!=null)
|
||||||
respObj=gson.fromJson(reader, req.respTypeToken.getType());
|
respObj=gson.fromJson(reader, req.respTypeToken.getType());
|
||||||
else
|
else if(req.respClass!=null)
|
||||||
respObj=gson.fromJson(reader, req.respClass);
|
respObj=gson.fromJson(reader, req.respClass);
|
||||||
|
else
|
||||||
|
respObj=null;
|
||||||
}
|
}
|
||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
if (req.context != null && response.body().contentType().subtype().equals("html")) {
|
if (req.context != null && response.body().contentType().subtype().equals("html")) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
Token token;
|
Token token;
|
||||||
boolean canceled, isRemote;
|
boolean canceled, isRemote;
|
||||||
Map<String, String> headers;
|
Map<String, String> headers;
|
||||||
|
long timeout;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
protected boolean removeUnsupportedItems;
|
protected boolean removeUnsupportedItems;
|
||||||
@Nullable Context context;
|
@Nullable Context context;
|
||||||
|
@ -117,7 +118,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
.findAny())
|
.findAny())
|
||||||
.map(AccountSession::getID)
|
.map(AccountSession::getID)
|
||||||
.map(this::exec)
|
.map(this::exec)
|
||||||
.orElse(this.execNoAuth(domain));
|
.orElseGet(() -> this.execNoAuth(domain));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||||
|
@ -152,6 +153,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||||
headers.put(key, value);
|
headers.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setTimeout(long timeout){
|
||||||
|
this.timeout=timeout;
|
||||||
|
}
|
||||||
|
|
||||||
protected String getPathPrefix(){
|
protected String getPathPrefix(){
|
||||||
return "/api/v1";
|
return "/api/v1";
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,6 @@ public class PushSubscriptionManager{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private PrivateKey privateKey;
|
private PrivateKey privateKey;
|
||||||
private PublicKey publicKey;
|
private PublicKey publicKey;
|
||||||
private PublicKey serverKey;
|
|
||||||
private byte[] authKey;
|
private byte[] authKey;
|
||||||
|
|
||||||
public PushSubscriptionManager(String accountID){
|
public PushSubscriptionManager(String accountID){
|
||||||
|
@ -162,10 +161,6 @@ public class PushSubscriptionManager{
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
result.serverKey=result.serverKey.replace('/','_');
|
|
||||||
result.serverKey=result.serverKey.replace('+','-');
|
|
||||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||||
if(session==null)
|
if(session==null)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.joinmastodon.android.api;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
public abstract class ResultlessMastodonAPIRequest extends MastodonAPIRequest<Void>{
|
||||||
|
public ResultlessMastodonAPIRequest(HttpMethod method, String path){
|
||||||
|
super(method, path, (Class<Void>)null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetAccountFeaturedHashtags extends MastodonAPIRequest<List<Hashtag>>{
|
||||||
|
public GetAccountFeaturedHashtags(String id){
|
||||||
|
super(HttpMethod.GET, "/accounts/"+id+"/featured_tags", new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,22 +21,22 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
||||||
switch(filter){
|
switch(filter){
|
||||||
case DEFAULT -> addQueryParameter("exclude_replies", "true");
|
case DEFAULT -> addQueryParameter("exclude_replies", "true");
|
||||||
case INCLUDE_REPLIES -> {}
|
case INCLUDE_REPLIES -> {}
|
||||||
case PINNED -> addQueryParameter("pinned", "true");
|
|
||||||
case MEDIA -> addQueryParameter("only_media", "true");
|
case MEDIA -> addQueryParameter("only_media", "true");
|
||||||
case NO_REBLOGS -> {
|
case NO_REBLOGS -> {
|
||||||
addQueryParameter("exclude_replies", "true");
|
addQueryParameter("exclude_replies", "true");
|
||||||
addQueryParameter("exclude_reblogs", "true");
|
addQueryParameter("exclude_reblogs", "true");
|
||||||
}
|
}
|
||||||
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
||||||
|
case PINNED -> addQueryParameter("pinned", "true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Filter{
|
public enum Filter{
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
INCLUDE_REPLIES,
|
INCLUDE_REPLIES,
|
||||||
PINNED,
|
|
||||||
MEDIA,
|
MEDIA,
|
||||||
NO_REBLOGS,
|
NO_REBLOGS,
|
||||||
OWN_POSTS_AND_REPLIES
|
OWN_POSTS_AND_REPLIES,
|
||||||
|
PINNED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,21 +4,22 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
public class RegisterAccount extends MastodonAPIRequest<Token>{
|
public class RegisterAccount extends MastodonAPIRequest<Token>{
|
||||||
public RegisterAccount(String username, String email, String password, String locale, String reason){
|
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone){
|
||||||
super(HttpMethod.POST, "/accounts", Token.class);
|
super(HttpMethod.POST, "/accounts", Token.class);
|
||||||
setRequestBody(new Body(username, email, password, locale, reason));
|
setRequestBody(new Body(username, email, password, locale, reason, timezone));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Body{
|
private static class Body{
|
||||||
public String username, email, password, locale, reason;
|
public String username, email, password, locale, reason, timeZone;
|
||||||
public boolean agreement=true;
|
public boolean agreement=true;
|
||||||
|
|
||||||
public Body(String username, String email, String password, String locale, String reason){
|
public Body(String username, String email, String password, String locale, String reason, String timeZone){
|
||||||
this.username=username;
|
this.username=username;
|
||||||
this.email=email;
|
this.email=email;
|
||||||
this.password=password;
|
this.password=password;
|
||||||
this.locale=locale;
|
this.locale=locale;
|
||||||
this.reason=reason;
|
this.reason=reason;
|
||||||
|
this.timeZone=timeZone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Preferences;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
|
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
||||||
|
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
|
||||||
|
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
||||||
|
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Request{
|
||||||
|
public Boolean locked, discoverable;
|
||||||
|
public RequestSource source;
|
||||||
|
|
||||||
|
public Request(Boolean locked, Boolean discoverable, RequestSource source){
|
||||||
|
this.locked=locked;
|
||||||
|
this.discoverable=discoverable;
|
||||||
|
this.source=source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RequestSource{
|
||||||
|
public StatusPrivacy privacy;
|
||||||
|
public String language;
|
||||||
|
|
||||||
|
public RequestSource(StatusPrivacy privacy, String language){
|
||||||
|
this.privacy=privacy;
|
||||||
|
this.language=language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.joinmastodon.android.api.requests.catalog;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.catalog.CatalogDefaultInstance;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetCatalogDefaultInstances extends MastodonAPIRequest<List<CatalogDefaultInstance>>{
|
||||||
|
public GetCatalogDefaultInstances(){
|
||||||
|
super(HttpMethod.GET, null, new TypeToken<>(){});
|
||||||
|
setTimeout(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getURL(){
|
||||||
|
return Uri.parse("https://api.joinmastodon.org/default-servers");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.FilterAction;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
|
import org.joinmastodon.android.model.FilterKeyword;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CreateFilter extends MastodonAPIRequest<Filter>{
|
||||||
|
public CreateFilter(String title, EnumSet<FilterContext> context, FilterAction action, int expiresIn, List<FilterKeyword> words){
|
||||||
|
super(HttpMethod.POST, "/filters", Filter.class);
|
||||||
|
setRequestBody(new FilterRequest(title, context, action, expiresIn==0 ? null : expiresIn, words.stream().map(w->new KeywordAttribute(null, null, w.keyword, w.wholeWord)).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPathPrefix(){
|
||||||
|
return "/api/v2";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
|
||||||
|
|
||||||
|
public class DeleteFilter extends ResultlessMastodonAPIRequest{
|
||||||
|
public DeleteFilter(String id){
|
||||||
|
super(HttpMethod.DELETE, "/filters/"+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPathPrefix(){
|
||||||
|
return "/api/v2";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.FilterAction;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class FilterRequest{
|
||||||
|
public String title;
|
||||||
|
public EnumSet<FilterContext> context;
|
||||||
|
public FilterAction filterAction;
|
||||||
|
public Integer expiresIn;
|
||||||
|
public List<KeywordAttribute> keywordsAttributes;
|
||||||
|
|
||||||
|
public FilterRequest(String title, EnumSet<FilterContext> context, FilterAction filterAction, Integer expiresIn, List<KeywordAttribute> keywordsAttributes){
|
||||||
|
this.title=title;
|
||||||
|
this.context=context;
|
||||||
|
this.filterAction=filterAction;
|
||||||
|
this.expiresIn=expiresIn;
|
||||||
|
this.keywordsAttributes=keywordsAttributes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.joinmastodon.android.api.requests.accounts;
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
@ -7,8 +7,13 @@ import org.joinmastodon.android.model.Filter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetWordFilters extends MastodonAPIRequest<List<Filter>>{
|
public class GetFilters extends MastodonAPIRequest<List<Filter>>{
|
||||||
public GetWordFilters(){
|
public GetFilters(){
|
||||||
super(HttpMethod.GET, "/filters", new TypeToken<>(){});
|
super(HttpMethod.GET, "/filters", new TypeToken<>(){});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPathPrefix(){
|
||||||
|
return "/api/v2";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetLegacyFilters extends MastodonAPIRequest<List<LegacyFilter>>{
|
||||||
|
public GetLegacyFilters(){
|
||||||
|
super(HttpMethod.GET, "/filters", new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
class KeywordAttribute{
|
||||||
|
public String id;
|
||||||
|
@SerializedName("_destroy")
|
||||||
|
public Boolean delete;
|
||||||
|
public String keyword;
|
||||||
|
public Boolean wholeWord;
|
||||||
|
|
||||||
|
public KeywordAttribute(String id, Boolean delete, String keyword, Boolean wholeWord){
|
||||||
|
this.id=id;
|
||||||
|
this.delete=delete;
|
||||||
|
this.keyword=keyword;
|
||||||
|
this.wholeWord=wholeWord;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.joinmastodon.android.api.requests.filters;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.FilterAction;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
|
import org.joinmastodon.android.model.FilterKeyword;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class UpdateFilter extends MastodonAPIRequest<Filter>{
|
||||||
|
public UpdateFilter(String id, String title, EnumSet<FilterContext> context, FilterAction action, int expiresIn, List<FilterKeyword> words, List<String> deletedWords){
|
||||||
|
super(HttpMethod.PUT, "/filters/"+id, Filter.class);
|
||||||
|
|
||||||
|
List<KeywordAttribute> attrs=Stream.of(
|
||||||
|
words.stream().map(w->new KeywordAttribute(w.id, null, w.keyword, w.wholeWord)),
|
||||||
|
deletedWords.stream().map(wid->new KeywordAttribute(wid, true, null, null))
|
||||||
|
).flatMap(Function.identity()).collect(Collectors.toList());
|
||||||
|
setRequestBody(new FilterRequest(title, context, action, expiresIn==0 ? null : expiresIn, attrs));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getPathPrefix(){
|
||||||
|
return "/api/v2";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.joinmastodon.android.api.requests.instance;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class GetInstanceExtendedDescription extends MastodonAPIRequest<GetInstanceExtendedDescription.Response>{
|
||||||
|
public GetInstanceExtendedDescription(){
|
||||||
|
super(HttpMethod.GET, "/instance/extended_description", Response.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response{
|
||||||
|
public Instant updatedAt;
|
||||||
|
public String content;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
package org.joinmastodon.android.api.requests.markers;
|
package org.joinmastodon.android.api.requests.markers;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ApiUtils;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Marker;
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.model.Markers;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
public class GetMarkers extends MastodonAPIRequest<TimelineMarkers>{
|
||||||
|
public GetMarkers(){
|
||||||
public class GetMarkers extends MastodonAPIRequest<Markers> {
|
super(HttpMethod.GET, "/markers", TimelineMarkers.class);
|
||||||
public GetMarkers(EnumSet<Marker.Type> timelines) {
|
addQueryParameter("timeline[]", "home");
|
||||||
super(HttpMethod.GET, "/markers", Markers.class);
|
addQueryParameter("timeline[]", "notifications");
|
||||||
for (String type : ApiUtils.enumSetToStrings(timelines, Marker.Type.class)){
|
|
||||||
addQueryParameter("timeline[]", type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ package org.joinmastodon.android.api.requests.markers;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
|
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
|
||||||
import org.joinmastodon.android.model.Marker;
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
|
|
||||||
public class SaveMarkers extends MastodonAPIRequest<SaveMarkers.Response>{
|
public class SaveMarkers extends MastodonAPIRequest<TimelineMarkers>{
|
||||||
public SaveMarkers(String lastSeenHomePostID, String lastSeenNotificationID){
|
public SaveMarkers(String lastSeenHomePostID, String lastSeenNotificationID){
|
||||||
super(HttpMethod.POST, "/markers", Response.class);
|
super(HttpMethod.POST, "/markers", TimelineMarkers.class);
|
||||||
JsonObjectBuilder builder=new JsonObjectBuilder();
|
JsonObjectBuilder builder=new JsonObjectBuilder();
|
||||||
if(lastSeenHomePostID!=null)
|
if(lastSeenHomePostID!=null)
|
||||||
builder.add("home", new JsonObjectBuilder().add("last_read_id", lastSeenHomePostID));
|
builder.add("home", new JsonObjectBuilder().add("last_read_id", lastSeenHomePostID));
|
||||||
|
@ -14,8 +14,4 @@ public class SaveMarkers extends MastodonAPIRequest<SaveMarkers.Response>{
|
||||||
builder.add("notifications", new JsonObjectBuilder().add("last_read_id", lastSeenNotificationID));
|
builder.add("notifications", new JsonObjectBuilder().add("last_read_id", lastSeenNotificationID));
|
||||||
setRequestBody(builder.build());
|
setRequestBody(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Response{
|
|
||||||
public Marker home, notifications;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@ public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
||||||
addQueryParameter("resolve", "true");
|
addQueryParameter("resolve", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetSearchResults limit(int limit){
|
||||||
|
addQueryParameter("limit", String.valueOf(limit));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getPathPrefix(){
|
protected String getPathPrefix(){
|
||||||
return "/api/v2";
|
return "/api/v2";
|
||||||
|
|
|
@ -4,20 +4,19 @@ import android.text.TextUtils;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
|
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
|
||||||
public GetBubbleTimeline(String maxID, int limit) {
|
public GetBubbleTimeline(String maxID, int limit, String replyVisibility) {
|
||||||
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
|
||||||
if(!TextUtils.isEmpty(maxID))
|
if(!TextUtils.isEmpty(maxID))
|
||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", limit+"");
|
addQueryParameter("limit", limit+"");
|
||||||
if(GlobalUserPreferences.replyVisibility != null)
|
if(replyVisibility != null)
|
||||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
addQueryParameter("reply_visibility", replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,13 @@ package org.joinmastodon.android.api.requests.timelines;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
|
public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit, List<String> containsAny, List<String> containsAll, List<String> containsNone, boolean localOnly){
|
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit, List<String> containsAny, List<String> containsAll, List<String> containsNone, boolean localOnly, String replyVisibility){
|
||||||
super(HttpMethod.GET, "/timelines/tag/"+hashtag, new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/tag/"+hashtag, new TypeToken<>(){});
|
||||||
if (localOnly)
|
if (localOnly)
|
||||||
addQueryParameter("local", "true");
|
addQueryParameter("local", "true");
|
||||||
|
@ -30,17 +27,7 @@ public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
if(containsNone!=null)
|
if(containsNone!=null)
|
||||||
for (String tag : containsNone)
|
for (String tag : containsNone)
|
||||||
addQueryParameter("none[]", tag);
|
addQueryParameter("none[]", tag);
|
||||||
}
|
if(replyVisibility != null)
|
||||||
|
addQueryParameter("reply_visibility", replyVisibility);
|
||||||
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit){
|
|
||||||
super(HttpMethod.GET, "/timelines/tag/"+hashtag, new TypeToken<>(){});
|
|
||||||
if(maxID!=null)
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
if(minID!=null)
|
|
||||||
addQueryParameter("min_id", minID);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", ""+limit);
|
|
||||||
if(GlobalUserPreferences.replyVisibility != null)
|
|
||||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ package org.joinmastodon.android.api.requests.timelines;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID){
|
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID, String replyVisibility){
|
||||||
super(HttpMethod.GET, "/timelines/home", new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/home", new TypeToken<>(){});
|
||||||
if(maxID!=null)
|
if(maxID!=null)
|
||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
|
@ -19,7 +18,7 @@ public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
addQueryParameter("since_id", sinceID);
|
addQueryParameter("since_id", sinceID);
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
if(GlobalUserPreferences.replyVisibility != null)
|
if(replyVisibility != null)
|
||||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
addQueryParameter("reply_visibility", replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ package org.joinmastodon.android.api.requests.timelines;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
|
public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
|
||||||
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID) {
|
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID, String replyVisibility) {
|
||||||
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
|
||||||
if(maxID!=null)
|
if(maxID!=null)
|
||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
|
@ -19,7 +18,7 @@ public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
if(sinceID!=null)
|
if(sinceID!=null)
|
||||||
addQueryParameter("since_id", sinceID);
|
addQueryParameter("since_id", sinceID);
|
||||||
if(GlobalUserPreferences.replyVisibility != null)
|
if(replyVisibility != null)
|
||||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
addQueryParameter("reply_visibility", replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,13 @@ import android.text.TextUtils;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit){
|
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit, String replyVisibility){
|
||||||
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
|
||||||
if(local)
|
if(local)
|
||||||
addQueryParameter("local", "true");
|
addQueryParameter("local", "true");
|
||||||
|
@ -21,7 +20,7 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", limit+"");
|
addQueryParameter("limit", limit+"");
|
||||||
if(GlobalUserPreferences.replyVisibility != null)
|
if(replyVisibility != null)
|
||||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
addQueryParameter("reply_visibility", replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.fromJson;
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.enumValue;
|
||||||
|
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AccountLocalPreferences{
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
|
||||||
|
public boolean showInteractionCounts;
|
||||||
|
public boolean customEmojiInNames;
|
||||||
|
public boolean revealCWs;
|
||||||
|
public boolean hideSensitiveMedia;
|
||||||
|
public boolean serverSideFiltersSupported;
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
|
public boolean showReplies;
|
||||||
|
public boolean showBoosts;
|
||||||
|
public ArrayList<String> recentLanguages;
|
||||||
|
public boolean bottomEncoding;
|
||||||
|
public ContentType defaultContentType;
|
||||||
|
public boolean contentTypesEnabled;
|
||||||
|
public ArrayList<TimelineDefinition> timelines;
|
||||||
|
public boolean localOnlySupported;
|
||||||
|
public boolean glitchInstance;
|
||||||
|
public String publishButtonText;
|
||||||
|
public String timelineReplyVisibility; // akkoma-only
|
||||||
|
public boolean keepOnlyLatestNotification;
|
||||||
|
|
||||||
|
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
|
||||||
|
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||||
|
|
||||||
|
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
||||||
|
this.prefs=prefs;
|
||||||
|
showInteractionCounts=prefs.getBoolean("interactionCounts", false);
|
||||||
|
customEmojiInNames=prefs.getBoolean("emojiInNames", true);
|
||||||
|
revealCWs=prefs.getBoolean("revealCWs", false);
|
||||||
|
hideSensitiveMedia=prefs.getBoolean("hideSensitive", true);
|
||||||
|
serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false);
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
|
showReplies=prefs.getBoolean("showReplies", true);
|
||||||
|
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
|
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
||||||
|
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||||
|
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
|
||||||
|
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
|
||||||
|
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
||||||
|
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
||||||
|
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
||||||
|
publishButtonText=prefs.getString("publishButtonText", null);
|
||||||
|
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||||
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getNotificationsPauseEndTime(){
|
||||||
|
return prefs.getLong("notificationsPauseTime", 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotificationsPauseEndTime(long time){
|
||||||
|
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(){
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean("interactionCounts", showInteractionCounts)
|
||||||
|
.putBoolean("emojiInNames", customEmojiInNames)
|
||||||
|
.putBoolean("revealCWs", revealCWs)
|
||||||
|
.putBoolean("hideSensitive", hideSensitiveMedia)
|
||||||
|
.putBoolean("serverSideFilters", serverSideFiltersSupported)
|
||||||
|
|
||||||
|
// MEGALODON
|
||||||
|
.putBoolean("showReplies", showReplies)
|
||||||
|
.putBoolean("showBoosts", showBoosts)
|
||||||
|
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||||
|
.putBoolean("bottomEncoding", bottomEncoding)
|
||||||
|
.putString("defaultContentType", defaultContentType==null ? null : defaultContentType.name())
|
||||||
|
.putBoolean("contentTypesEnabled", contentTypesEnabled)
|
||||||
|
.putString("timelines", gson.toJson(timelines))
|
||||||
|
.putBoolean("localOnlySupported", localOnlySupported)
|
||||||
|
.putBoolean("glitchInstance", glitchInstance)
|
||||||
|
.putString("publishButtonText", publishButtonText)
|
||||||
|
.putString("timelineReplyVisibility", timelineReplyVisibility)
|
||||||
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,53 @@
|
||||||
package org.joinmastodon.android.api.session;
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.CacheController;
|
import org.joinmastodon.android.api.CacheController;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
import org.joinmastodon.android.api.StatusInteractionController;
|
import org.joinmastodon.android.api.StatusInteractionController;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentialsPreferences;
|
||||||
|
import org.joinmastodon.android.api.requests.markers.GetMarkers;
|
||||||
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
|
import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterAction;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
|
import org.joinmastodon.android.model.FilterResult;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Markers;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class AccountSession{
|
public class AccountSession{
|
||||||
|
private static final String TAG="AccountSession";
|
||||||
|
|
||||||
public Token token;
|
public Token token;
|
||||||
public Account self;
|
public Account self;
|
||||||
public String domain;
|
public String domain;
|
||||||
|
@ -32,15 +60,17 @@ public class AccountSession{
|
||||||
public PushSubscription pushSubscription;
|
public PushSubscription pushSubscription;
|
||||||
public boolean needUpdatePushSettings;
|
public boolean needUpdatePushSettings;
|
||||||
public long filtersLastUpdated;
|
public long filtersLastUpdated;
|
||||||
public List<Filter> wordFilters=new ArrayList<>();
|
public List<LegacyFilter> wordFilters=new ArrayList<>();
|
||||||
public String pushAccountID;
|
public String pushAccountID;
|
||||||
public Preferences preferences;
|
|
||||||
public AccountActivationInfo activationInfo;
|
public AccountActivationInfo activationInfo;
|
||||||
public Markers markers;
|
public Preferences preferences;
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||||
|
private transient SharedPreferences prefs;
|
||||||
|
private transient boolean preferencesNeedSaving;
|
||||||
|
private transient AccountLocalPreferences localPreferences;
|
||||||
|
|
||||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
||||||
this.token=token;
|
this.token=token;
|
||||||
|
@ -58,10 +88,6 @@ public class AccountSession{
|
||||||
return domain+"_"+self.id;
|
return domain+"_"+self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullUsername() {
|
|
||||||
return "@"+self.username+"@"+domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MastodonAPIController getApiController(){
|
public MastodonAPIController getApiController(){
|
||||||
if(apiController==null)
|
if(apiController==null)
|
||||||
apiController=new MastodonAPIController(this);
|
apiController=new MastodonAPIController(this);
|
||||||
|
@ -92,6 +118,188 @@ public class AccountSession{
|
||||||
return pushSubscriptionManager;
|
return pushSubscriptionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFullUsername(){
|
||||||
|
return '@'+self.username+'@'+domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void preferencesFromAccountSource(Account account) {
|
||||||
|
if (account != null && account.source != null && preferences != null) {
|
||||||
|
if (account.source.privacy != null)
|
||||||
|
preferences.postingDefaultVisibility = account.source.privacy;
|
||||||
|
if (account.source.language != null)
|
||||||
|
preferences.postingDefaultLanguage = account.source.language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadPreferences(Consumer<Preferences> callback){
|
||||||
|
new GetPreferences()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Preferences result){
|
||||||
|
preferences=result;
|
||||||
|
preferencesFromAccountSource(self);
|
||||||
|
if(callback!=null)
|
||||||
|
callback.accept(result);
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedPreferences getRawLocalPreferences(){
|
||||||
|
if(prefs==null)
|
||||||
|
prefs=MastodonApp.context.getSharedPreferences(getID(), Context.MODE_PRIVATE);
|
||||||
|
return prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reloadNotificationsMarker(Consumer<String> callback){
|
||||||
|
new GetMarkers()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(TimelineMarkers result){
|
||||||
|
if(result.notifications!=null && !TextUtils.isEmpty(result.notifications.lastReadId)){
|
||||||
|
String id=result.notifications.lastReadId;
|
||||||
|
String lastKnown=getLastKnownNotificationsMarker();
|
||||||
|
if(ObjectIdComparator.INSTANCE.compare(id, lastKnown)<0){
|
||||||
|
// Marker moved back -- previous marker update must have failed.
|
||||||
|
// Pretend it didn't happen and repeat the request.
|
||||||
|
id=lastKnown;
|
||||||
|
new SaveMarkers(null, id).exec(getID());
|
||||||
|
}
|
||||||
|
callback.accept(id);
|
||||||
|
setNotificationsMarker(id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){}
|
||||||
|
})
|
||||||
|
.exec(getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastKnownNotificationsMarker(){
|
||||||
|
return getRawLocalPreferences().getString("notificationsMarker", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotificationsMarker(String id, boolean clearUnread){
|
||||||
|
getRawLocalPreferences().edit().putString("notificationsMarker", id).apply();
|
||||||
|
E.post(new NotificationsMarkerUpdatedEvent(getID(), id, clearUnread));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logOut(Activity activity, Runnable onDone){
|
||||||
|
new RevokeOauthToken(app.clientId, app.clientSecret, token.accessToken)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object result){
|
||||||
|
AccountSessionManager.getInstance().removeAccount(getID());
|
||||||
|
onDone.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
AccountSessionManager.getInstance().removeAccount(getID());
|
||||||
|
onDone.run();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(activity, R.string.loading, false)
|
||||||
|
.exec(getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePreferencesLater(){
|
||||||
|
preferencesNeedSaving=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void savePreferencesIfPending(){
|
||||||
|
if(preferencesNeedSaving){
|
||||||
|
new UpdateAccountCredentialsPreferences(preferences, null, null)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Account result){
|
||||||
|
preferencesNeedSaving=false;
|
||||||
|
self=result;
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
Log.e(TAG, "failed to save preferences: "+error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountLocalPreferences getLocalPreferences(){
|
||||||
|
if(localPreferences==null)
|
||||||
|
localPreferences=new AccountLocalPreferences(getRawLocalPreferences(), this);
|
||||||
|
return localPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filterStatuses(List<Status> statuses, FilterContext context){
|
||||||
|
filterStatuses(statuses, context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filterStatuses(List<Status> statuses, FilterContext context, Account profile){
|
||||||
|
filterStatusContainingObjects(statuses, Function.identity(), context, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context){
|
||||||
|
filterStatusContainingObjects(objects, extractor, context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||||
|
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null
|
||||||
|
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
||||||
|
|
||||||
|
if(getLocalPreferences().serverSideFiltersSupported){
|
||||||
|
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
||||||
|
objects.removeIf(o->{
|
||||||
|
Status s=extractor.apply(o);
|
||||||
|
if(s==null)
|
||||||
|
return false;
|
||||||
|
if(s.filtered==null)
|
||||||
|
return false;
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if (statusIsOnOwnProfile.test(s))
|
||||||
|
return false;
|
||||||
|
for(FilterResult filter:s.filtered){
|
||||||
|
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(wordFilters==null)
|
||||||
|
return;
|
||||||
|
for(T obj:objects){
|
||||||
|
Status s=extractor.apply(obj);
|
||||||
|
if(s!=null && s.filtered!=null){
|
||||||
|
getLocalPreferences().serverSideFiltersSupported=true;
|
||||||
|
getLocalPreferences().save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.removeIf(o->{
|
||||||
|
Status s=extractor.apply(o);
|
||||||
|
if(s==null)
|
||||||
|
return false;
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if (statusIsOnOwnProfile.test(s))
|
||||||
|
return false;
|
||||||
|
for(LegacyFilter filter:wordFilters){
|
||||||
|
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<Instance> getInstance() {
|
public Optional<Instance> getInstance() {
|
||||||
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,23 +21,18 @@ import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
import org.joinmastodon.android.api.requests.filters.GetLegacyFilters;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
|
||||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.api.requests.markers.GetMarkers;
|
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Marker;
|
|
||||||
import org.joinmastodon.android.model.Markers;
|
|
||||||
import org.joinmastodon.android.model.Preferences;
|
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -50,10 +45,10 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -155,11 +150,19 @@ public class AccountSessionManager{
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AccountSession get(String id){
|
||||||
|
return getInstance().getAccount(id);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public AccountSession tryGetAccount(String id){
|
public AccountSession tryGetAccount(String id){
|
||||||
return sessions.get(id);
|
return sessions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<AccountSession> getOptional(String id) {
|
||||||
|
return Optional.ofNullable(getInstance().tryGetAccount(id));
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public AccountSession tryGetAccount(Account account) {
|
public AccountSession tryGetAccount(Account account) {
|
||||||
return sessions.get(account.getDomainFromURL() + "_" + account.id);
|
return sessions.get(account.getDomainFromURL() + "_" + account.id);
|
||||||
|
@ -192,13 +195,19 @@ public class AccountSessionManager{
|
||||||
AccountSession session=getAccount(id);
|
AccountSession session=getAccount(id);
|
||||||
session.getCacheController().closeDatabase();
|
session.getCacheController().closeDatabase();
|
||||||
MastodonApp.context.deleteDatabase(id+".db");
|
MastodonApp.context.deleteDatabase(id+".db");
|
||||||
GlobalUserPreferences.removeAccount(id);
|
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||||
|
MastodonApp.context.deleteSharedPreferences(id);
|
||||||
|
}else{
|
||||||
|
new File(MastodonApp.context.getDir("shared_prefs", Context.MODE_PRIVATE), id+".xml").delete();
|
||||||
|
}
|
||||||
sessions.remove(id);
|
sessions.remove(id);
|
||||||
if(lastActiveAccountID.equals(id)){
|
if(lastActiveAccountID.equals(id)){
|
||||||
if(sessions.isEmpty())
|
if(sessions.isEmpty())
|
||||||
lastActiveAccountID=null;
|
lastActiveAccountID=null;
|
||||||
else
|
else
|
||||||
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
|
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
|
||||||
|
prefs.edit().putString("lastActiveAccount", lastActiveAccountID).apply();
|
||||||
}
|
}
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
String domain=session.domain.toLowerCase();
|
String domain=session.domain.toLowerCase();
|
||||||
|
@ -271,14 +280,13 @@ public class AccountSessionManager{
|
||||||
HashSet<String> domains=new HashSet<>();
|
HashSet<String> domains=new HashSet<>();
|
||||||
for(AccountSession session:sessions.values()){
|
for(AccountSession session:sessions.values()){
|
||||||
domains.add(session.domain.toLowerCase());
|
domains.add(session.domain.toLowerCase());
|
||||||
if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
|
if(session == activeSession || now-session.infoLastUpdated>24L*3600_000L){
|
||||||
updateSessionPreferences(session);
|
session.reloadPreferences(null);
|
||||||
updateSessionLocalInfo(session);
|
updateSessionLocalInfo(session);
|
||||||
}
|
}
|
||||||
if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
|
if(session == activeSession || (session.getLocalPreferences().serverSideFiltersSupported && now-session.filtersLastUpdated>3600_000L)){
|
||||||
updateSessionWordFilters(session);
|
updateSessionWordFilters(session);
|
||||||
}
|
}
|
||||||
updateSessionMarkers(session);
|
|
||||||
}
|
}
|
||||||
if(loadedInstances){
|
if(loadedInstances){
|
||||||
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
|
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
|
||||||
|
@ -289,20 +297,12 @@ public class AccountSessionManager{
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
for(String domain:domains){
|
for(String domain:domains){
|
||||||
Long lastUpdated=instancesLastUpdated.get(domain);
|
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||||
if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
|
if(domain.equals(activeDomain) || lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||||
updateInstanceInfo(domain);
|
updateInstanceInfo(domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void preferencesFromSource(AccountSession session, Account account) {
|
|
||||||
if (account != null && account.source != null && session.preferences != null) {
|
|
||||||
if (account.source.privacy != null)
|
|
||||||
session.preferences.postingDefaultVisibility = account.source.privacy;
|
|
||||||
if (account.source.language != null)
|
|
||||||
session.preferences.postingDefaultLanguage = account.source.language;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSessionLocalInfo(AccountSession session){
|
private void updateSessionLocalInfo(AccountSession session){
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
|
@ -311,39 +311,7 @@ public class AccountSessionManager{
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
session.self=result;
|
session.self=result;
|
||||||
session.infoLastUpdated=System.currentTimeMillis();
|
session.infoLastUpdated=System.currentTimeMillis();
|
||||||
preferencesFromSource(session, result);
|
session.preferencesFromAccountSource(result);
|
||||||
writeAccountsFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){}
|
|
||||||
})
|
|
||||||
.exec(session.getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSessionPreferences(AccountSession session){
|
|
||||||
new GetPreferences().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Preferences preferences) {
|
|
||||||
session.preferences=preferences;
|
|
||||||
preferencesFromSource(session, session.self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
session.preferences = new Preferences();
|
|
||||||
preferencesFromSource(session, session.self);
|
|
||||||
}
|
|
||||||
}).exec(session.getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSessionWordFilters(AccountSession session){
|
|
||||||
new GetWordFilters()
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Filter> result){
|
|
||||||
session.wordFilters=result;
|
|
||||||
session.filtersLastUpdated=System.currentTimeMillis();
|
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,19 +323,22 @@ public class AccountSessionManager{
|
||||||
.exec(session.getID());
|
.exec(session.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSessionMarkers(AccountSession session) {
|
private void updateSessionWordFilters(AccountSession session){
|
||||||
new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() {
|
new GetLegacyFilters()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Markers markers) {
|
public void onSuccess(List<LegacyFilter> result){
|
||||||
session.markers = markers;
|
session.wordFilters=result;
|
||||||
|
session.filtersLastUpdated=System.currentTimeMillis();
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error){
|
||||||
|
|
||||||
}
|
}
|
||||||
}).exec(session.getID());
|
})
|
||||||
|
.exec(session.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateInstanceInfo(String domain){
|
public void updateInstanceInfo(String domain){
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class AllNotificationsSeenEvent {
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class NotificationReceivedEvent {
|
|
||||||
public String account, id;
|
|
||||||
public NotificationReceivedEvent(String account, String id) {
|
|
||||||
this.account = account;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class NotificationsMarkerUpdatedEvent{
|
||||||
|
public final String accountID;
|
||||||
|
public final String marker;
|
||||||
|
public final boolean clearUnread;
|
||||||
|
|
||||||
|
public NotificationsMarkerUpdatedEvent(String accountID, String marker, boolean clearUnread){
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.marker=marker;
|
||||||
|
this.clearUnread=clearUnread;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
|
||||||
|
public class SettingsFilterCreatedOrUpdatedEvent{
|
||||||
|
public final String accountID;
|
||||||
|
public final Filter filter;
|
||||||
|
|
||||||
|
public SettingsFilterCreatedOrUpdatedEvent(String accountID, Filter filter){
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.filter=filter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class SettingsFilterDeletedEvent{
|
||||||
|
public final String accountID;
|
||||||
|
public final String filterID;
|
||||||
|
|
||||||
|
public SettingsFilterDeletedEvent(String accountID, String filterID){
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.filterID=filterID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
package org.joinmastodon.android.events;
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.CacheController;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
public class StatusCountersUpdatedEvent{
|
public class StatusCountersUpdatedEvent{
|
||||||
public String id;
|
public String id;
|
||||||
public long favorites, reblogs, replies;
|
public long favorites, reblogs, replies;
|
||||||
public boolean favorited, reblogged, bookmarked, pinned;
|
public boolean favorited, reblogged, bookmarked, pinned;
|
||||||
|
public Status status;
|
||||||
|
|
||||||
public StatusCountersUpdatedEvent(Status s){
|
public StatusCountersUpdatedEvent(Status s){
|
||||||
id=s.id;
|
id=s.id;
|
||||||
|
status=s;
|
||||||
favorites=s.favouritesCount;
|
favorites=s.favouritesCount;
|
||||||
reblogs=s.reblogsCount;
|
reblogs=s.reblogsCount;
|
||||||
replies=s.repliesCount;
|
replies=s.repliesCount;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class StatusDisplaySettingsChangedEvent{
|
||||||
|
public final String accountID;
|
||||||
|
|
||||||
|
public StatusDisplaySettingsChangedEvent(String accountID){
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
@ -48,27 +48,22 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
|
||||||
user=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
user=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||||
filter=GetAccountStatuses.Filter.valueOf(getArguments().getString("filter"));
|
filter=GetAccountStatuses.Filter.valueOf(getArguments().getString("filter"));
|
||||||
|
super.onAttach(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
if(user==null) // TODO figure out why this happens
|
|
||||||
return;
|
|
||||||
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
if(getActivity()==null) return;
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
result=result.stream().filter(status -> {
|
boolean empty=result.isEmpty();
|
||||||
// don't hide own posts in own profile
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
|
||||||
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
|
onDataLoaded(result, !empty);
|
||||||
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
@ -77,6 +72,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
view.setBackground(null); // prevents unnecessary overdraw
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,20 +82,20 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
protected void onStatusCreated(Status status){
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
if(!asm.isSelf(accountID, ev.status.account) || !asm.isSelf(accountID, user))
|
if(!asm.isSelf(accountID, status.account) || !asm.isSelf(accountID, user))
|
||||||
return;
|
return;
|
||||||
if(filter==GetAccountStatuses.Filter.PINNED) return;
|
if(filter==GetAccountStatuses.Filter.PINNED) return;
|
||||||
if(filter==GetAccountStatuses.Filter.DEFAULT){
|
if(filter==GetAccountStatuses.Filter.DEFAULT){
|
||||||
// Keep replies to self, discard all other replies
|
// Keep replies to self, discard all other replies
|
||||||
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
if(status.inReplyToAccountId!=null && !status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
||||||
return;
|
return;
|
||||||
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
||||||
if(Optional.ofNullable(ev.status.mediaAttachments).map(List::isEmpty).orElse(true))
|
if(Optional.ofNullable(status.mediaAttachments).map(List::isEmpty).orElse(true))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(status), true);
|
||||||
if (isOnTop()) scrollToTop();
|
if (isOnTop()) scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,8 +126,8 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.ACCOUNT;
|
return FilterContext.ACCOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,14 +6,10 @@ import android.content.res.Configuration;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Layout;
|
import android.util.Log;
|
||||||
import android.text.StaticLayout;
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
@ -26,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
|
@ -33,13 +30,16 @@ import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
|
@ -54,6 +54,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -68,10 +69,11 @@ import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends MastodonRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
|
@ -96,7 +98,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(GlobalUserPreferences.disableMarquee){
|
if(GlobalUserPreferences.toolbarMarquee){
|
||||||
setTitleMarqueeEnabled(false);
|
setTitleMarqueeEnabled(false);
|
||||||
setSubtitleMarqueeEnabled(false);
|
setSubtitleMarqueeEnabled(false);
|
||||||
}
|
}
|
||||||
|
@ -350,7 +352,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
public void getSelectorBounds(View view, Rect outRect){
|
public void getSelectorBounds(View view, Rect outRect){
|
||||||
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
||||||
int lastIndex = -1, firstIndex = -1;
|
int lastIndex = -1, firstIndex = -1;
|
||||||
|
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
|
||||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||||
|
}else{
|
||||||
|
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||||
|
}
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder){
|
||||||
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
|
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
|
||||||
|
@ -427,6 +433,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getMainAdapterOffset(){
|
protected int getMainAdapterOffset(){
|
||||||
|
if(list.getAdapter() instanceof MergeRecyclerAdapter mergeAdapter){
|
||||||
|
return mergeAdapter.getPositionForAdapter(adapter);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +447,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
c.drawLine(0, y, parent.getWidth(), y, paint);
|
c.drawLine(0, y, parent.getWidth(), y, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void onItemClick(String id);
|
public abstract void onItemClick(String id);
|
||||||
|
|
||||||
protected void updatePoll(String itemID, Status status, Poll poll){
|
protected void updatePoll(String itemID, Status status, Poll poll){
|
||||||
|
@ -525,38 +538,57 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRevealSpoilerClick(TextStatusDisplayItem.Holder holder){
|
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
|
||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
revealSpoiler(status, holder.getItemID());
|
toggleSpoiler(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
|
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
||||||
Status status=holder.getItem().status;
|
Status status = holder.getItem().status;
|
||||||
revealSpoiler(status, holder.getItemID());
|
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
||||||
|
if (mediaGrid != null) {
|
||||||
|
if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
|
||||||
|
else mediaGrid.hideSensitive();
|
||||||
|
} else {
|
||||||
|
// media grid's methods normally change the status' state - we still want to be able
|
||||||
|
// to do this if the media grid is not bound, tho - so, doing it ourselves here
|
||||||
|
status.sensitiveRevealed = !status.sensitiveRevealed;
|
||||||
|
}
|
||||||
|
holder.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
|
||||||
|
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
|
if(header != null) header.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void toggleSpoiler(Status status, String itemID){
|
||||||
|
status.spoilerRevealed=!status.spoilerRevealed;
|
||||||
|
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
|
||||||
|
status.sensitiveRevealed = false;
|
||||||
|
|
||||||
|
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
||||||
|
if(spoiler!=null)
|
||||||
|
spoiler.rebind();
|
||||||
|
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
|
||||||
|
|
||||||
|
int index=displayItems.indexOf(spoilerItem);
|
||||||
|
if(status.spoilerRevealed){
|
||||||
|
displayItems.addAll(index+1, spoilerItem.contentItems);
|
||||||
|
adapter.notifyItemRangeInserted(index+1, spoilerItem.contentItems.size());
|
||||||
|
}else{
|
||||||
|
displayItems.subList(index+1, index+1+spoilerItem.contentItems.size()).clear();
|
||||||
|
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void revealSpoiler(Status status, String itemID){
|
|
||||||
status.spoilerRevealed=true;
|
|
||||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
if(text!=null)
|
if(text!=null)
|
||||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header!=null)
|
if(header!=null)
|
||||||
header.rebind();
|
header.rebind();
|
||||||
updateImagesSpoilerState(status, itemID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){
|
list.invalidateItemDecorations();
|
||||||
Status status=holder.getItem().status;
|
|
||||||
status.spoilerRevealed=!status.spoilerRevealed;
|
|
||||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
|
||||||
TextStatusDisplayItem.Holder text = findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
|
|
||||||
if(text!=null){
|
|
||||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
holder.rebind();
|
|
||||||
updateImagesSpoilerState(status, holder.getItemID());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
||||||
|
@ -575,30 +607,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
if (header != null) header.rebind();
|
if (header != null) header.rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
|
||||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
|
||||||
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
|
||||||
if(mediaGrid!=null){
|
|
||||||
mediaGrid.setRevealed(status.spoilerRevealed);
|
|
||||||
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
|
||||||
}
|
|
||||||
int i=0;
|
|
||||||
for(StatusDisplayItem item:displayItems){
|
|
||||||
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImageUpdated(MediaGridStatusDisplayItem.Holder holder, int index) {
|
|
||||||
holder.rebind();
|
|
||||||
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
|
||||||
if(mediaGrid!=null){
|
|
||||||
adapter.notifyItemChanged(mediaGrid.getAbsoluteAdapterPosition());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||||
|
|
||||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||||
|
@ -754,11 +762,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void rebuildAllDisplayItems(){
|
||||||
|
displayItems.clear();
|
||||||
|
for(T item:data){
|
||||||
|
displayItems.addAll(buildDisplayItems(item));
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDataLoaded(List<T> d, boolean more) {
|
protected void onDataLoaded(List<T> d, boolean more) {
|
||||||
|
if (getContext()==null) return;
|
||||||
super.onDataLoaded(d, more);
|
super.onDataLoaded(d, more);
|
||||||
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
||||||
if (more && d.size() < itemsPerPage) preloader.onScrolledToLastItem();
|
if (more && d.size() < itemsPerPage) {
|
||||||
|
Log.d("BaseStatusListFragment", "doing the 'loading more things' thing!!! ipp: "+itemsPerPage+", items size: "+ d.size());
|
||||||
|
new Exception().printStackTrace();
|
||||||
|
preloader.onScrolledToLastItem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
@ -770,7 +793,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public BindableViewHolder<StatusDisplayItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public BindableViewHolder<StatusDisplayItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
return (BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent);
|
BindableViewHolder<StatusDisplayItem> holder=(BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent, BaseStatusListFragment.this);
|
||||||
|
onModifyItemViewHolder(holder);
|
||||||
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -801,15 +826,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
||||||
private Paint dividerPaint=new Paint(), hiddenMediaPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
private Paint dividerPaint=new Paint();
|
||||||
private Typeface mediumTypeface=Typeface.create("sans-serif-medium", Typeface.NORMAL);
|
|
||||||
private Layout mediaHiddenTitleLayout, mediaHiddenTextLayout, tapToRevealTextLayout;
|
|
||||||
private int currentMediaHiddenLayoutsWidth=0;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OutlineVariant));
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||||
dividerPaint.setStrokeWidth(V.dp(1));
|
dividerPaint.setStrokeWidth(V.dp(0.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -819,80 +841,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||||
View bottomSibling=parent.getChildAt(i+1);
|
View bottomSibling=parent.getChildAt(i+1);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
if(needDrawDivider(holder, siblingHolder)){
|
||||||
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
|
||||||
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
|
|
||||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean needDrawDivider(RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||||
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
if(needDividerForExtraItem(holder.itemView, siblingHolder.itemView, holder, siblingHolder))
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
return true;
|
||||||
View child=parent.getChildAt(i);
|
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh){
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
// Do not draw dividers between hashtag and/or account rows
|
||||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder))
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
return false;
|
||||||
hiddenMediaPaint.setColor(0x80000000);
|
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) return false;
|
||||||
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
return (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP;
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
}
|
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
|
||||||
View child=parent.getChildAt(i);
|
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
|
||||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
|
||||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
|
||||||
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
|
||||||
int listWidth=getListWidthForMediaLayout();
|
|
||||||
int width=Math.min(listWidth, UiUtils.MAX_WIDTH);
|
|
||||||
if(currentMediaHiddenLayoutsWidth!=width)
|
|
||||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
|
||||||
c.save();
|
|
||||||
float totalHeight;
|
|
||||||
boolean hiddenByAuthor=imgHolder.getItem().status.sensitive;
|
|
||||||
if(hiddenByAuthor)
|
|
||||||
totalHeight=mediaHiddenTitleLayout.getHeight()+mediaHiddenTextLayout.getHeight()+V.dp(8);
|
|
||||||
else
|
|
||||||
totalHeight=tapToRevealTextLayout.getHeight();
|
|
||||||
c.translate(child.getX()+V.dp(16), child.getY()+child.getHeight()/2f-totalHeight/2f);
|
|
||||||
if(hiddenByAuthor){
|
|
||||||
mediaHiddenTitleLayout.draw(c);
|
|
||||||
c.translate(0, mediaHiddenTitleLayout.getHeight()+V.dp(8));
|
|
||||||
mediaHiddenTextLayout.draw(c);
|
|
||||||
}else{
|
|
||||||
tapToRevealTextLayout.draw(c);
|
|
||||||
}
|
|
||||||
c.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rebuildMediaHiddenLayouts(int width){
|
|
||||||
currentMediaHiddenLayoutsWidth=width;
|
|
||||||
String title=getString(R.string.sensitive_content);
|
|
||||||
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
titlePaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray50));
|
|
||||||
titlePaint.setTextSize(V.dp(22));
|
|
||||||
titlePaint.setTypeface(mediumTypeface);
|
|
||||||
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
|
||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
|
||||||
.build();
|
|
||||||
String tapToReveal=getString(R.string.tap_to_reveal);
|
|
||||||
tapToRevealTextLayout=StaticLayout.Builder.obtain(tapToReveal, 0, tapToReveal.length(), titlePaint, width)
|
|
||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
|
||||||
.build();
|
|
||||||
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
textPaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray200));
|
|
||||||
textPaint.setTextSize(V.dp(16));
|
|
||||||
String text=getString(R.string.sensitive_content_explain);
|
|
||||||
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
|
||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
|
||||||
.setLineSpacing(V.dp(5), 1f)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
@ -39,8 +40,13 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
return Filter.FilterContext.ACCOUNT;
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FilterContext getFilterContext() {
|
||||||
|
return FilterContext.ACCOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,18 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.TypedArray;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.media.MediaMetadataRetriever;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Gravity;
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.style.BulletSpan;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -12,28 +20,34 @@ import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.UpdateAttachment;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.parceler.Parcels;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import java.util.Collections;
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import androidx.annotation.NonNull;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import androidx.annotation.Nullable;
|
||||||
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment implements OnBackPressedListener{
|
||||||
|
private static final String TAG="ComposeImageDescription";
|
||||||
|
|
||||||
private String accountID, attachmentID;
|
private String accountID, attachmentID;
|
||||||
private EditText edit;
|
private EditText edit;
|
||||||
private Button saveButton;
|
private ImageView image;
|
||||||
|
private ContextThemeWrapper themeWrapper;
|
||||||
|
private PhotoViewer photoViewer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -46,7 +60,13 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setTitle(R.string.edit_image);
|
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||||
|
setTitle(R.string.add_alt_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
|
return super.onCreateView(themeWrapper.getSystemService(LayoutInflater.class), container, savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,14 +74,48 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||||
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
|
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
|
||||||
|
|
||||||
edit=view.findViewById(R.id.edit);
|
edit=view.findViewById(R.id.edit);
|
||||||
ImageView image=view.findViewById(R.id.photo);
|
image=view.findViewById(R.id.photo);
|
||||||
|
int width=getArguments().getInt("width", 0);
|
||||||
|
int height=getArguments().getInt("height", 0);
|
||||||
|
if(width>0 && height>0){
|
||||||
|
// image.setAspectRatio(Math.max(1f, (float)width/height));
|
||||||
|
}
|
||||||
|
image.setOnClickListener(v->openPhotoViewer());
|
||||||
Uri uri=getArguments().getParcelable("uri");
|
Uri uri=getArguments().getParcelable("uri");
|
||||||
|
Attachment.Type type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
|
||||||
|
if(type==Attachment.Type.IMAGE)
|
||||||
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
|
||||||
|
else
|
||||||
|
loadVideoThumbIntoView(image, uri);
|
||||||
edit.setText(getArguments().getString("existingDescription"));
|
edit.setText(getArguments().getString("existingDescription"));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadVideoThumbIntoView(ImageView target, Uri uri){
|
||||||
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
Context context=getActivity();
|
||||||
|
if(context==null)
|
||||||
|
return;
|
||||||
|
try{
|
||||||
|
MediaMetadataRetriever mmr=new MediaMetadataRetriever();
|
||||||
|
mmr.setDataSource(context, uri);
|
||||||
|
Bitmap frame=mmr.getFrameAtTime(3_000_000);
|
||||||
|
mmr.release();
|
||||||
|
int size=Math.max(frame.getWidth(), frame.getHeight());
|
||||||
|
int maxSize=V.dp(250);
|
||||||
|
if(size>maxSize){
|
||||||
|
float factor=maxSize/(float)size;
|
||||||
|
frame=Bitmap.createScaledBitmap(frame, Math.round(frame.getWidth()*factor), Math.round(frame.getHeight()*factor), true);
|
||||||
|
}
|
||||||
|
Bitmap finalFrame=frame;
|
||||||
|
target.post(()->target.setImageBitmap(finalFrame));
|
||||||
|
}catch(Exception x){
|
||||||
|
Log.w(TAG, "loadVideoThumbIntoView: error getting video frame", x);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
@ -71,43 +125,114 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
TypedArray ta=getActivity().obtainStyledAttributes(new int[]{R.attr.secondaryButtonStyle});
|
inflater.inflate(R.menu.compose_image_description, menu);
|
||||||
int buttonStyle=ta.getResourceId(0, 0);
|
|
||||||
ta.recycle();
|
|
||||||
saveButton=new Button(getActivity(), null, 0, buttonStyle);
|
|
||||||
saveButton.setText(R.string.save);
|
|
||||||
saveButton.setOnClickListener(this::onSaveClick);
|
|
||||||
FrameLayout wrap=new FrameLayout(getActivity());
|
|
||||||
wrap.addView(saveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
|
|
||||||
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
|
||||||
wrap.setClipToPadding(false);
|
|
||||||
MenuItem item=menu.add(R.string.publish);
|
|
||||||
item.setActionView(wrap);
|
|
||||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
if(item.getItemId()==R.id.help){
|
||||||
|
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
|
||||||
|
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
|
||||||
|
for(BulletSpan span:spans){
|
||||||
|
BulletSpan betterSpan;
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
|
||||||
|
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
|
||||||
|
else
|
||||||
|
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
|
||||||
|
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
|
||||||
|
msg.removeSpan(span);
|
||||||
|
}
|
||||||
|
new M3AlertDialogBuilder(themeWrapper)
|
||||||
|
.setTitle(R.string.what_is_alt_text)
|
||||||
|
.setMessage(msg)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSaveClick(View v){
|
|
||||||
new UpdateAttachment(attachmentID, edit.getText().toString().trim())
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Attachment result){
|
public boolean onBackPressed(){
|
||||||
Bundle r=new Bundle();
|
deliverResult();
|
||||||
r.putParcelable("attachment", Parcels.wrap(result));
|
return false;
|
||||||
setResult(true, r);
|
|
||||||
Nav.finish(ComposeImageDescriptionFragment.this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
protected LayoutInflater getToolbarLayoutInflater(){
|
||||||
error.showToast(getActivity());
|
return LayoutInflater.from(themeWrapper);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.saving, false)
|
private void deliverResult(){
|
||||||
.exec(accountID);
|
Bundle r=new Bundle();
|
||||||
|
r.putString("text", edit.getText().toString().trim());
|
||||||
|
r.putString("attachment", attachmentID);
|
||||||
|
setResult(true, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openPhotoViewer(){
|
||||||
|
Attachment fakeAttachment=new Attachment();
|
||||||
|
fakeAttachment.id="local";
|
||||||
|
fakeAttachment.type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
|
||||||
|
int width=getArguments().getInt("width", 0);
|
||||||
|
int height=getArguments().getInt("height", 0);
|
||||||
|
Uri uri=getArguments().getParcelable("uri");
|
||||||
|
fakeAttachment.url=uri.toString();
|
||||||
|
fakeAttachment.meta=new Attachment.Metadata();
|
||||||
|
fakeAttachment.meta.width=width;
|
||||||
|
fakeAttachment.meta.height=height;
|
||||||
|
|
||||||
|
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){
|
||||||
|
@Override
|
||||||
|
public void setPhotoViewVisibility(int index, boolean visible){
|
||||||
|
image.setAlpha(visible ? 1f : 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||||
|
int[] pos={0, 0};
|
||||||
|
image.getLocationOnScreen(pos);
|
||||||
|
outRect.set(pos[0], pos[1], pos[0]+image.getWidth(), pos[1]+image.getHeight());
|
||||||
|
image.setElevation(1f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
|
||||||
|
image.setTranslationX(translateX);
|
||||||
|
image.setTranslationY(translateY);
|
||||||
|
image.setScaleX(scale);
|
||||||
|
image.setScaleY(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endPhotoViewTransition(){
|
||||||
|
Drawable d=image.getDrawable();
|
||||||
|
image.setImageDrawable(null);
|
||||||
|
image.setImageDrawable(d);
|
||||||
|
|
||||||
|
image.setTranslationX(0f);
|
||||||
|
image.setTranslationY(0f);
|
||||||
|
image.setScaleX(1f);
|
||||||
|
image.setScaleY(1f);
|
||||||
|
image.setElevation(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||||
|
return image.getDrawable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void photoViewerDismissed(){
|
||||||
|
photoViewer=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissions(String[] permissions){
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
photoViewer.removeMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import android.view.MotionEvent;
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
@ -35,10 +34,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.hootsuite.nachos.NachoTextView;
|
import com.hootsuite.nachos.NachoTextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
@ -52,6 +52,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
@ -59,7 +60,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private TimelinesAdapter adapter;
|
private TimelinesAdapter adapter;
|
||||||
private final ItemTouchHelper itemTouchHelper;
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
|
@ -121,7 +122,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
itemTouchHelper.attachToRecyclerView(list);
|
itemTouchHelper.attachToRecyclerView(list);
|
||||||
refreshLayout.setEnabled(false);
|
refreshLayout.setEnabled(false);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,7 +188,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
makeBackItem(listsMenu);
|
makeBackItem(listsMenu);
|
||||||
makeBackItem(hashtagsMenu);
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
||||||
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
||||||
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
||||||
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
@ -200,9 +201,11 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveTimelines() {
|
private void saveTimelines() {
|
||||||
updated = true;
|
updated=true;
|
||||||
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
|
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||||
GlobalUserPreferences.save();
|
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
|
||||||
|
prefs.timelines=data;
|
||||||
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTimeline(int position) {
|
private void removeTimeline(int position) {
|
||||||
|
@ -214,7 +217,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false);
|
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +259,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
||||||
|
|
||||||
|
View divider = view.findViewById(R.id.divider);
|
||||||
Button advancedBtn = view.findViewById(R.id.advanced);
|
Button advancedBtn = view.findViewById(R.id.advanced);
|
||||||
EditText editText = view.findViewById(R.id.input);
|
EditText editText = view.findViewById(R.id.input);
|
||||||
if (item != null) editText.setText(item.getCustomTitle());
|
if (item != null) editText.setText(item.getCustomTitle());
|
||||||
|
@ -264,11 +268,12 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
|
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
|
||||||
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
|
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
|
||||||
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
|
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
|
||||||
view.findViewById(R.id.divider).setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
|
|
||||||
advancedBtn.setOnClickListener(l -> {
|
advancedBtn.setOnClickListener(l -> {
|
||||||
advancedBtn.setSelected(!advancedBtn.isSelected());
|
advancedBtn.setSelected(!advancedBtn.isSelected());
|
||||||
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
||||||
|
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
||||||
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
||||||
|
UiUtils.beginLayoutTransition((ViewGroup) view);
|
||||||
});
|
});
|
||||||
|
|
||||||
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
|
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
|
||||||
|
@ -281,7 +286,8 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
|
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
tagMain.setText(item.getHashtagName());
|
tagMain.setText(item.getHashtagName());
|
||||||
boolean hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny());
|
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
|
||||||
|
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
|
||||||
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
||||||
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
||||||
if (item.isHashtagLocalOnly()) {
|
if (item.isHashtagLocalOnly()) {
|
||||||
|
@ -292,6 +298,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
||||||
advancedBtn.setSelected(true);
|
advancedBtn.setSelected(true);
|
||||||
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
||||||
tagWrap.setVisibility(View.VISIBLE);
|
tagWrap.setVisibility(View.VISIBLE);
|
||||||
|
divider.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.ACCOUNT;
|
return FilterContext.ACCOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag>{
|
||||||
|
private Account account;
|
||||||
|
private String accountID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||||
|
onDataLoaded(getArguments().getParcelableArrayList("hashtags").stream().map(p->(Hashtag)Parcels.unwrap(p)).collect(Collectors.toList()), false);
|
||||||
|
setTitle(R.string.hashtags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<StatusDisplayItem> buildDisplayItems(Hashtag s){
|
||||||
|
return Collections.singletonList(new HashtagStatusDisplayItem(s.name, this, s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAccountToKnown(Hashtag s){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(String id){
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountID, id, data.stream().filter(h -> Objects.equals(h.name, id)).findAny().map(h -> h.following).orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base){
|
||||||
|
return null; // TODO
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
@ -254,7 +254,7 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
||||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
|
@ -278,7 +278,7 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||||
actionWrap.setVisibility(View.VISIBLE);
|
actionWrap.setVisibility(View.VISIBLE);
|
||||||
acceptWrap.setVisibility(View.GONE);
|
acceptWrap.setVisibility(View.GONE);
|
||||||
rejectWrap.setVisibility(View.GONE);
|
rejectWrap.setVisibility(View.GONE);
|
||||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,6 +313,7 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||||
private void onFollowRequestButtonClick(View v) {
|
private void onFollowRequestButtonClick(View v) {
|
||||||
itemView.setHasTransientState(true);
|
itemView.setHasTransientState(true);
|
||||||
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, accountID, null, v == acceptButton, relationship, rel -> {
|
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, accountID, null, v == acceptButton, relationship, rel -> {
|
||||||
|
if(getContext()==null) return;
|
||||||
itemView.setHasTransientState(false);
|
itemView.setHasTransientState(false);
|
||||||
relationships.put(item.account.id, rel);
|
relationships.put(item.account.id, rel);
|
||||||
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||||
|
@ -328,6 +329,7 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||||
private void onActionButtonClick(View v){
|
private void onActionButtonClick(View v){
|
||||||
itemView.setHasTransientState(true);
|
itemView.setHasTransientState(true);
|
||||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||||
|
if(getContext()==null) return;
|
||||||
itemView.setHasTransientState(false);
|
itemView.setHasTransientState(false);
|
||||||
relationships.put(item.account.id, rel);
|
relationships.put(item.account.id, rel);
|
||||||
rebind();
|
rebind();
|
||||||
|
|
|
@ -21,7 +21,7 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
@ -24,4 +25,8 @@ public interface HasAccountID {
|
||||||
default Optional<Instance> getInstance() {
|
default Optional<Instance> getInstance() {
|
||||||
return getSession().getInstance();
|
return getSession().getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default AccountLocalPreferences getLocalPrefs() {
|
||||||
|
return AccountSessionManager.get(getAccountID()).getLocalPreferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
|
||||||
|
public interface HasElevationOnScrollListener {
|
||||||
|
ElevationOnScrollListener getElevationOnScrollListener();
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
@ -126,7 +126,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly)
|
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
@ -160,12 +160,12 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSetFabBottomInset(int inset){
|
protected void onSetFabBottomInset(int inset){
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return FilterContext.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -23,26 +23,27 @@ import androidx.annotation.Nullable;
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TabBar;
|
import org.joinmastodon.android.ui.views.TabBar;
|
||||||
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
@ -59,42 +60,38 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
private FragmentRootLinearLayout content;
|
private FragmentRootLinearLayout content;
|
||||||
private HomeTabFragment homeTabFragment;
|
private HomeTabFragment homeTabFragment;
|
||||||
private NotificationsFragment notificationsFragment;
|
private NotificationsFragment notificationsFragment;
|
||||||
private DiscoverFragment searchFragment;
|
private DiscoverFragment discoverFragment;
|
||||||
private ProfileFragment profileFragment;
|
private ProfileFragment profileFragment;
|
||||||
private TabBar tabBar;
|
private TabBar tabBar;
|
||||||
private View tabBarWrap;
|
private View tabBarWrap;
|
||||||
private ImageView tabBarAvatar;
|
private ImageView tabBarAvatar;
|
||||||
private ImageView notificationTabIcon;
|
|
||||||
@IdRes
|
@IdRes
|
||||||
private int currentTab=R.id.tab_home;
|
private int currentTab=R.id.tab_home;
|
||||||
|
private TextView notificationsBadge;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean isPleroma;
|
private boolean isAkkoma;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
E.register(this);
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.sk_app_name);
|
setTitle(R.string.sk_app_name);
|
||||||
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
isAkkoma = getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
.map(Instance::isAkkoma)
|
|
||||||
.orElse(false);
|
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
// TODO: clean up
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
homeTabFragment=new HomeTabFragment();
|
homeTabFragment=new HomeTabFragment();
|
||||||
homeTabFragment.setArguments(args);
|
homeTabFragment.setArguments(args);
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("disableDiscover", isPleroma);
|
args.putBoolean("disableDiscover", isAkkoma);
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
searchFragment=new DiscoverFragment();
|
discoverFragment=new DiscoverFragment();
|
||||||
searchFragment.setArguments(args);
|
discoverFragment.setArguments(args);
|
||||||
notificationsFragment=new NotificationsFragment();
|
notificationsFragment=new NotificationsFragment();
|
||||||
notificationsFragment.setArguments(args);
|
notificationsFragment.setArguments(args);
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
|
@ -104,6 +101,13 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
profileFragment.setArguments(args);
|
profileFragment.setArguments(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
E.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy(){
|
||||||
|
super.onDestroy();
|
||||||
|
E.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -121,24 +125,28 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
tabBar.setListeners(this::onTabSelected, this::onTabLongClick);
|
tabBar.setListeners(this::onTabSelected, this::onTabLongClick);
|
||||||
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
||||||
|
|
||||||
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
// this one's for the pill haters (https://m3.material.io/components/navigation-bar/overview)
|
||||||
tabBarAvatar.setOutlineProvider(new ViewOutlineProvider(){
|
if (GlobalUserPreferences.disableM3PillActiveIndicator) {
|
||||||
@Override
|
for(int i=0; i<tabBar.getChildCount(); i++){
|
||||||
public void getOutline(View view, Outline outline){
|
ViewGroup f=(ViewGroup) tabBar.getChildAt(i);
|
||||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
f.setBackgroundResource(R.drawable.bg_tabbar_tab_ripple);
|
||||||
}
|
}
|
||||||
});
|
tabBar.findViewById(R.id.tab_profile).setBackgroundResource(R.drawable.bg_tab_profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
||||||
|
tabBarAvatar.setOutlineProvider(OutlineProviders.OVAL);
|
||||||
tabBarAvatar.setClipToOutline(true);
|
tabBarAvatar.setClipToOutline(true);
|
||||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||||
ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28)));
|
ViewImageLoader.loadWithoutAnimation(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(24), V.dp(24)));
|
||||||
|
|
||||||
notificationTabIcon=content.findViewById(R.id.tab_notifications);
|
notificationsBadge=tabBar.findViewById(R.id.notifications_badge);
|
||||||
updateNotificationBadge();
|
notificationsBadge.setVisibility(View.GONE);
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(me.grishka.appkit.R.id.fragment_wrap, homeTabFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, homeTabFragment)
|
||||||
.add(me.grishka.appkit.R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, discoverFragment).hide(discoverFragment)
|
||||||
.add(me.grishka.appkit.R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||||
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
@ -165,7 +173,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
if(savedInstanceState==null) return;
|
if(savedInstanceState==null) return;
|
||||||
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
||||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
discoverFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
currentTab=savedInstanceState.getInt("selectedTab");
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
|
@ -173,7 +181,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
Fragment current=fragmentForTab(currentTab);
|
Fragment current=fragmentForTab(currentTab);
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.hide(homeTabFragment)
|
.hide(homeTabFragment)
|
||||||
.hide(searchFragment)
|
.hide(discoverFragment)
|
||||||
.hide(notificationsFragment)
|
.hide(notificationsFragment)
|
||||||
.hide(profileFragment)
|
.hide(profileFragment)
|
||||||
.show(current)
|
.show(current)
|
||||||
|
@ -189,7 +197,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean wantsLightStatusBar(){
|
public boolean wantsLightStatusBar(){
|
||||||
return currentTab!=R.id.tab_profile && !UiUtils.isDarkTheme();
|
return !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -201,14 +209,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
if(Build.VERSION.SDK_INT>=27){
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
int inset=insets.getSystemWindowInsetBottom();
|
||||||
tabBarWrap.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
tabBarWrap.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(24)) : 0);
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0));
|
||||||
}else{
|
}else{
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
}
|
}
|
||||||
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||||
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
discoverFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +225,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
if(tab==R.id.tab_home){
|
if(tab==R.id.tab_home){
|
||||||
return homeTabFragment;
|
return homeTabFragment;
|
||||||
}else if(tab==R.id.tab_search){
|
}else if(tab==R.id.tab_search){
|
||||||
return searchFragment;
|
return discoverFragment;
|
||||||
}else if(tab==R.id.tab_notifications){
|
}else if(tab==R.id.tab_notifications){
|
||||||
return notificationsFragment;
|
return notificationsFragment;
|
||||||
}else if(tab==R.id.tab_profile){
|
}else if(tab==R.id.tab_profile){
|
||||||
|
@ -235,10 +243,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
|
|
||||||
private void onTabSelected(@IdRes int tab){
|
private void onTabSelected(@IdRes int tab){
|
||||||
Fragment newFragment=fragmentForTab(tab);
|
Fragment newFragment=fragmentForTab(tab);
|
||||||
if(tab==currentTab){
|
if(tab==currentTab && newFragment instanceof ScrollableToTop scrollable) {
|
||||||
if (tab == R.id.tab_search)
|
|
||||||
searchFragment.onSelect();
|
|
||||||
else if(newFragment instanceof ScrollableToTop scrollable)
|
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -247,7 +252,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
|
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
|
||||||
currentTab=tab;
|
currentTab=tab;
|
||||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||||
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeTriggerLoading(Fragment newFragment){
|
private void maybeTriggerLoading(Fragment newFragment){
|
||||||
|
@ -258,7 +262,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
((DiscoverFragment) newFragment).loadData();
|
((DiscoverFragment) newFragment).loadData();
|
||||||
}else if(newFragment instanceof NotificationsFragment){
|
}else if(newFragment instanceof NotificationsFragment){
|
||||||
((NotificationsFragment) newFragment).loadData();
|
((NotificationsFragment) newFragment).loadData();
|
||||||
// TODO make an interface?
|
|
||||||
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
||||||
for (StatusBarNotification notification : nm.getActiveNotifications()) {
|
for (StatusBarNotification notification : nm.getActiveNotifications()) {
|
||||||
if (accountID.equals(notification.getTag())) {
|
if (accountID.equals(notification.getTag())) {
|
||||||
|
@ -276,6 +279,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
}
|
}
|
||||||
new AccountSwitcherSheet(getActivity(), this).show();
|
new AccountSwitcherSheet(getActivity(), this).show();
|
||||||
return true;
|
return true;
|
||||||
|
} else if(tab==R.id.tab_search){
|
||||||
|
tabBar.selectTab(R.id.tab_search);
|
||||||
|
onTabSelected(R.id.tab_search);
|
||||||
|
discoverFragment.openSearch();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -285,7 +293,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
if(currentTab==R.id.tab_profile)
|
if(currentTab==R.id.tab_profile)
|
||||||
if (profileFragment.onBackPressed()) return true;
|
if (profileFragment.onBackPressed()) return true;
|
||||||
if(currentTab==R.id.tab_search)
|
if(currentTab==R.id.tab_search)
|
||||||
if (searchFragment.onBackPressed()) return true;
|
if (discoverFragment.onBackPressed()) return true;
|
||||||
if (currentTab!=R.id.tab_home) {
|
if (currentTab!=R.id.tab_home) {
|
||||||
tabBar.selectTab(R.id.tab_home);
|
tabBar.selectTab(R.id.tab_home);
|
||||||
onTabSelected(R.id.tab_home);
|
onTabSelected(R.id.tab_home);
|
||||||
|
@ -300,52 +308,79 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("selectedTab", currentTab);
|
outState.putInt("selectedTab", currentTab);
|
||||||
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
||||||
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
if (discoverFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", discoverFragment);
|
||||||
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNotificationBadge() {
|
|
||||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
Optional<Instance> instance = session.getInstance();
|
|
||||||
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
|
||||||
|
|
||||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
|
|
||||||
.setCallback(new Callback<>() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> notifications) {
|
protected void onShown(){
|
||||||
if (notifications.size() > 0) {
|
super.onShown();
|
||||||
try {
|
reloadNotificationsForUnreadCount();
|
||||||
long newestId = Long.parseLong(notifications.get(0).id);
|
|
||||||
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
|
|
||||||
setNotificationBadge(newestId > lastSeenId);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
setNotificationBadge(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reloadNotificationsForUnreadCount(){
|
||||||
|
List<Notification>[] notifications=new List[]{null};
|
||||||
|
String[] marker={null};
|
||||||
|
|
||||||
|
AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{
|
||||||
|
marker[0]=m;
|
||||||
|
if(notifications[0]!=null){
|
||||||
|
updateUnreadCount(notifications[0], marker[0]);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().getNotifications(null, 40, false, false, true, new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||||
|
notifications[0]=result.items;
|
||||||
|
if(marker[0]!=null)
|
||||||
|
updateUnreadCount(notifications[0], marker[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error){}
|
||||||
setNotificationBadge(false);
|
});
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNotificationBadge(boolean badge) {
|
@SuppressLint("DefaultLocale")
|
||||||
notificationTabIcon.setImageResource(badge
|
private void updateUnreadCount(List<Notification> notifications, String marker){
|
||||||
? R.drawable.ic_fluent_alert_28_selector_badged
|
if(notifications.isEmpty() || ObjectIdComparator.INSTANCE.compare(notifications.get(0).id, marker)<=0){
|
||||||
: R.drawable.ic_fluent_alert_28_selector);
|
V.setVisibilityAnimated(notificationsBadge, View.GONE);
|
||||||
|
}else{
|
||||||
|
V.setVisibilityAnimated(notificationsBadge, View.VISIBLE);
|
||||||
|
if(ObjectIdComparator.INSTANCE.compare(notifications.get(notifications.size()-1).id, marker)>0){
|
||||||
|
notificationsBadge.setText(String.format("%d+", notifications.size()));
|
||||||
|
}else{
|
||||||
|
int count=0;
|
||||||
|
for(Notification n:notifications){
|
||||||
|
if(n.id.equals(marker))
|
||||||
|
break;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
notificationsBadge.setText(String.format("%d", count));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onNotificationReceived(NotificationReceivedEvent notificationReceivedEvent) {
|
public void onNotificationsMarkerUpdated(NotificationsMarkerUpdatedEvent ev){
|
||||||
if (notificationReceivedEvent.account.equals(accountID)) setNotificationBadge(true);
|
if(!ev.accountID.equals(accountID))
|
||||||
|
return;
|
||||||
|
if(ev.clearUnread)
|
||||||
|
V.setVisibilityAnimated(notificationsBadge, View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
public void onStatusDisplaySettingsChanged(StatusDisplaySettingsChangedEvent ev){
|
||||||
setNotificationBadge(false);
|
if(!ev.accountID.equals(accountID))
|
||||||
|
return;
|
||||||
|
if(homeTabFragment.getCurrentFragment() instanceof LoaderFragment lf && lf.loaded
|
||||||
|
&& lf instanceof BaseStatusListFragment<?> homeTimelineFragment)
|
||||||
|
homeTimelineFragment.rebuildAllDisplayItems();
|
||||||
|
if(notificationsFragment.getCurrentFragment() instanceof LoaderFragment lf && lf.loaded
|
||||||
|
&& lf instanceof BaseStatusListFragment<?> l)
|
||||||
|
l.rebuildAllDisplayItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.app.Fragment;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -43,10 +44,12 @@ import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||||
import org.joinmastodon.android.model.Announcement;
|
import org.joinmastodon.android.model.Announcement;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
@ -55,6 +58,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -72,8 +76,9 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent {
|
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent, HasElevationOnScrollListener {
|
||||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
@ -91,7 +96,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
private PopupMenu switcherPopup;
|
private PopupMenu switcherPopup;
|
||||||
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
||||||
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
||||||
private List<TimelineDefinition> timelineDefinitions;
|
private List<TimelineDefinition> timelinesList;
|
||||||
private int count;
|
private int count;
|
||||||
private Fragment[] fragments;
|
private Fragment[] fragments;
|
||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
|
@ -102,19 +107,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
private View overflowActionView = null;
|
private View overflowActionView = null;
|
||||||
private boolean announcementsBadged, settingsBadged;
|
private boolean announcementsBadged, settingsBadged;
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
|
private ElevationOnScrollListener elevationOnScrollListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
E.register(this);
|
E.register(this);
|
||||||
accountID = getArguments().getString("account");
|
accountID = getArguments().getString("account");
|
||||||
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID));
|
timelinesList=AccountSessionManager.get(accountID).getLocalPreferences().timelines;
|
||||||
assert timelineDefinitions != null;
|
assert timelinesList!=null;
|
||||||
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
if(timelinesList.isEmpty()) timelinesList=List.of(TimelineDefinition.HOME_TIMELINE);
|
||||||
count = timelineDefinitions.size();
|
count=timelinesList.size();
|
||||||
fragments = new Fragment[count];
|
fragments=new Fragment[count];
|
||||||
tabViews = new FrameLayout[count];
|
tabViews=new FrameLayout[count];
|
||||||
timelines = new TimelineDefinition[count];
|
timelines=new TimelineDefinition[count];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,7 +131,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||||
|
FragmentRootLinearLayout rootView = new FragmentRootLinearLayout(getContext());
|
||||||
|
rootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
FrameLayout view = new FrameLayout(getContext());
|
FrameLayout view = new FrameLayout(getContext());
|
||||||
|
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
rootView.addView(view);
|
||||||
inflater.inflate(R.layout.compose_fab, view);
|
inflater.inflate(R.layout.compose_fab, view);
|
||||||
fab = view.findViewById(R.id.fab);
|
fab = view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
@ -140,8 +150,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
args.putBoolean("__disable_fab", true);
|
args.putBoolean("__disable_fab", true);
|
||||||
args.putBoolean("onlyPosts", true);
|
args.putBoolean("onlyPosts", true);
|
||||||
|
|
||||||
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
for (int i=0; i < timelinesList.size(); i++) {
|
||||||
TimelineDefinition tl = timelineDefinitions.get(i);
|
TimelineDefinition tl = timelinesList.get(i);
|
||||||
fragments[i] = tl.getFragment();
|
fragments[i] = tl.getFragment();
|
||||||
timelines[i] = tl;
|
timelines[i] = tl;
|
||||||
}
|
}
|
||||||
|
@ -168,7 +178,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
overflowActionView.setOnClickListener(l -> overflowPopup.show());
|
overflowActionView.setOnClickListener(l -> overflowPopup.show());
|
||||||
overflowActionView.setOnTouchListener(overflowPopup.getDragToOpenListener());
|
overflowActionView.setOnTouchListener(overflowPopup.getDragToOpenListener());
|
||||||
|
|
||||||
return view;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@ -243,6 +253,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elevationOnScrollListener = new ElevationOnScrollListener((FragmentRootLinearLayout) view, getToolbar());
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||||
}
|
}
|
||||||
|
@ -289,6 +301,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ElevationOnScrollListener getElevationOnScrollListener() {
|
||||||
|
return elevationOnScrollListener;
|
||||||
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
||||||
l.onFabClick(v);
|
l.onFabClick(v);
|
||||||
|
@ -466,10 +482,19 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
&& fabulous.isScrolling();
|
&& fabulous.isScrolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (elevationOnScrollListener != null) elevationOnScrollListener.setViews(getToolbar());
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
private void updateSwitcherIcon(int i) {
|
||||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
showFab();
|
showFab();
|
||||||
|
if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f) {
|
||||||
|
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -484,7 +509,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
getToolbar().post(() -> overflowPopup.show());
|
getToolbar().post(() -> overflowPopup.show());
|
||||||
return true;
|
return true;
|
||||||
} else if (id == R.id.settings || id == R.id.settings_action) {
|
} else if (id == R.id.settings || id == R.id.settings_action) {
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
Nav.go(getActivity(), SettingsMainFragment.class, args);
|
||||||
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
||||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||||
} else if (id == R.id.edit_timelines) {
|
} else if (id == R.id.edit_timelines) {
|
||||||
|
@ -633,8 +658,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
@Override
|
@Override
|
||||||
protected void onShown() {
|
protected void onShown() {
|
||||||
super.onShown();
|
super.onShown();
|
||||||
Object pinnedTimelines = GlobalUserPreferences.pinnedTimelines.get(accountID);
|
Object timelines = AccountSessionManager.get(accountID).getLocalPreferences().timelines;
|
||||||
if (pinnedTimelines != null && timelineDefinitions != pinnedTimelines) UiUtils.restartApp();
|
if (timelines != null && timelinesList!= timelines) UiUtils.restartApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -698,6 +723,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||||
return hashtagsItems.values();
|
return hashtagsItems.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Fragment getCurrentFragment() {
|
||||||
|
return fragments[pager.getCurrentItem()];
|
||||||
|
}
|
||||||
|
|
||||||
public ImageButton getFab() {
|
public ImageButton getFab() {
|
||||||
return fab;
|
return fab;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,13 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
@ -49,8 +51,9 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean typeFilterPredicate(Status s) {
|
private boolean typeFilterPredicate(Status s) {
|
||||||
return (GlobalUserPreferences.showReplies || s.inReplyToId == null) &&
|
AccountLocalPreferences lp=getLocalPrefs();
|
||||||
(GlobalUserPreferences.showBoosts || s.reblog == null);
|
return (lp.showReplies || s.inReplyToId == null) &&
|
||||||
|
(lp.showBoosts || s.reblog == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterPosts(List<Status> items) {
|
private List<Status> filterPosts(List<Status> items) {
|
||||||
|
@ -110,7 +113,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
new SaveMarkers(topPostID, null)
|
new SaveMarkers(topPostID, null)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SaveMarkers.Response result){
|
public void onSuccess(TimelineMarkers result){
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,8 +126,8 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(Status status){
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(status), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNewPosts(){
|
private void loadNewPosts(){
|
||||||
|
@ -134,7 +137,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
||||||
// between the existing and newly loaded parts of the timeline.
|
// between the existing and newly loaded parts of the timeline.
|
||||||
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
||||||
currentRequest=new GetHomeTimeline(null, null, 20, sinceID)
|
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
@ -151,8 +154,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
|
||||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
||||||
|
@ -169,7 +171,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|
||||||
if (parent.getParentFragment() instanceof HomeFragment homeFragment) {
|
if (parent.getParentFragment() instanceof HomeFragment homeFragment) {
|
||||||
homeFragment.updateNotificationBadge();
|
homeFragment.reloadNotificationsForUnreadCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +184,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
V.setVisibilityAnimated(item.text, View.GONE);
|
V.setVisibilityAnimated(item.text, View.GONE);
|
||||||
GapStatusDisplayItem gap=item.getItem();
|
GapStatusDisplayItem gap=item.getItem();
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null)
|
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
@ -239,6 +241,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
insertedPosts.add(s);
|
insertedPosts.add(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
|
||||||
if(targetList.isEmpty()){
|
if(targetList.isEmpty()){
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
|
@ -285,8 +288,8 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.HOME;
|
return FilterContext.HOME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,7 +18,7 @@ import org.joinmastodon.android.api.requests.lists.UpdateList;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
@ -134,7 +134,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count) {
|
protected void doLoadData(int offset, int count) {
|
||||||
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null)
|
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result) {
|
public void onSuccess(List<Status> result) {
|
||||||
|
@ -167,8 +167,8 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.HOME;
|
return FilterContext.HOME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -42,7 +42,7 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ListsFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private String profileAccountId;
|
private String profileAccountId;
|
||||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||||
|
@ -80,7 +80,7 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
|
public abstract class MastodonRecyclerFragment<T> extends BaseRecyclerFragment<T>{
|
||||||
|
protected ElevationOnScrollListener elevationOnScrollListener;
|
||||||
|
|
||||||
|
public MastodonRecyclerFragment(int perPage){
|
||||||
|
super(perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MastodonRecyclerFragment(int layout, int perPage){
|
||||||
|
super(layout, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<View> getViewsForElevationEffect(){
|
||||||
|
Toolbar toolbar=getToolbar();
|
||||||
|
return toolbar!=null ? Collections.singletonList(toolbar) : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CallSuper
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if (getParentFragment() instanceof HasElevationOnScrollListener elevator)
|
||||||
|
list.addOnScrollListener(elevator.getElevationOnScrollListener());
|
||||||
|
else if(wantsElevationOnScrollEffect())
|
||||||
|
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
|
||||||
|
if(refreshLayout!=null)
|
||||||
|
setRefreshLayoutColors(refreshLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CallSuper
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
if(elevationOnScrollListener!=null){
|
||||||
|
elevationOnScrollListener.setViews(getViewsForElevationEffect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean wantsElevationOnScrollEffect(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRefreshLayoutColors(SwipeRefreshLayout l) {
|
||||||
|
List<Integer> colors = new ArrayList<>(Arrays.asList(
|
||||||
|
UiUtils.isDarkTheme() ? R.color.primary_200 : R.color.primary_600,
|
||||||
|
UiUtils.isDarkTheme() ? R.color.red_primary_200 : R.color.red_primary_600,
|
||||||
|
UiUtils.isDarkTheme() ? R.color.green_primary_200 : R.color.green_primary_600,
|
||||||
|
UiUtils.isDarkTheme() ? R.color.blue_primary_200 : R.color.blue_primary_600,
|
||||||
|
UiUtils.isDarkTheme() ? R.color.purple_200 : R.color.purple_600
|
||||||
|
));
|
||||||
|
int primary = UiUtils.getThemeColorRes(l.getContext(),
|
||||||
|
UiUtils.isDarkTheme() ? R.attr.colorPrimary200 : R.attr.colorPrimary600);
|
||||||
|
if (!colors.contains(primary)) colors.add(0, primary);
|
||||||
|
int offset = colors.indexOf(primary);
|
||||||
|
int[] sorted = new int[colors.size()];
|
||||||
|
for (int i = 0; i < colors.size(); i++) {
|
||||||
|
sorted[i] = colors.get((i + offset) % colors.size());
|
||||||
|
}
|
||||||
|
l.setColorSchemeResources(sorted);
|
||||||
|
int colorBackground=UiUtils.getThemeColor(l.getContext(), R.attr.colorM3Background);
|
||||||
|
int colorPrimary=UiUtils.getThemeColor(l.getContext(), R.attr.colorM3Primary);
|
||||||
|
l.setProgressBackgroundColorSchemeColor(UiUtils.alphaBlendColors(colorBackground, colorPrimary, 0.11f));
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,15 @@ import androidx.annotation.CallSuper;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
|
|
||||||
public abstract class MastodonToolbarFragment extends ToolbarFragment{
|
public abstract class MastodonToolbarFragment extends ToolbarFragment{
|
||||||
|
|
||||||
|
public MastodonToolbarFragment(){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MastodonToolbarFragment(int layout){
|
||||||
|
super(layout);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -24,6 +25,9 @@ import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
|
import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
@ -31,6 +35,8 @@ import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
|
@ -38,15 +44,19 @@ import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent {
|
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent, HasElevationOnScrollListener {
|
||||||
|
|
||||||
private TabLayout tabLayout;
|
TabLayout tabLayout;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
|
private View tabsDivider;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
|
String unreadMarker, realUnreadMarker;
|
||||||
|
private MenuItem markAllReadItem;
|
||||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
||||||
|
private ElevationOnScrollListener elevationOnScrollListener;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,11 +82,19 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
setTitle(R.string.notifications);
|
setTitle(R.string.notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShown() {
|
||||||
|
super.onShown();
|
||||||
|
unreadMarker=realUnreadMarker=AccountSessionManager.get(accountID).getLastKnownNotificationsMarker();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.notifications, menu);
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
||||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests);
|
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
||||||
|
updateMarkAllReadButton();
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests, R.id.mark_all_read);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,15 +111,40 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.mark_all_read) {
|
||||||
|
markAsRead();
|
||||||
|
if (getCurrentFragment() instanceof NotificationsListFragment nlf) {
|
||||||
|
nlf.resetUnreadBackground();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void markAsRead(){
|
||||||
|
if(allNotificationsFragment.getData().isEmpty()) return;
|
||||||
|
String id=allNotificationsFragment.getData().get(0).id;
|
||||||
|
if(ObjectIdComparator.INSTANCE.compare(id, realUnreadMarker)>0){
|
||||||
|
new SaveMarkers(null, id).exec(accountID);
|
||||||
|
if (allNotificationsFragment.isInstanceAkkoma()) {
|
||||||
|
new PleromaMarkNotificationsRead(id).exec(accountID);
|
||||||
|
}
|
||||||
|
AccountSessionManager.get(accountID).setNotificationsMarker(id, true);
|
||||||
|
realUnreadMarker=id;
|
||||||
|
updateMarkAllReadButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMarkAllReadButton(){
|
||||||
|
markAllReadItem.setVisible(!allNotificationsFragment.getData().isEmpty() && realUnreadMarker!=null && !realUnreadMarker.equals(allNotificationsFragment.getData().get(0).id));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_notifications, container, false);
|
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||||
|
|
||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
|
tabsDivider=view.findViewById(R.id.tabs_divider);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
|
|
||||||
|
@ -119,7 +162,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
}
|
}
|
||||||
|
|
||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextSize(V.dp(16));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTabSelected(TabLayout.Tab tab) {}
|
public void onTabSelected(TabLayout.Tab tab) {}
|
||||||
|
@ -139,6 +182,8 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position){
|
public void onPageSelected(int position){
|
||||||
|
if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f)
|
||||||
|
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
|
||||||
if(position==0)
|
if(position==0)
|
||||||
return;
|
return;
|
||||||
Fragment _page=getFragmentForPage(position);
|
Fragment _page=getFragmentForPage(position);
|
||||||
|
@ -176,7 +221,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
case 1 -> R.string.mentions;
|
case 1 -> R.string.mentions;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tabLayoutMediator.attach();
|
tabLayoutMediator.attach();
|
||||||
|
@ -184,6 +228,28 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
elevationOnScrollListener = new ElevationOnScrollListener((FragmentRootLinearLayout) view, getToolbar(), tabLayout);
|
||||||
|
elevationOnScrollListener.setDivider(tabsDivider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (elevationOnScrollListener == null) return;
|
||||||
|
elevationOnScrollListener.setViews(getToolbar(), tabLayout);
|
||||||
|
if (getCurrentFragment() instanceof IsOnTop f) {
|
||||||
|
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElevationOnScrollListener getElevationOnScrollListener() {
|
||||||
|
return elevationOnScrollListener;
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshFollowRequestsBadge() {
|
public void refreshFollowRequestsBadge() {
|
||||||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -228,6 +294,10 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Fragment getCurrentFragment() {
|
||||||
|
return getFragmentForPage(pager.getCurrentItem());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProvideAssistContent(AssistContent assistContent) {
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -10,50 +13,44 @@ import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
|
||||||
import org.joinmastodon.android.model.Emoji;
|
|
||||||
import org.joinmastodon.android.model.Filter;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Markers;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
|
||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
private boolean onlyPosts;
|
private boolean onlyPosts;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
private boolean reloadingFromCache;
|
||||||
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
|
@ -64,6 +61,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
E.register(this);
|
E.register(this);
|
||||||
|
if(savedInstanceState!=null){
|
||||||
|
onlyMentions=savedInstanceState.getBoolean("onlyMentions", false);
|
||||||
|
onlyPosts=savedInstanceState.getBoolean("onlyPosts", false);
|
||||||
|
}
|
||||||
|
if (onlyPosts) {
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS, accountID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,59 +81,35 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
onlyMentions=getArguments().getBoolean("onlyMentions", false);
|
onlyMentions=getArguments().getBoolean("onlyMentions", false);
|
||||||
onlyPosts=getArguments().getBoolean("onlyPosts", false);
|
onlyPosts=getArguments().getBoolean("onlyPosts", false);
|
||||||
}
|
setTitle(R.string.notifications);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
super.onRefresh();
|
|
||||||
if (getParentFragment() instanceof NotificationsFragment notificationsFragment) {
|
|
||||||
notificationsFragment.refreshFollowRequestsBadge();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||||
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
|
NotificationHeaderStatusDisplayItem titleItem;
|
||||||
n.report.targetAccount;
|
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||||
Emoji emoji = new Emoji();
|
titleItem=null;
|
||||||
if(n.emojiUrl!=null){
|
}else{
|
||||||
emoji.shortcode=n.emoji.substring(1,n.emoji.length()-1);
|
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||||
emoji.url=n.emojiUrl;
|
}
|
||||||
emoji.staticUrl=n.emojiUrl;
|
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
||||||
emoji.visibleInPicker=false;
|
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
||||||
|
items.add(titleItem);
|
||||||
|
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
String extraText=switch(n.type){
|
|
||||||
case FOLLOW -> getString(R.string.user_followed_you);
|
|
||||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
|
||||||
case MENTION, STATUS -> null;
|
|
||||||
case REBLOG -> getString(R.string.notification_boosted);
|
|
||||||
case FAVORITE -> getString(R.string.user_favorited);
|
|
||||||
case POLL -> getString(R.string.poll_ended);
|
|
||||||
case UPDATE -> getString(R.string.sk_post_edited);
|
|
||||||
case SIGN_UP -> getString(R.string.sk_signed_up);
|
|
||||||
case REPORT -> getString(R.string.sk_reported);
|
|
||||||
case REACTION, PLEROMA_EMOJI_REACTION ->
|
|
||||||
n.emoji != null ? getString(R.string.sk_reacted_with, n.emoji) : getString(R.string.sk_reacted);
|
|
||||||
};
|
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null;
|
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET); // | StatusDisplayItem.FLAG_NO_HEADER);
|
||||||
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
||||||
if(titleItem!=null)
|
if(titleItem!=null)
|
||||||
items.add(0, titleItem);
|
items.add(0, titleItem);
|
||||||
return items;
|
return items;
|
||||||
}else if(titleItem!=null){
|
}else if(titleItem!=null){
|
||||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
return Collections.singletonList(titleItem);
|
||||||
reportTarget != null ? reportTarget : n.account, n);
|
|
||||||
TextStatusDisplayItem text = n.report != null && !TextUtils.isEmpty(n.report.comment) ?
|
|
||||||
new TextStatusDisplayItem(n.id, n.report.comment, this,
|
|
||||||
Status.ofFake(n.id, n.report.comment, n.createdAt), true) :
|
|
||||||
null;
|
|
||||||
return text == null ? Arrays.asList(titleItem, card) : Arrays.asList(titleItem, text, card);
|
|
||||||
}else{
|
}else{
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addAccountToKnown(Notification s){
|
protected void addAccountToKnown(Notification s){
|
||||||
if(!knownAccounts.containsKey(s.account.id))
|
if(!knownAccounts.containsKey(s.account.id))
|
||||||
|
@ -144,52 +124,38 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
AccountSessionManager.getInstance()
|
AccountSessionManager.getInstance()
|
||||||
.getAccount(accountID).getCacheController()
|
.getAccount(accountID).getCacheController()
|
||||||
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing && !reloadingFromCache, new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(CacheablePaginatedResponse<List<Notification>> result){
|
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null)
|
||||||
if(refreshing)
|
return;
|
||||||
relationships.clear();
|
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
||||||
Set<String> needRelationships=result.items.stream()
|
reloadingFromCache=false;
|
||||||
.filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id))
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
.map(ntf->ntf.account.id)
|
nf.updateMarkAllReadButton();
|
||||||
.collect(Collectors.toSet());
|
|
||||||
loadRelationships(needRelationships);
|
|
||||||
|
|
||||||
Markers markers = AccountSessionManager.getInstance().getAccount(accountID).markers;
|
|
||||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && markers != null && markers.notifications != null){
|
|
||||||
E.post(new AllNotificationsSeenEvent());
|
|
||||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
|
||||||
.notifications.lastReadId = result.items.get(0).id;
|
|
||||||
AccountSessionManager.getInstance().writeAccountsFile();
|
|
||||||
|
|
||||||
if (isInstanceAkkoma()) {
|
|
||||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRelationshipsLoaded(){
|
protected void onShown(){
|
||||||
if(getActivity()==null)
|
super.onShown();
|
||||||
return;
|
if(!dataLoading){
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
if(onlyMentions){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
refresh();
|
||||||
if(holder instanceof AccountCardStatusDisplayItem.Holder accountHolder)
|
}else{
|
||||||
accountHolder.rebind();
|
reloadingFromCache=true;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onHidden(){
|
||||||
super.onShown();
|
super.onHidden();
|
||||||
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
resetUnreadBackground();
|
||||||
// loadData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -205,7 +171,50 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||||
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
|
private Paint paint=new Paint();
|
||||||
|
private Rect tmpRect=new Rect();
|
||||||
|
|
||||||
|
{
|
||||||
|
paint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3SurfaceVariant));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
|
if(TextUtils.isEmpty(nf.unreadMarker))
|
||||||
|
return;
|
||||||
|
for(int i=0;i<parent.getChildCount();i++){
|
||||||
|
View child=parent.getChildAt(i);
|
||||||
|
if(parent.getChildViewHolder(child) instanceof StatusDisplayItem.Holder<?> holder){
|
||||||
|
String itemID=holder.getItemID();
|
||||||
|
if(ObjectIdComparator.INSTANCE.compare(itemID, nf.unreadMarker)>0){
|
||||||
|
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
|
c.drawRect(tmpRect, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<View> getViewsForElevationEffect(){
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
|
ArrayList<View> views=new ArrayList<>(super.getViewsForElevationEffect());
|
||||||
|
views.add(nf.tabLayout);
|
||||||
|
return views;
|
||||||
|
} else {
|
||||||
|
return super.getViewsForElevationEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState){
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putBoolean("onlyMentions", onlyMentions);
|
||||||
|
outState.putBoolean("onlyPosts", onlyPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification getNotificationByID(String id){
|
private Notification getNotificationByID(String id){
|
||||||
|
@ -238,6 +247,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
if (n.status == null) continue;
|
if (n.status == null) continue;
|
||||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
n.status.getContentStatus().update(ev);
|
n.status.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||||
|
@ -252,6 +262,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
if (n.status == null) continue;
|
if (n.status == null) continue;
|
||||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
n.status.getContentStatus().update(ev);
|
n.status.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,6 +300,40 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
|
||||||
|
return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=adapter.getItemCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetUnreadBackground(){
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
|
nf.unreadMarker=nf.realUnreadMarker;
|
||||||
|
list.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh(){
|
||||||
|
super.onRefresh();
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
|
if (!onlyMentions && !onlyPosts) nf.markAsRead();
|
||||||
|
else AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{
|
||||||
|
nf.unreadMarker=nf.realUnreadMarker=m;
|
||||||
|
nf.updateMarkAllReadButton();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resetUnreadBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
|
if (bannerHelper == null) return super.getAdapter();
|
||||||
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return base.path(isInstanceAkkoma()
|
return base.path(isInstanceAkkoma()
|
||||||
|
|
|
@ -7,20 +7,21 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||||
protected List<TimelineDefinition> pinnedTimelines;
|
protected List<TimelineDefinition> timelines;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)));
|
timelines=new ArrayList<>(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,7 +31,7 @@ public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isPinned() {
|
protected boolean isPinned() {
|
||||||
return pinnedTimelines.contains(makeTimelineDefinition());
|
return timelines.contains(makeTimelineDefinition());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updatePinButton(MenuItem pin) {
|
protected void updatePinButton(MenuItem pin) {
|
||||||
|
@ -57,11 +58,12 @@ public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||||
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
TimelineDefinition def = makeTimelineDefinition();
|
TimelineDefinition def = makeTimelineDefinition();
|
||||||
boolean pinned = isPinned();
|
boolean pinned = isPinned();
|
||||||
if (pinned) pinnedTimelines.remove(def);
|
if (pinned) timelines.remove(def);
|
||||||
else pinnedTimelines.add(def);
|
else timelines.add(def);
|
||||||
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
|
||||||
GlobalUserPreferences.pinnedTimelines.put(accountID, pinnedTimelines);
|
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||||
GlobalUserPreferences.save();
|
prefs.timelines=new ArrayList<>(timelines);
|
||||||
|
prefs.save();
|
||||||
updatePinButton(pin);
|
updatePinButton(pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import android.os.Bundle;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ public class PinnedPostsListFragment extends StatusListFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.ACCOUNT;
|
return FilterContext.ACCOUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,6 +13,7 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
@ -43,16 +44,15 @@ import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareFragment{
|
public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareFragment{
|
||||||
private static final int MAX_FIELDS=4;
|
static final int MAX_FIELDS=Integer.MAX_VALUE;
|
||||||
|
|
||||||
public UsableRecyclerView list;
|
public UsableRecyclerView list;
|
||||||
private List<AccountField> fields=Collections.emptyList();
|
private List<AccountField> fields=Collections.emptyList();
|
||||||
private AboutAdapter adapter;
|
private AboutAdapter adapter;
|
||||||
private Paint dividerPaint=new Paint();
|
|
||||||
private boolean isInEditMode;
|
private boolean isInEditMode;
|
||||||
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
|
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
|
||||||
private RecyclerView.ViewHolder draggedViewHolder;
|
|
||||||
private ListImageLoaderWrapper imgLoader;
|
private ListImageLoaderWrapper imgLoader;
|
||||||
|
private boolean editDirty;
|
||||||
|
|
||||||
public void setFields(List<AccountField> fields){
|
public void setFields(List<AccountField> fields){
|
||||||
this.fields=fields;
|
this.fields=fields;
|
||||||
|
@ -74,27 +74,8 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null);
|
imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null);
|
||||||
list.setAdapter(adapter=new AboutAdapter());
|
list.setAdapter(adapter=new AboutAdapter());
|
||||||
int pad=V.dp(16);
|
list.setPadding(0, V.dp(16), 0, 0);
|
||||||
list.setPadding(pad, pad, pad, pad);
|
|
||||||
list.setClipToPadding(false);
|
list.setClipToPadding(false);
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
dividerPaint.setStrokeWidth(V.dp(1));
|
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
|
||||||
@Override
|
|
||||||
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
for(int i=0;i<parent.getChildCount();i++){
|
|
||||||
View item=parent.getChildAt(i);
|
|
||||||
int pos=parent.getChildAdapterPosition(item);
|
|
||||||
int draggedPos=draggedViewHolder==null ? -1 : draggedViewHolder.getAbsoluteAdapterPosition();
|
|
||||||
if(pos<adapter.getItemCount()-1 && pos!=draggedPos && pos!=draggedPos-1){
|
|
||||||
float y=item.getY()+item.getHeight();
|
|
||||||
dividerPaint.setAlpha(Math.round(255*item.getAlpha()));
|
|
||||||
c.drawLine(item.getLeft(), y, item.getRight(), y, dividerPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,12 +84,17 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
fields=editableFields;
|
fields=editableFields;
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
dragHelper.attachToRecyclerView(list);
|
dragHelper.attachToRecyclerView(list);
|
||||||
|
editDirty=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AccountField> getFields(){
|
public List<AccountField> getFields(){
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEditDirty(){
|
||||||
|
return editDirty;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
|
@ -183,36 +169,25 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class BaseViewHolder extends BindableViewHolder<AccountField>{
|
private abstract class BaseViewHolder extends BindableViewHolder<AccountField>{
|
||||||
protected ShapeDrawable background=new ShapeDrawable();
|
|
||||||
|
|
||||||
public BaseViewHolder(int layout){
|
public BaseViewHolder(int layout){
|
||||||
super(getActivity(), layout, list);
|
super(getActivity(), layout, list);
|
||||||
background.getPaint().setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
|
||||||
itemView.setBackground(background);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(AccountField item){
|
public void onBind(AccountField item){
|
||||||
boolean first=getAbsoluteAdapterPosition()==0, last=getAbsoluteAdapterPosition()==adapter.getItemCount()-1;
|
|
||||||
float radius=V.dp(10);
|
|
||||||
float[] rad=new float[8];
|
|
||||||
if(first)
|
|
||||||
rad[0]=rad[1]=rad[2]=rad[3]=radius;
|
|
||||||
if(last)
|
|
||||||
rad[4]=rad[5]=rad[6]=rad[7]=radius;
|
|
||||||
background.setShape(new RoundRectShape(rad, null, null));
|
|
||||||
itemView.invalidateOutline();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{
|
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{
|
||||||
private TextView title;
|
private final TextView title;
|
||||||
private LinkedTextView value;
|
private final LinkedTextView value;
|
||||||
|
// private final ImageView verifiedIcon;
|
||||||
|
|
||||||
public AboutViewHolder(){
|
public AboutViewHolder(){
|
||||||
super(R.layout.item_profile_about);
|
super(R.layout.item_profile_about);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
value=findViewById(R.id.value);
|
value=findViewById(R.id.value);
|
||||||
|
// verifiedIcon=findViewById(R.id.verified_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -220,20 +195,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
super.onBind(item);
|
super.onBind(item);
|
||||||
title.setText(item.parsedName);
|
title.setText(item.parsedName);
|
||||||
value.setText(item.parsedValue);
|
value.setText(item.parsedValue);
|
||||||
if(item.verifiedAt!=null){
|
// verifiedIcon.setVisibility(item.verifiedAt!=null ? View.VISIBLE : View.GONE);
|
||||||
background.getPaint().setColor(UiUtils.isDarkTheme() ? 0xFF49595a : 0xFFd7e3da);
|
|
||||||
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
|
|
||||||
value.setTextColor(textColor);
|
|
||||||
value.setLinkTextColor(textColor);
|
|
||||||
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_24_regular, getActivity().getTheme()).mutate();
|
|
||||||
check.setTint(textColor);
|
|
||||||
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
|
|
||||||
}else{
|
|
||||||
background.getPaint().setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
|
||||||
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
|
||||||
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
|
|
||||||
value.setCompoundDrawables(null, null, null, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -251,27 +213,38 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EditableAboutViewHolder extends BaseViewHolder{
|
private class EditableAboutViewHolder extends BaseViewHolder{
|
||||||
private EditText title;
|
private final EditText title;
|
||||||
private EditText value;
|
private final EditText value;
|
||||||
|
private boolean ignoreTextChange;
|
||||||
|
|
||||||
public EditableAboutViewHolder(){
|
public EditableAboutViewHolder(){
|
||||||
super(R.layout.item_profile_about_editable);
|
super(R.layout.onboarding_profile_field);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
value=findViewById(R.id.value);
|
value=findViewById(R.id.content);
|
||||||
findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
||||||
dragHelper.startDrag(this);
|
dragHelper.startDrag(this);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
title.addTextChangedListener(new SimpleTextWatcher(e->item.name=e.toString()));
|
title.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||||
value.addTextChangedListener(new SimpleTextWatcher(e->item.value=e.toString()));
|
item.name=e.toString();
|
||||||
findViewById(R.id.remove_row_btn).setOnClickListener(this::onRemoveRowClick);
|
if(!ignoreTextChange)
|
||||||
|
editDirty=true;
|
||||||
|
}));
|
||||||
|
value.addTextChangedListener(new SimpleTextWatcher(e->{
|
||||||
|
item.value=e.toString();
|
||||||
|
if(!ignoreTextChange)
|
||||||
|
editDirty=true;
|
||||||
|
}));
|
||||||
|
findViewById(R.id.delete).setOnClickListener(this::onRemoveRowClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(AccountField item){
|
public void onBind(AccountField item){
|
||||||
super.onBind(item);
|
super.onBind(item);
|
||||||
|
ignoreTextChange=true;
|
||||||
title.setText(item.name);
|
title.setText(item.name);
|
||||||
value.setText(item.value);
|
value.setText(item.value);
|
||||||
|
ignoreTextChange=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRemoveRowClick(View v){
|
private void onRemoveRowClick(View v){
|
||||||
|
@ -323,8 +296,8 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||||
((BindableViewHolder)viewHolder).rebind();
|
((BindableViewHolder<?>)viewHolder).rebind();
|
||||||
((BindableViewHolder)target).rebind();
|
((BindableViewHolder<?>)target).rebind();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +312,6 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
||||||
viewHolder.itemView.setTag(me.grishka.appkit.R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
viewHolder.itemView.setTag(me.grishka.appkit.R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||||
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
draggedViewHolder=viewHolder;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +319,6 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
|
||||||
super.clearView(recyclerView, viewHolder);
|
super.clearView(recyclerView, viewHolder);
|
||||||
viewHolder.itemView.animate().translationZ(0).setDuration(100).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
viewHolder.itemView.animate().translationZ(0).setDuration(100).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
draggedViewHolder=null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,50 +0,0 @@
|
||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract class RecyclerFragment<T> extends BaseRecyclerFragment<T> {
|
|
||||||
public RecyclerFragment(int perPage) {
|
|
||||||
super(perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RecyclerFragment(int layout, int perPage) {
|
|
||||||
super(layout, perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
if (refreshLayout != null) setRefreshLayoutColors(refreshLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setRefreshLayoutColors(SwipeRefreshLayout l) {
|
|
||||||
List<Integer> colors = new ArrayList<>(Arrays.asList(
|
|
||||||
R.color.primary_600,
|
|
||||||
R.color.red_primary_600,
|
|
||||||
R.color.green_primary_600,
|
|
||||||
R.color.blue_primary_600,
|
|
||||||
R.color.purple_600
|
|
||||||
));
|
|
||||||
int primary = UiUtils.getThemeColorRes(l.getContext(), R.attr.colorPrimary600);
|
|
||||||
if (!colors.contains(primary)) colors.add(0, primary);
|
|
||||||
int offset = colors.indexOf(primary);
|
|
||||||
int[] sorted = new int[colors.size()];
|
|
||||||
for (int i = 0; i < colors.size(); i++) {
|
|
||||||
sorted[i] = colors.get((i + offset) % colors.size());
|
|
||||||
}
|
|
||||||
l.setColorSchemeResources(sorted);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -80,7 +80,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null);
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,20 +7,27 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.catalog.CatalogDefaultInstance;
|
||||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
|
@ -37,11 +44,16 @@ public class SplashFragment extends AppKitFragment{
|
||||||
private View artContainer, blueFill, greenFill;
|
private View artContainer, blueFill, greenFill;
|
||||||
private InterpolatingMotionEffect motionEffect;
|
private InterpolatingMotionEffect motionEffect;
|
||||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
||||||
|
private ProgressBarButton defaultServerButton;
|
||||||
|
private ProgressBar defaultServerProgress;
|
||||||
|
private String chosenDefaultServer=DEFAULT_SERVER;
|
||||||
|
private boolean loadingDefaultServer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||||
|
loadAndChooseDefaultServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -50,9 +62,14 @@ public class SplashFragment extends AppKitFragment{
|
||||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||||
Button joinDefault=contentView.findViewById(R.id.btn_join_default_server);
|
defaultServerButton=contentView.findViewById(R.id.btn_join_default_server);
|
||||||
joinDefault.setText(getString(R.string.join_default_server, DEFAULT_SERVER));
|
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));
|
||||||
joinDefault.setOnClickListener(this::onJoinDefaultServerClick);
|
defaultServerButton.setOnClickListener(this::onJoinDefaultServerClick);
|
||||||
|
defaultServerProgress=contentView.findViewById(R.id.action_progress);
|
||||||
|
if(loadingDefaultServer){
|
||||||
|
defaultServerButton.setTextVisible(false);
|
||||||
|
defaultServerProgress.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
contentView.findViewById(R.id.btn_learn_more).setOnClickListener(this::onLearnMoreClick);
|
contentView.findViewById(R.id.btn_learn_more).setOnClickListener(this::onLearnMoreClick);
|
||||||
|
|
||||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
artClouds=contentView.findViewById(R.id.art_clouds);
|
||||||
|
@ -96,12 +113,22 @@ public class SplashFragment extends AppKitFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onJoinDefaultServerClick(View v){
|
private void onJoinDefaultServerClick(View v){
|
||||||
|
if(loadingDefaultServer)
|
||||||
|
return;
|
||||||
new GetInstance()
|
new GetInstance()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance result){
|
public void onSuccess(Instance result){
|
||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
return;
|
return;
|
||||||
|
if(!result.registrations){
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.instance_signup_closed)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putParcelable("instance", Parcels.wrap(result));
|
args.putParcelable("instance", Parcels.wrap(result));
|
||||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
|
@ -115,7 +142,7 @@ public class SplashFragment extends AppKitFragment{
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||||
.execNoAuth(DEFAULT_SERVER);
|
.execNoAuth(chosenDefaultServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onLearnMoreClick(View v){
|
private void onLearnMoreClick(View v){
|
||||||
|
@ -168,4 +195,54 @@ public class SplashFragment extends AppKitFragment{
|
||||||
super.onHidden();
|
super.onHidden();
|
||||||
motionEffect.deactivate();
|
motionEffect.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadAndChooseDefaultServer(){
|
||||||
|
loadingDefaultServer=true;
|
||||||
|
new GetCatalogDefaultInstances()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<CatalogDefaultInstance> result){
|
||||||
|
if(result.isEmpty()){
|
||||||
|
setChosenDefaultServer(DEFAULT_SERVER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float sum=0f;
|
||||||
|
for(CatalogDefaultInstance inst:result){
|
||||||
|
sum+=inst.weight;
|
||||||
|
}
|
||||||
|
if(sum<=0)
|
||||||
|
sum=1f;
|
||||||
|
for(CatalogDefaultInstance inst:result){
|
||||||
|
inst.weight/=sum;
|
||||||
|
}
|
||||||
|
float rand=ThreadLocalRandom.current().nextFloat();
|
||||||
|
float prev=0f;
|
||||||
|
for(CatalogDefaultInstance inst:result){
|
||||||
|
if(rand>=prev && rand<prev+inst.weight){
|
||||||
|
setChosenDefaultServer(inst.domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prev+=inst.weight;
|
||||||
|
}
|
||||||
|
// Just in case something didn't add up
|
||||||
|
setChosenDefaultServer(result.get(result.size()-1).domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
setChosenDefaultServer(DEFAULT_SERVER);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execNoAuth("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChosenDefaultServer(String domain){
|
||||||
|
chosenDefaultServer=domain;
|
||||||
|
loadingDefaultServer=false;
|
||||||
|
if(defaultServerButton!=null && getActivity()!=null){
|
||||||
|
defaultServerButton.setTextVisible(true);
|
||||||
|
defaultServerProgress.setVisibility(View.GONE);
|
||||||
|
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
@ -57,7 +57,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, null);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET);
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
|
@ -160,7 +160,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.assist.AssistContent;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
@ -8,13 +7,14 @@ import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
|
@ -35,10 +35,10 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
||||||
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, getFilterContext());
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), addFooter ? 0 : StatusDisplayItem.FLAG_NO_FOOTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Filter.FilterContext getFilterContext();
|
protected abstract FilterContext getFilterContext();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addAccountToKnown(Status s){
|
protected void addAccountToKnown(Status s){
|
||||||
|
@ -65,6 +65,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
Status status=getContentStatusByID(id);
|
Status status=getContentStatusByID(id);
|
||||||
if(status==null)
|
if(status==null)
|
||||||
return;
|
return;
|
||||||
|
Status parentStatus = getStatusByID(id);
|
||||||
|
if (parentStatus != status) {
|
||||||
|
status.spoilerRevealed = parentStatus.spoilerRevealed;
|
||||||
|
status.sensitiveRevealed = parentStatus.sensitiveRevealed;
|
||||||
|
}
|
||||||
status.filterRevealed = true;
|
status.filterRevealed = true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
|
@ -74,26 +79,26 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){}
|
protected void onStatusCreated(Status status){}
|
||||||
|
|
||||||
protected void onStatusUpdated(StatusUpdatedEvent ev){
|
protected void onStatusUpdated(Status status){
|
||||||
ArrayList<Status> statusesForDisplayItems=new ArrayList<>();
|
ArrayList<Status> statusesForDisplayItems=new ArrayList<>();
|
||||||
for(int i=0;i<data.size();i++){
|
for(int i=0;i<data.size();i++){
|
||||||
Status s=data.get(i);
|
Status s=data.get(i);
|
||||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
if(s.reblog!=null && s.reblog.id.equals(status.id)){
|
||||||
s.reblog=ev.status;
|
s.reblog=status.clone();
|
||||||
statusesForDisplayItems.add(s);
|
statusesForDisplayItems.add(s);
|
||||||
}else if(s.id.equals(ev.status.id)){
|
}else if(s.id.equals(status.id)){
|
||||||
data.set(i, ev.status);
|
data.set(i, status);
|
||||||
statusesForDisplayItems.add(ev.status);
|
statusesForDisplayItems.add(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(int i=0;i<preloadedData.size();i++){
|
for(int i=0;i<preloadedData.size();i++){
|
||||||
Status s=preloadedData.get(i);
|
Status s=preloadedData.get(i);
|
||||||
if(s.reblog!=null && s.reblog.id.equals(ev.status.id)){
|
if(s.reblog!=null && s.reblog.id.equals(status.id)){
|
||||||
s.reblog=ev.status;
|
s.reblog=status.clone();
|
||||||
}else if(s.id.equals(ev.status.id)){
|
}else if(s.id.equals(status.id)){
|
||||||
preloadedData.set(i, ev.status);
|
preloadedData.set(i, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +216,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
for(Status s:data){
|
for(Status s:data){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
s.getContentStatus().update(ev);
|
s.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
||||||
|
@ -224,6 +230,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
for(Status s:preloadedData){
|
for(Status s:preloadedData){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
s.getContentStatus().update(ev);
|
s.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,12 +249,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
if(!ev.accountID.equals(accountID))
|
if(!ev.accountID.equals(accountID))
|
||||||
return;
|
return;
|
||||||
StatusListFragment.this.onStatusCreated(ev);
|
StatusListFragment.this.onStatusCreated(ev.status.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onStatusUpdated(StatusUpdatedEvent ev){
|
public void onStatusUpdated(StatusUpdatedEvent ev){
|
||||||
StatusListFragment.this.onStatusUpdated(ev);
|
StatusListFragment.this.onStatusUpdated(ev.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -257,7 +264,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||||
for(Status status:data){
|
for(Status status:data){
|
||||||
Status contentStatus=status.getContentStatus();
|
Status contentStatus=status.getContentStatus();
|
||||||
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
||||||
updatePoll(status.id, status, ev.poll);
|
updatePoll(status.id, contentStatus, ev.poll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusContext;
|
import org.joinmastodon.android.model.StatusContext;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
@ -152,6 +152,26 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void restoreStatusStates(List<Status> newData, Map<String, Status> oldData) {
|
||||||
|
for (Status s : newData) {
|
||||||
|
if (s == mainStatus) continue;
|
||||||
|
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||||
|
// restore previous spoiler/filter revealed states when refreshing
|
||||||
|
if (oldStatus != null) {
|
||||||
|
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||||
|
s.sensitiveRevealed = oldStatus.sensitiveRevealed;
|
||||||
|
s.filterRevealed = oldStatus.filterRevealed;
|
||||||
|
}
|
||||||
|
if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||||
|
s.spoilerText != null &&
|
||||||
|
s.spoilerText.equals(mainStatus.spoilerText)) {
|
||||||
|
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||||
|
s.spoilerRevealed = mainStatus.spoilerRevealed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
protected void maybeApplyContext() {
|
protected void maybeApplyContext() {
|
||||||
if (!transitionFinished || result == null || getContext() == null) return;
|
if (!transitionFinished || result == null || getContext() == null) return;
|
||||||
Map<String, Status> oldData = null;
|
Map<String, Status> oldData = null;
|
||||||
|
@ -170,6 +190,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
|
|
||||||
result.descendants=filterStatuses(result.descendants);
|
result.descendants=filterStatuses(result.descendants);
|
||||||
result.ancestors=filterStatuses(result.ancestors);
|
result.ancestors=filterStatuses(result.ancestors);
|
||||||
|
restoreStatusStates(result.descendants, oldData);
|
||||||
|
restoreStatusStates(result.ancestors, oldData);
|
||||||
|
|
||||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||||
ancestryMap.put(i.status.id, i);
|
ancestryMap.put(i.status.id, i);
|
||||||
|
@ -178,8 +200,10 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
if(footerProgress!=null)
|
if(footerProgress!=null)
|
||||||
footerProgress.setVisibility(View.GONE);
|
footerProgress.setVisibility(View.GONE);
|
||||||
data.addAll(result.descendants);
|
data.addAll(result.descendants);
|
||||||
|
|
||||||
int prevCount=displayItems.size();
|
int prevCount=displayItems.size();
|
||||||
onAppendItems(result.descendants);
|
onAppendItems(result.descendants);
|
||||||
|
|
||||||
int count=displayItems.size();
|
int count=displayItems.size();
|
||||||
if(!refreshing)
|
if(!refreshing)
|
||||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||||
|
@ -190,22 +214,6 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
count--;
|
count--;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Status s : data) {
|
|
||||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
|
||||||
// restore previous spoiler/filter revealed states when refreshing
|
|
||||||
if (oldStatus != null) {
|
|
||||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
|
||||||
s.filterRevealed = oldStatus.filterRevealed;
|
|
||||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
|
||||||
s.spoilerText != null &&
|
|
||||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
|
||||||
mainStatus.spoilerRevealed) {
|
|
||||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
|
||||||
s.spoilerRevealed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataLoaded();
|
dataLoaded();
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
refreshDone();
|
refreshDone();
|
||||||
|
@ -222,13 +230,13 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
|
|
||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object maybeApplyMainStatus() {
|
protected Object maybeApplyMainStatus() {
|
||||||
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||||
|
|
||||||
// restore revealed states for main status because it gets updated after doLoadData
|
// restore revealed states for main status because it gets updated after doLoadData
|
||||||
updatedStatus.filterRevealed = mainStatus.filterRevealed;
|
updatedStatus.filterRevealed = mainStatus.filterRevealed;
|
||||||
updatedStatus.spoilerRevealed = mainStatus.spoilerRevealed;
|
updatedStatus.spoilerRevealed = mainStatus.spoilerRevealed;
|
||||||
|
updatedStatus.sensitiveRevealed = mainStatus.sensitiveRevealed;
|
||||||
|
|
||||||
// returning fired event object to facilitate testing
|
// returning fired event object to facilitate testing
|
||||||
Object event;
|
Object event;
|
||||||
|
@ -352,9 +360,9 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
protected void onStatusCreated(Status status){
|
||||||
if (ev.status.inReplyToId == null) return;
|
if (status.inReplyToId == null) return;
|
||||||
Status repliedToStatus = getStatusByID(ev.status.inReplyToId);
|
Status repliedToStatus = getStatusByID(status.inReplyToId);
|
||||||
if (repliedToStatus == null) return;
|
if (repliedToStatus == null) return;
|
||||||
NeighborAncestryInfo ancestry = ancestryMap.get(repliedToStatus.id);
|
NeighborAncestryInfo ancestry = ancestryMap.get(repliedToStatus.id);
|
||||||
|
|
||||||
|
@ -400,12 +408,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
}
|
}
|
||||||
|
|
||||||
// update replied-to status' ancestry
|
// update replied-to status' ancestry
|
||||||
if (ancestry != null) ancestry.descendantNeighbor = ev.status;
|
if (ancestry != null) ancestry.descendantNeighbor = status;
|
||||||
|
|
||||||
// add ancestry for newly created status before building its display items
|
// add ancestry for newly created status before building its display items
|
||||||
ancestryMap.put(ev.status.id, new NeighborAncestryInfo(ev.status, null, repliedToStatus));
|
ancestryMap.put(status.id, new NeighborAncestryInfo(status, null, repliedToStatus));
|
||||||
displayItems.addAll(nextDisplayItemsIndex, buildDisplayItems(ev.status));
|
displayItems.addAll(nextDisplayItemsIndex, buildDisplayItems(status));
|
||||||
data.add(nextDataIndex, ev.status);
|
data.add(nextDataIndex, status);
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,8 +434,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.THREAD;
|
return FilterContext.THREAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,44 +1,22 @@
|
||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.drawable.Animatable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.fragments.HasAccountID;
|
|
||||||
import org.joinmastodon.android.fragments.ListsFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -48,21 +26,16 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> implements ProvidesAssistContent.ProvidesWebUri {
|
public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<AccountViewModel> implements ProvidesAssistContent.ProvidesWebUri {
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||||
|
@ -71,6 +44,10 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
super(40);
|
super(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BaseAccountListFragment(int layout, int perPage){
|
||||||
|
super(layout, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -78,7 +55,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDataLoaded(List<AccountItem> d, boolean more){
|
protected void onDataLoaded(List<AccountViewModel> d, boolean more){
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
relationships.clear();
|
relationships.clear();
|
||||||
}
|
}
|
||||||
|
@ -95,7 +72,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
super.onRefresh();
|
super.onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadRelationships(List<AccountItem> accounts){
|
protected void loadRelationships(List<AccountViewModel> accounts){
|
||||||
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
|
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
|
||||||
GetAccountRelationships req=new GetAccountRelationships(ids);
|
GetAccountRelationships req=new GetAccountRelationships(ids);
|
||||||
relationshipsRequests.add(req);
|
relationshipsRequests.add(req);
|
||||||
|
@ -132,10 +109,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
// list.setPadding(0, V.dp(16), 0, V.dp(16));
|
|
||||||
list.setClipToPadding(false);
|
list.setClipToPadding(false);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1,
|
|
||||||
Math.round(16f + 56f * getResources().getConfiguration().fontScale), 16));
|
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +142,8 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
list.setPadding(0, V.dp(16), 0, V.dp(16)+insets.getSystemWindowInsetBottom());
|
list.setPadding(0, V.dp(16), 0, V.dp(16)+insets.getSystemWindowInsetBottom());
|
||||||
|
emptyView.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||||
|
progress.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||||
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||||
}else{
|
}else{
|
||||||
list.setPadding(0, V.dp(16), 0, V.dp(16));
|
list.setPadding(0, V.dp(16), 0, V.dp(16));
|
||||||
|
@ -185,6 +161,8 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){}
|
||||||
|
|
||||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public AccountsAdapter(){
|
public AccountsAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
@ -193,7 +171,9 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
return new AccountViewHolder();
|
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships);
|
||||||
|
onConfigureViewHolder(holder);
|
||||||
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -214,225 +194,8 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
AccountItem item=data.get(position);
|
AccountViewModel item=data.get(position);
|
||||||
return image==0 ? item.avaRequest : item.emojiHelper.getImageRequest(image-1);
|
return image==0 ? item.avaRequest : item.emojiHelper.getImageRequest(image-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class AccountViewHolder extends BindableViewHolder<AccountItem> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
|
|
||||||
private final TextView name, username;
|
|
||||||
private final ImageView avatar;
|
|
||||||
private final Button button;
|
|
||||||
private final PopupMenu contextMenu;
|
|
||||||
private final View menuAnchor;
|
|
||||||
|
|
||||||
public AccountViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_account_list, list);
|
|
||||||
name=findViewById(R.id.name);
|
|
||||||
username=findViewById(R.id.username);
|
|
||||||
avatar=findViewById(R.id.avatar);
|
|
||||||
button=findViewById(R.id.button);
|
|
||||||
menuAnchor=findViewById(R.id.menu_anchor);
|
|
||||||
|
|
||||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
|
||||||
avatar.setClipToOutline(true);
|
|
||||||
|
|
||||||
button.setOnClickListener(this::onButtonClick);
|
|
||||||
|
|
||||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
|
||||||
contextMenu.inflate(R.menu.profile);
|
|
||||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
|
||||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
@Override
|
|
||||||
public void onBind(AccountItem item){
|
|
||||||
name.setText(item.parsedName);
|
|
||||||
username.setText("@"+ (item.account.isRemote
|
|
||||||
? item.account.getFullyQualifiedName()
|
|
||||||
: item.account.acct));
|
|
||||||
bindRelationship();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bindRelationship(){
|
|
||||||
Relationship rel=relationships.get(item.account.id);
|
|
||||||
if(rel==null || item.account.isRemote || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
|
||||||
button.setVisibility(View.GONE);
|
|
||||||
}else{
|
|
||||||
button.setVisibility(View.VISIBLE);
|
|
||||||
UiUtils.setRelationshipToActionButton(rel, button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setImage(int index, Drawable image){
|
|
||||||
if(index==0){
|
|
||||||
avatar.setImageDrawable(image);
|
|
||||||
}else{
|
|
||||||
item.emojiHelper.setImageDrawable(index-1, image);
|
|
||||||
name.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(image instanceof Animatable a && !a.isRunning())
|
|
||||||
a.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearImage(int index){
|
|
||||||
setImage(index, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
if (item.account.isRemote) args.putParcelable("remoteAccount", Parcels.wrap(item.account));
|
|
||||||
else args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(float x, float y){
|
|
||||||
Relationship relationship=relationships.get(item.account.id);
|
|
||||||
if(relationship==null)
|
|
||||||
return false;
|
|
||||||
Menu menu=contextMenu.getMenu();
|
|
||||||
Account account=item.account;
|
|
||||||
|
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
|
||||||
|
|
||||||
MenuItem mute = menu.findItem(R.id.mute);
|
|
||||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
|
||||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
|
||||||
|
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
|
||||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
|
||||||
if(relationship.following){
|
|
||||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
|
||||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
|
||||||
hideBoosts.setVisible(true);
|
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
|
||||||
|
|
||||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
|
||||||
manageUserLists.setVisible(true);
|
|
||||||
}else{
|
|
||||||
hideBoosts.setVisible(false);
|
|
||||||
manageUserLists.setVisible(false);
|
|
||||||
}
|
|
||||||
menu.findItem(R.id.block_domain).setVisible(false);
|
|
||||||
|
|
||||||
menuAnchor.setTranslationX(x);
|
|
||||||
menuAnchor.setTranslationY(y);
|
|
||||||
contextMenu.show();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onButtonClick(View v){
|
|
||||||
ProgressDialog progress=new ProgressDialog(getActivity());
|
|
||||||
progress.setMessage(getString(R.string.loading));
|
|
||||||
progress.setCancelable(false);
|
|
||||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationships.get(item.account.id), button, progressShown->{
|
|
||||||
itemView.setHasTransientState(progressShown);
|
|
||||||
if(progressShown)
|
|
||||||
progress.show();
|
|
||||||
else
|
|
||||||
progress.dismiss();
|
|
||||||
}, result->{
|
|
||||||
relationships.put(item.account.id, result);
|
|
||||||
bindRelationship();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onContextMenuItemSelected(MenuItem item){
|
|
||||||
Relationship relationship=relationships.get(this.item.account.id);
|
|
||||||
if(relationship==null)
|
|
||||||
return false;
|
|
||||||
Account account=this.item.account;
|
|
||||||
|
|
||||||
int id=item.getItemId();
|
|
||||||
if(id==R.id.share){
|
|
||||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
|
||||||
intent.setType("text/plain");
|
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
|
||||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
|
||||||
}else if(id==R.id.mute){
|
|
||||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
|
||||||
}else if(id==R.id.block){
|
|
||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
|
||||||
}else if(id==R.id.soft_block){
|
|
||||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
|
||||||
}else if(id==R.id.report){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("reportAccount", Parcels.wrap(account));
|
|
||||||
Nav.go(getActivity(), ReportReasonChoiceFragment.class, args);
|
|
||||||
}else if(id==R.id.open_in_browser){
|
|
||||||
UiUtils.launchWebBrowser(getActivity(), account.url);
|
|
||||||
}else if(id==R.id.block_domain){
|
|
||||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
|
||||||
relationship.domainBlocking=!relationship.domainBlocking;
|
|
||||||
bindRelationship();
|
|
||||||
});
|
|
||||||
}else if(id==R.id.hide_boosts){
|
|
||||||
new SetAccountFollowed(account.id, true, !relationship.showingReblogs, relationship.notifying)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Relationship result){
|
|
||||||
relationships.put(AccountViewHolder.this.item.account.id, result);
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
bindRelationship();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.wrapProgress(getActivity(), R.string.loading, false)
|
|
||||||
.exec(accountID);
|
|
||||||
}else if(id==R.id.manage_user_lists){
|
|
||||||
final Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putString("profileAccount", account.id);
|
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
|
||||||
Nav.go(getActivity(), ListsFragment.class, args);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRelationship(Relationship r){
|
|
||||||
relationships.put(item.account.id, r);
|
|
||||||
bindRelationship();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class AccountItem{
|
|
||||||
public final Account account;
|
|
||||||
public final ImageLoaderRequest avaRequest;
|
|
||||||
public final CustomEmojiHelper emojiHelper;
|
|
||||||
public final CharSequence parsedName;
|
|
||||||
|
|
||||||
public AccountItem(Account account){
|
|
||||||
this.account=account;
|
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
|
||||||
emojiHelper=new CustomEmojiHelper();
|
|
||||||
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
return obj instanceof AccountItem i && i.account.url.equals(account.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
|
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
|
public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||||
|
private String currentQuery;
|
||||||
|
private boolean resultDelivered;
|
||||||
|
private SearchViewHelper searchViewHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setRefreshEnabled(false);
|
||||||
|
setEmptyText("");
|
||||||
|
dataLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_hint));
|
||||||
|
searchViewHelper.setListeners(this::onQueryChanged, null);
|
||||||
|
searchViewHelper.addDivider(contentView);
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
view.setBackgroundResource(R.drawable.bg_m3_surface3);
|
||||||
|
int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Surface, R.attr.colorM3Primary, 0.11f);
|
||||||
|
setStatusBarColor(color);
|
||||||
|
setNavigationBarColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
refreshing=true;
|
||||||
|
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults result){
|
||||||
|
setEmptyText(R.string.no_search_results);
|
||||||
|
onDataLoaded(result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
searchViewHelper.install(getToolbar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsElevationOnScrollEffect(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||||
|
super.onConfigureViewHolder(holder);
|
||||||
|
holder.setOnClickListener(this::onItemClick);
|
||||||
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onItemClick(AccountViewHolder holder){
|
||||||
|
if(resultDelivered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
resultDelivered=true;
|
||||||
|
Bundle res=new Bundle();
|
||||||
|
res.putParcelable("selectedAccount", Parcels.wrap(holder.getItem().account));
|
||||||
|
setResult(true, res);
|
||||||
|
Nav.finish(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onQueryChanged(String q){
|
||||||
|
currentQuery=q;
|
||||||
|
if(currentRequest!=null){
|
||||||
|
currentRequest.cancel();
|
||||||
|
currentRequest=null;
|
||||||
|
}
|
||||||
|
if(!TextUtils.isEmpty(currentQuery))
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -125,17 +126,17 @@ public abstract class PaginatedAccountListFragment<T> extends BaseAccountListFra
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
boolean justRefreshed = !doneWithHomeInstance && offset == 0;
|
boolean justRefreshed = !doneWithHomeInstance && offset == 0;
|
||||||
Collection<AccountItem> d = justRefreshed ? List.of() : data;
|
Collection<AccountViewModel> d = justRefreshed ? List.of() : data;
|
||||||
|
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
nextMaxID=null;
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
List<AccountItem> items = result.stream()
|
List<AccountViewModel> items = result.stream()
|
||||||
.filter(a -> d.size() > 1000 || d.stream()
|
.filter(a -> d.size() > 1000 || d.stream()
|
||||||
.noneMatch(i -> i.account.url.equals(a.url)))
|
.noneMatch(i -> i.account.url.equals(a.url)))
|
||||||
.map(AccountItem::new)
|
.map(a->new AccountViewModel(a, accountID))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
boolean hasMore = nextMaxID != null;
|
boolean hasMore = nextMaxID != null;
|
||||||
|
|
|
@ -2,12 +2,12 @@ package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
@ -16,20 +16,27 @@ import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
public class BubbleTimelineFragment extends StatusListFragment {
|
public class BubbleTimelineFragment extends StatusListFragment {
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.BUBBLE_TIMELINE);
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.BUBBLE_TIMELINE, accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count)
|
currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
@ -44,14 +51,16 @@ public class BubbleTimelineFragment extends StatusListFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return FilterContext.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,8 +17,8 @@ import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FollowSuggestion;
|
import org.joinmastodon.android.model.FollowSuggestion;
|
||||||
|
@ -51,7 +51,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
@ -242,7 +242,7 @@ public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsF
|
||||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
||||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
|
@ -252,7 +252,7 @@ public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsF
|
||||||
actionWrap.setVisibility(View.GONE);
|
actionWrap.setVisibility(View.GONE);
|
||||||
}else{
|
}else{
|
||||||
actionWrap.setVisibility(View.VISIBLE);
|
actionWrap.setVisibility(View.VISIBLE);
|
||||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,64 +1,58 @@
|
||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop, ProvidesAssistContent {
|
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop {
|
||||||
|
private static final int QUERY_RESULT=937;
|
||||||
|
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
private EditText searchEdit;
|
|
||||||
private boolean searchActive;
|
private boolean searchActive;
|
||||||
private FrameLayout searchView;
|
private FrameLayout searchView;
|
||||||
private ImageButton searchBack, searchClear;
|
private ImageButton searchBack;
|
||||||
private ProgressBar searchProgress;
|
private TextView searchText;
|
||||||
|
private View tabsDivider;
|
||||||
|
|
||||||
private DiscoverPostsFragment postsFragment;
|
private DiscoverPostsFragment postsFragment;
|
||||||
private DiscoverHashtagsFragment hashtagsFragment;
|
private TrendingHashtagsFragment hashtagsFragment;
|
||||||
private DiscoverNewsFragment newsFragment;
|
private DiscoverNewsFragment newsFragment;
|
||||||
private DiscoverAccountsFragment accountsFragment;
|
private DiscoverAccountsFragment accountsFragment;
|
||||||
private SearchFragment searchFragment;
|
private SearchFragment searchFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private String currentQuery;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -92,12 +86,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
tabViews[i]=tabView;
|
tabViews[i]=tabView;
|
||||||
}
|
}
|
||||||
|
|
||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextSize(V.dp(14));
|
||||||
|
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,7 +112,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
postsFragment=new DiscoverPostsFragment();
|
postsFragment=new DiscoverPostsFragment();
|
||||||
postsFragment.setArguments(args);
|
postsFragment.setArguments(args);
|
||||||
|
|
||||||
hashtagsFragment=new DiscoverHashtagsFragment();
|
hashtagsFragment=new TrendingHashtagsFragment();
|
||||||
hashtagsFragment.setArguments(args);
|
hashtagsFragment.setArguments(args);
|
||||||
|
|
||||||
newsFragment=new DiscoverNewsFragment();
|
newsFragment=new DiscoverNewsFragment();
|
||||||
|
@ -129,10 +121,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
accountsFragment=new DiscoverAccountsFragment();
|
accountsFragment=new DiscoverAccountsFragment();
|
||||||
accountsFragment.setArguments(args);
|
accountsFragment.setArguments(args);
|
||||||
|
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.discover_posts, postsFragment)
|
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
|
.add(R.id.discover_posts, postsFragment)
|
||||||
.add(R.id.discover_news, newsFragment)
|
.add(R.id.discover_news, newsFragment)
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
@ -148,7 +139,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
case 3 -> R.string.for_you;
|
case 3 -> R.string.for_you;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tabLayoutMediator.attach();
|
tabLayoutMediator.attach();
|
||||||
|
@ -165,62 +155,52 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
searchEdit=view.findViewById(R.id.search_edit);
|
|
||||||
searchEdit.setOnFocusChangeListener(this::onSearchEditFocusChanged);
|
|
||||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
|
||||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
|
||||||
if(s.length()==0){
|
|
||||||
V.setVisibilityAnimated(searchClear, View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
|
||||||
searchEdit.postDelayed(searchDebouncer, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s){
|
|
||||||
if(s.length()==0){
|
|
||||||
V.setVisibilityAnimated(searchClear, View.INVISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
searchView=view.findViewById(R.id.search_fragment);
|
searchView=view.findViewById(R.id.search_fragment);
|
||||||
if(searchFragment==null){
|
if(searchFragment==null){
|
||||||
searchFragment=new SearchFragment();
|
searchFragment=new SearchFragment();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
searchFragment.setArguments(args);
|
searchFragment.setArguments(args);
|
||||||
searchFragment.setProgressVisibilityListener(this::onSearchProgressVisibilityChanged);
|
|
||||||
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
|
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBack=view.findViewById(R.id.search_back);
|
searchBack=view.findViewById(R.id.search_back);
|
||||||
searchClear=view.findViewById(R.id.search_clear);
|
searchText=view.findViewById(R.id.search_text);
|
||||||
searchProgress=view.findViewById(R.id.search_progress);
|
|
||||||
searchBack.setEnabled(searchActive);
|
|
||||||
searchBack.setImportantForAccessibility(searchActive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
searchBack.setImportantForAccessibility(searchActive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
searchBack.setOnClickListener(v->exitSearch());
|
searchBack.setOnClickListener(v->{
|
||||||
|
if(searchActive) exitSearch(); else openSearch();
|
||||||
|
});
|
||||||
if(searchActive){
|
if(searchActive){
|
||||||
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
||||||
pager.setVisibility(View.GONE);
|
pager.setVisibility(View.GONE);
|
||||||
tabLayout.setVisibility(View.GONE);
|
tabLayout.setVisibility(View.GONE);
|
||||||
searchView.setVisibility(View.VISIBLE);
|
searchView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
searchClear.setOnClickListener(v->{
|
|
||||||
searchEdit.setText("");
|
View searchWrap=view.findViewById(R.id.search_wrap);
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
searchWrap.setOutlineProvider(OutlineProviders.roundedRect(28));
|
||||||
onSearchChangedDebounced();
|
searchWrap.setClipToOutline(true);
|
||||||
});
|
searchText.setOnClickListener(v->openSearch());
|
||||||
|
tabsDivider=view.findViewById(R.id.tabs_divider);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return searchActive ? searchFragment.isOnTop()
|
||||||
|
: ((IsOnTop)getFragmentForPage(pager.getCurrentItem())).isOnTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openSearch() {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
if(!TextUtils.isEmpty(currentQuery)){
|
||||||
|
args.putString("query", currentQuery);
|
||||||
|
}
|
||||||
|
Nav.goForResult(getActivity(), SearchQueryFragment.class, args, QUERY_RESULT, DiscoverFragment.this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
if(!searchActive){
|
if(!searchActive){
|
||||||
|
@ -230,30 +210,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return searchActive ? searchFragment.isOnTop()
|
|
||||||
: ((IsOnTop)getFragmentForPage(pager.getCurrentItem())).isOnTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSelect() {
|
|
||||||
if (isOnTop()) selectSearch();
|
|
||||||
else scrollToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectSearch() {
|
|
||||||
searchEdit.requestFocus();
|
|
||||||
onSearchEditFocusChanged(searchEdit, true);
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
||||||
hashtagsFragment.loadData();
|
postsFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
private void enterSearch(){
|
||||||
if(!searchActive && hasFocus){
|
if(!searchActive){
|
||||||
searchActive=true;
|
searchActive=true;
|
||||||
pager.setVisibility(View.GONE);
|
pager.setVisibility(View.GONE);
|
||||||
tabLayout.setVisibility(View.GONE);
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
@ -261,28 +224,23 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
||||||
searchBack.setEnabled(true);
|
searchBack.setEnabled(true);
|
||||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||||
|
tabsDivider.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitSearch(){
|
private void exitSearch(){
|
||||||
|
if(!searchActive)
|
||||||
|
return;
|
||||||
searchActive=false;
|
searchActive=false;
|
||||||
pager.setVisibility(View.VISIBLE);
|
pager.setVisibility(View.VISIBLE);
|
||||||
tabLayout.setVisibility(View.VISIBLE);
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
searchView.setVisibility(View.GONE);
|
searchView.setVisibility(View.GONE);
|
||||||
searchEdit.clearFocus();
|
searchText.setText(R.string.search_mastodon);
|
||||||
searchEdit.setText("");
|
|
||||||
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
|
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
|
||||||
searchBack.setEnabled(false);
|
searchBack.setEnabled(false);
|
||||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
tabsDivider.setVisibility(View.VISIBLE);
|
||||||
if (getArguments().getBoolean("disableDiscover"))
|
currentQuery=null;
|
||||||
((HomeFragment) getParentFragment()).onBackPressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHidden(){
|
|
||||||
super.onHidden();
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
|
@ -304,30 +262,20 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchChangedDebounced(){
|
|
||||||
searchFragment.setQuery(searchEdit.getText().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
|
||||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
|
||||||
return true;
|
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
|
||||||
onSearchChangedDebounced();
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSearchProgressVisibilityChanged(boolean visible){
|
|
||||||
V.setVisibilityAnimated(searchProgress, visible ? View.VISIBLE : View.INVISIBLE);
|
|
||||||
if(searchEdit.length()>0)
|
|
||||||
V.setVisibilityAnimated(searchClear, visible ? View.INVISIBLE : View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProvideAssistContent(AssistContent assistContent) {
|
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||||
callFragmentToProvideAssistContent(searchActive
|
if(reqCode==QUERY_RESULT && success){
|
||||||
? searchFragment
|
enterSearch();
|
||||||
: getFragmentForPage(pager.getCurrentItem()), assistContent);
|
currentQuery=result.getString("query");
|
||||||
|
SearchResult.Type type;
|
||||||
|
if(result.containsKey("filter")){
|
||||||
|
type=SearchResult.Type.values()[result.getInt("filter")];
|
||||||
|
}else{
|
||||||
|
type=null;
|
||||||
|
}
|
||||||
|
searchFragment.setQuery(currentQuery, type);
|
||||||
|
searchText.setText(currentQuery);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -11,36 +11,45 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
|
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
|
||||||
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
|
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverNewsFragment extends RecyclerFragment<Card> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
private MergeRecyclerAdapter mergeAdapter;
|
||||||
|
private UsableRecyclerView cardsList;
|
||||||
|
private ArrayList<CardViewModel> top3=new ArrayList<>();
|
||||||
|
private CardLinksAdapter cardsAdapter;
|
||||||
|
|
||||||
public DiscoverNewsFragment(){
|
public DiscoverNewsFragment(){
|
||||||
super(10);
|
super(10);
|
||||||
|
@ -50,6 +59,7 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,11 +68,14 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Card> result){
|
public void onSuccess(List<Card> result){
|
||||||
imageRequests=result.stream()
|
top3.clear();
|
||||||
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
top3.addAll(result.subList(0, Math.min(3, result.size())).stream().map(card->new CardViewModel(card, 280, 140)).collect(Collectors.toList()));
|
||||||
.collect(Collectors.toList());
|
cardsAdapter.notifyDataSetChanged();
|
||||||
if (getActivity() == null) return;
|
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result.subList(top3.size(), result.size()).stream()
|
||||||
|
.map(card->new CardViewModel(card, 56, 56))
|
||||||
|
.collect(Collectors.toList()), false);
|
||||||
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
@ -70,14 +83,27 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
return new LinksAdapter();
|
cardsList=new UsableRecyclerView(getActivity());
|
||||||
}
|
cardsList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
||||||
|
ListImageLoaderWrapper cardsImageLoader=new ListImageLoaderWrapper(getActivity(), cardsList, new RecyclerViewDelegate(cardsList), this);
|
||||||
|
cardsList.setAdapter(cardsAdapter=new CardLinksAdapter(cardsImageLoader, top3));
|
||||||
|
cardsList.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(256)));
|
||||||
|
cardsList.setPadding(V.dp(16), V.dp(8), 0, 0);
|
||||||
|
cardsList.setClipToPadding(false);
|
||||||
|
cardsList.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
outRect.right=V.dp(16);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 0, 0));
|
}
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
});
|
||||||
|
cardsList.setSelector(R.drawable.bg_rect_12dp_ripple);
|
||||||
|
cardsList.setDrawSelectorOnTop(true);
|
||||||
|
|
||||||
|
mergeAdapter=new MergeRecyclerAdapter();
|
||||||
|
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
||||||
|
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(cardsList));
|
||||||
|
mergeAdapter.addAdapter(new LinksAdapter(imgLoader, data));
|
||||||
|
return mergeAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -85,29 +111,17 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private class LinksAdapter extends UsableRecyclerView.Adapter<BaseLinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public boolean isOnTop() {
|
private final List<CardViewModel> data;
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public LinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||||
public String getAccountID() {
|
|
||||||
return accountID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
|
||||||
return isInstanceAkkoma() ? null : base.path("/explore/links").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
|
||||||
public LinksAdapter(){
|
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
this.data=data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public LinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
return new LinkViewHolder();
|
return new LinkViewHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,46 +131,51 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(LinkViewHolder holder, int position){
|
public void onBindViewHolder(BaseLinkViewHolder holder, int position){
|
||||||
holder.bind(data.get(position));
|
holder.bind(data.get(position).card);
|
||||||
super.onBindViewHolder(holder, position);
|
super.onBindViewHolder(holder, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageCountForItem(int position){
|
public int getImageCountForItem(int position){
|
||||||
return imageRequests.get(position)==null ? 0 : 1;
|
return data.get(position).imageRequest==null ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
return imageRequests.get(position);
|
return data.get(position).imageRequest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
private class CardLinksAdapter extends LinksAdapter{
|
||||||
private final TextView name, title, subtitle;
|
public CardLinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
||||||
private final ImageView photo;
|
super(imgLoader, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new LinkCardViewHolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BaseLinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
||||||
|
protected final TextView name, title;
|
||||||
|
protected final ImageView photo;
|
||||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||||
private boolean didClear;
|
private boolean didClear;
|
||||||
|
|
||||||
public LinkViewHolder(){
|
public BaseLinkViewHolder(int layout){
|
||||||
super(getActivity(), R.layout.item_trending_link, list);
|
super(getActivity(), layout, list);
|
||||||
name=findViewById(R.id.name);
|
name=findViewById(R.id.name);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
subtitle=findViewById(R.id.subtitle);
|
|
||||||
photo=findViewById(R.id.photo);
|
photo=findViewById(R.id.photo);
|
||||||
photo.setOutlineProvider(OutlineProviders.roundedRect(2));
|
|
||||||
photo.setClipToOutline(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Card item){
|
public void onBind(Card item){
|
||||||
name.setText(item.providerName);
|
name.setText(item.providerName);
|
||||||
title.setText(item.title);
|
title.setText(item.title);
|
||||||
int num=item.history.get(0).uses;
|
|
||||||
if(item.history.size()>1)
|
|
||||||
num+=item.history.get(1).uses;
|
|
||||||
subtitle.setText(getResources().getQuantityString(R.plurals.discussed_x_times, num, num));
|
|
||||||
crossfadeDrawable.setSize(item.width, item.height);
|
crossfadeDrawable.setSize(item.width, item.height);
|
||||||
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
||||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||||
|
@ -183,4 +202,20 @@ public class DiscoverNewsFragment extends RecyclerFragment<Card> implements Scro
|
||||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
UiUtils.launchWebBrowser(getActivity(), item.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LinkViewHolder extends BaseLinkViewHolder{
|
||||||
|
public LinkViewHolder(){
|
||||||
|
super(R.layout.item_trending_link);
|
||||||
|
photo.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||||
|
photo.setClipToOutline(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinkCardViewHolder extends BaseLinkViewHolder{
|
||||||
|
public LinkCardViewHolder(){
|
||||||
|
super(R.layout.item_trending_link_card);
|
||||||
|
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||||
|
itemView.setClipToOutline(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,27 @@ package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment {
|
public class DiscoverPostsFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS, accountID);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
|
@ -25,26 +30,27 @@ public class DiscoverPostsFragment extends StatusListFragment {
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return FilterContext.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base){
|
||||||
return isInstanceAkkoma() ? null : base.path("/explore/posts").build();
|
return isInstanceAkkoma() ? null : base.path("/explore/posts").build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,55 +2,59 @@ package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
|
public class FederatedTimelineFragment extends StatusListFragment{
|
||||||
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
|
||||||
public class FederatedTimelineFragment extends StatusListFragment {
|
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
public void onCreate(Bundle savedInstanceState){
|
||||||
return true;
|
super.onCreate(savedInstanceState);
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE, accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
|
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
if (getActivity() == null) return;
|
boolean empty=result.isEmpty();
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !empty);
|
||||||
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return FilterContext.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,54 +7,59 @@ import android.view.View;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
|
public class LocalTimelineFragment extends StatusListFragment{
|
||||||
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
|
||||||
public class LocalTimelineFragment extends StatusListFragment {
|
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
public void onCreate(Bundle savedInstanceState){
|
||||||
return true;
|
super.onCreate(savedInstanceState);
|
||||||
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE, accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
if (getActivity() == null) return;
|
boolean empty=result.isEmpty();
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !empty);
|
||||||
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(list, adapter);
|
||||||
|
adapter.addAdapter(super.getAdapter());
|
||||||
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Filter.FilterContext getFilterContext() {
|
protected FilterContext getFilterContext() {
|
||||||
return Filter.FilterContext.PUBLIC;
|
return FilterContext.PUBLIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base){
|
||||||
return base.path(isInstanceAkkoma() ? "/main/public" : "/public/local").build();
|
return base.path(isInstanceAkkoma() ? "/main/public" : "/public/local").build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.app.Activity;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
@ -24,7 +23,6 @@ import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
@ -35,24 +33,18 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
private List<StatusDisplayItem> prevDisplayItems;
|
private List<StatusDisplayItem> prevDisplayItems;
|
||||||
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||||
private List<SearchResult> unfilteredResults=Collections.emptyList();
|
private List<SearchResult> unfilteredResults=Collections.emptyList();
|
||||||
private HideableSingleViewRecyclerAdapter headerAdapter;
|
|
||||||
private ProgressVisibilityListener progressVisibilityListener;
|
|
||||||
private InputMethodManager imm;
|
private InputMethodManager imm;
|
||||||
|
|
||||||
private TabLayout tabLayout;
|
|
||||||
|
|
||||||
public SearchFragment(){
|
public SearchFragment(){
|
||||||
setLayout(R.layout.fragment_search);
|
setLayout(R.layout.fragment_search);
|
||||||
}
|
}
|
||||||
|
@ -62,8 +54,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
setEmptyText(R.string.no_search_results);
|
||||||
loadData();
|
loadData();
|
||||||
resetEmptyText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,16 +64,12 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
imm=activity.getSystemService(InputMethodManager.class);
|
imm=activity.getSystemService(InputMethodManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetEmptyText() {
|
|
||||||
setEmptyText(R.string.sk_recent_searches_placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
||||||
return switch(s.type){
|
return switch(s.type){
|
||||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.PUBLIC);
|
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, FilterContext.PUBLIC, 0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,24 +113,24 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
if (getActivity() == null) return;
|
GetSearchResults.Type type;
|
||||||
resetEmptyText();
|
if(currentFilter.size()==1){
|
||||||
if(isInRecentMode()){
|
type=switch(currentFilter.iterator().next()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
case ACCOUNT -> GetSearchResults.Type.ACCOUNTS;
|
||||||
if(getActivity()==null)
|
case HASHTAG -> GetSearchResults.Type.HASHTAGS;
|
||||||
return;
|
case STATUS -> GetSearchResults.Type.STATUSES;
|
||||||
unfilteredResults=sr;
|
};
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
|
||||||
onDataLoaded(sr, false);
|
|
||||||
});
|
|
||||||
}else{
|
}else{
|
||||||
setEmptyText(R.string.sk_searching);
|
type=null;
|
||||||
progressVisibilityListener.onProgressVisibilityChanged(true);
|
}
|
||||||
currentRequest=new GetSearchResults(currentQuery, null, true)
|
if(currentQuery==null){
|
||||||
|
dataLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentRequest=new GetSearchResults(currentQuery, type, true)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
setEmptyText(R.string.sk_no_results);
|
|
||||||
ArrayList<SearchResult> results=new ArrayList<>();
|
ArrayList<SearchResult> results=new ArrayList<>();
|
||||||
if(result.accounts!=null){
|
if(result.accounts!=null){
|
||||||
for(Account acc:result.accounts)
|
for(Account acc:result.accounts)
|
||||||
|
@ -158,25 +146,20 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
}
|
}
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
unfilteredResults=results;
|
unfilteredResults=results;
|
||||||
if (getActivity() == null) return;
|
|
||||||
onDataLoaded(filterSearchResults(results), false);
|
onDataLoaded(filterSearchResults(results), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
resetEmptyText();
|
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
Activity a=getActivity();
|
Activity a=getActivity();
|
||||||
if(a==null)
|
if(a==null)
|
||||||
return;
|
return;
|
||||||
error.showToast(a);
|
error.showToast(a);
|
||||||
if(progressVisibilityListener!=null)
|
|
||||||
progressVisibilityListener.onProgressVisibilityChanged(false);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateList(){
|
public void updateList(){
|
||||||
|
@ -185,86 +168,30 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
|
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
|
||||||
boolean recent=isInRecentMode() && !displayItems.isEmpty();
|
|
||||||
if(recent!=headerAdapter.isVisible())
|
|
||||||
headerAdapter.setVisible(recent);
|
|
||||||
imgLoader.forceUpdateImages();
|
imgLoader.forceUpdateImages();
|
||||||
prevDisplayItems=null;
|
prevDisplayItems=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setQuery(String q, SearchResult.Type filter){
|
||||||
protected void onDataLoaded(List<SearchResult> d, boolean more){
|
if(q.isBlank())
|
||||||
super.onDataLoaded(d, more);
|
|
||||||
if(progressVisibilityListener!=null)
|
|
||||||
progressVisibilityListener.onProgressVisibilityChanged(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
tabLayout=view.findViewById(R.id.tabbar);
|
|
||||||
tabLayout.setTabTextSize(V.dp(16));
|
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
|
||||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.search_all));
|
|
||||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.search_people));
|
|
||||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.hashtags));
|
|
||||||
tabLayout.addTab(tabLayout.newTab().setText(R.string.posts));
|
|
||||||
for(int i=0;i<tabLayout.getTabCount();i++){
|
|
||||||
tabLayout.getTabAt(i).view.textView.setAllCaps(true);
|
|
||||||
}
|
|
||||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
|
||||||
@Override
|
|
||||||
public void onTabSelected(TabLayout.Tab tab){
|
|
||||||
setFilter(switch(tab.getPosition()){
|
|
||||||
case 0 -> EnumSet.allOf(SearchResult.Type.class);
|
|
||||||
case 1 -> EnumSet.of(SearchResult.Type.ACCOUNT);
|
|
||||||
case 2 -> EnumSet.of(SearchResult.Type.HASHTAG);
|
|
||||||
case 3 -> EnumSet.of(SearchResult.Type.STATUS);
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+tab.getPosition());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabUnselected(TabLayout.Tab tab){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabReselected(TabLayout.Tab tab){
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
|
||||||
View header=getActivity().getLayoutInflater().inflate(R.layout.item_recent_searches_header, list, false);
|
|
||||||
header.findViewById(R.id.clear).setOnClickListener(this::onClearRecentClick);
|
|
||||||
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
|
||||||
adapter.addAdapter(headerAdapter=new HideableSingleViewRecyclerAdapter(header));
|
|
||||||
adapter.addAdapter(super.getAdapter());
|
|
||||||
headerAdapter.setVisible(isInRecentMode());
|
|
||||||
return adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuery(String q){
|
|
||||||
if(Objects.equals(q, currentQuery) || q.isBlank())
|
|
||||||
return;
|
return;
|
||||||
if(currentRequest!=null){
|
if(currentRequest!=null){
|
||||||
currentRequest.cancel();
|
currentRequest.cancel();
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
}
|
}
|
||||||
currentQuery=q;
|
currentQuery=q;
|
||||||
|
if(filter==null)
|
||||||
|
currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||||
|
else
|
||||||
|
currentFilter=EnumSet.of(filter);
|
||||||
refreshing=true;
|
refreshing=true;
|
||||||
doLoadData(0, 0);
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFilter(EnumSet<SearchResult.Type> filter){
|
private void setFilter(EnumSet<SearchResult.Type> filter){
|
||||||
if(filter.equals(currentFilter))
|
if(filter.equals(currentFilter))
|
||||||
return;
|
return;
|
||||||
currentFilter=filter;
|
currentFilter=filter;
|
||||||
if(isInRecentMode())
|
|
||||||
return;
|
|
||||||
// This can be optimized by not rebuilding display items every time filter is changed, but I'm too lazy
|
// This can be optimized by not rebuilding display items every time filter is changed, but I'm too lazy
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
refreshing=true;
|
refreshing=true;
|
||||||
|
@ -286,23 +213,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClearRecentClick(View v){
|
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().clearRecentSearches();
|
|
||||||
if(isInRecentMode()){
|
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
|
||||||
refreshing=true;
|
|
||||||
onDataLoaded(unfilteredResults=Collections.emptyList(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInRecentMode(){
|
|
||||||
return TextUtils.isEmpty(currentQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgressVisibilityListener(ProgressVisibilityListener progressVisibilityListener){
|
|
||||||
this.progressVisibilityListener=progressVisibilityListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScrollStarted(){
|
public void onScrollStarted(){
|
||||||
super.onScrollStarted();
|
super.onScrollStarted();
|
||||||
|
|
|
@ -0,0 +1,574 @@
|
||||||
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.RoundedCorner;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.MainActivity;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||||
|
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||||
|
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.CustomTransitionsFragment;
|
||||||
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultViewModel> implements CustomTransitionsFragment, OnBackPressedListener{
|
||||||
|
private static final Pattern HASHTAG_REGEX=Pattern.compile("^(\\w*[a-zA-Z·]\\w*)$", Pattern.CASE_INSENSITIVE);
|
||||||
|
private static final Pattern USERNAME_REGEX=Pattern.compile("^@?([a-z0-9_-]+)(@[^\\s]+)?$", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||||
|
private HideableSingleViewRecyclerAdapter recentsHeader;
|
||||||
|
private ListItem<Void> openUrlItem, goToHashtagItem, goToAccountItem, goToStatusSearchItem, goToAccountSearchItem;
|
||||||
|
private ArrayList<ListItem<Void>> topOptions=new ArrayList<>();
|
||||||
|
private GenericListItemsAdapter<Void> topOptionsAdapter;
|
||||||
|
|
||||||
|
private String accountID;
|
||||||
|
private SearchViewHelper searchViewHelper;
|
||||||
|
private String currentQuery;
|
||||||
|
private LayerDrawable navigationIcon;
|
||||||
|
private Drawable searchIcon, backIcon;
|
||||||
|
|
||||||
|
public SearchQueryFragment(){
|
||||||
|
super(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
setRefreshEnabled(false);
|
||||||
|
setEmptyText("");
|
||||||
|
|
||||||
|
openUrlItem=new ListItem<>(R.string.search_open_url, 0, R.drawable.ic_fluent_link_24_regular, this::onOpenURLClick);
|
||||||
|
goToHashtagItem=new ListItem<>("", null, R.drawable.ic_fluent_number_symbol_24_regular, this::onGoToHashtagClick);
|
||||||
|
goToAccountItem=new ListItem<>("", null, R.drawable.ic_fluent_person_24_regular, this::onGoToAccountClick);
|
||||||
|
goToStatusSearchItem=new ListItem<>("", null, R.drawable.ic_fluent_search_24_regular, this::onGoToStatusSearchClick);
|
||||||
|
goToAccountSearchItem=new ListItem<>("", null, R.drawable.ic_fluent_people_24_regular, this::onGoToAccountSearchClick);
|
||||||
|
currentQuery=getArguments().getString("query");
|
||||||
|
|
||||||
|
dataLoaded();
|
||||||
|
doLoadData(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
if(isInRecentMode()){
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(results->{
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
onDataLoaded(results.stream().map(sr->{
|
||||||
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
||||||
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
|
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||||
|
}
|
||||||
|
return vm;
|
||||||
|
}).collect(Collectors.toList()), false);
|
||||||
|
recentsHeader.setVisible(!data.isEmpty());
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
currentRequest=new GetSearchResults(currentQuery, null, false)
|
||||||
|
.limit(2)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults result){
|
||||||
|
onDataLoaded(Stream.of(result.hashtags.stream().map(SearchResult::new), result.accounts.stream().map(SearchResult::new))
|
||||||
|
.flatMap(Function.identity())
|
||||||
|
.map(sr->{
|
||||||
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
||||||
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
|
vm.hashtagItem.onClick=()->openHashtag(sr);
|
||||||
|
}
|
||||||
|
return vm;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList()), false);
|
||||||
|
recentsHeader.setVisible(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
|
View header=getActivity().getLayoutInflater().inflate(R.layout.display_item_section_header, list, false);
|
||||||
|
TextView title=header.findViewById(R.id.title);
|
||||||
|
Button action=header.findViewById(R.id.action_btn);
|
||||||
|
title.setText(R.string.recent_searches);
|
||||||
|
action.setText(R.string.clear_all);
|
||||||
|
action.setOnClickListener(v->onClearRecentClick());
|
||||||
|
recentsHeader=new HideableSingleViewRecyclerAdapter(header);
|
||||||
|
|
||||||
|
mergeAdapter.addAdapter(recentsHeader);
|
||||||
|
mergeAdapter.addAdapter(topOptionsAdapter=new GenericListItemsAdapter<>(topOptions));
|
||||||
|
mergeAdapter.addAdapter(new SearchResultsAdapter());
|
||||||
|
return mergeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_mastodon));
|
||||||
|
searchViewHelper.setListeners(this::onQueryChanged, this::onQueryChangedNoDebounce);
|
||||||
|
searchViewHelper.addDivider(contentView);
|
||||||
|
searchViewHelper.setEnterCallback(this::onSearchViewEnter);
|
||||||
|
|
||||||
|
navigationIcon=new LayerDrawable(new Drawable[]{
|
||||||
|
searchIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_fluent_search_24_regular, getToolbarContext().getTheme()).mutate(),
|
||||||
|
backIcon=getToolbarContext().getResources().getDrawable(R.drawable.ic_fluent_arrow_left_24_regular, getToolbarContext().getTheme()).mutate()
|
||||||
|
}){
|
||||||
|
@Override
|
||||||
|
public Drawable mutate(){
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Surface, R.attr.colorM3Primary, 0.11f);
|
||||||
|
view.setBackgroundColor(color);
|
||||||
|
setStatusBarColor(color);
|
||||||
|
setNavigationBarColor(color);
|
||||||
|
if(currentQuery!=null){
|
||||||
|
searchViewHelper.setQuery(currentQuery);
|
||||||
|
searchIcon.setAlpha(0);
|
||||||
|
}
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 1, 0, 0, vh->!isInRecentMode() && vh.getAbsoluteAdapterPosition()==topOptions.size()-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
((ViewGroup.MarginLayoutParams)getToolbar().getLayoutParams()).topMargin=V.dp(8);
|
||||||
|
searchViewHelper.install(getToolbar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsElevationOnScrollEffect(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onQueryChanged(String q){
|
||||||
|
currentQuery=q;
|
||||||
|
if(currentRequest!=null){
|
||||||
|
currentRequest.cancel();
|
||||||
|
currentRequest=null;
|
||||||
|
}
|
||||||
|
refreshing=true;
|
||||||
|
doLoadData(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onQueryChangedNoDebounce(String q){
|
||||||
|
updateTopOptions(q);
|
||||||
|
if(!TextUtils.isEmpty(q)){
|
||||||
|
recentsHeader.setVisible(false);
|
||||||
|
}
|
||||||
|
data.clear();
|
||||||
|
mergeAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTopOptions(String q){
|
||||||
|
topOptions.clear();
|
||||||
|
// https://github.com/mastodon/mastodon/blob/a985d587e13494b78ef2879e4d97f78a2df693db/app/javascript/mastodon/features/compose/components/search.jsx#L233
|
||||||
|
String trimmedValue=q.trim();
|
||||||
|
if(trimmedValue.length()>0){
|
||||||
|
boolean couldBeURL=trimmedValue.startsWith("https://") && !trimmedValue.contains(" ");
|
||||||
|
if(couldBeURL){
|
||||||
|
topOptions.add(openUrlItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean couldBeHashtag=(trimmedValue.startsWith("#") && trimmedValue.length()>1 && !trimmedValue.contains(" ")) || HASHTAG_REGEX.matcher(trimmedValue).find();
|
||||||
|
if(couldBeHashtag){
|
||||||
|
String tag=trimmedValue.startsWith("#") ? trimmedValue.substring(1) : trimmedValue;
|
||||||
|
goToHashtagItem.title=getString(R.string.posts_matching_hashtag, "#"+tag);
|
||||||
|
topOptions.add(goToHashtagItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher usernameMatcher=USERNAME_REGEX.matcher(trimmedValue);
|
||||||
|
if(usernameMatcher.find()){
|
||||||
|
String username="@"+usernameMatcher.group(1);
|
||||||
|
String atDomain=usernameMatcher.group(2);
|
||||||
|
if(atDomain==null){
|
||||||
|
username+="@"+AccountSessionManager.get(accountID).domain;
|
||||||
|
}
|
||||||
|
goToAccountItem.title=getString(R.string.search_go_to_account, username);
|
||||||
|
topOptions.add(goToAccountItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
goToStatusSearchItem.title=getString(R.string.posts_matching_string, trimmedValue);
|
||||||
|
topOptions.add(goToStatusSearchItem);
|
||||||
|
goToAccountSearchItem.title=getString(R.string.accounts_matching_string, trimmedValue);
|
||||||
|
topOptions.add(goToAccountSearchItem);
|
||||||
|
}
|
||||||
|
topOptionsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Animator onCreateEnterTransition(View prev, View container){
|
||||||
|
return createTransition(prev, container, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Animator onCreateExitTransition(View prev, View container){
|
||||||
|
return createTransition(prev, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsCustomNavigationIcon(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Drawable getNavigationIconDrawable(){
|
||||||
|
return navigationIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchViewHelper.getSearchEdit(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHidden(){
|
||||||
|
super.onHidden();
|
||||||
|
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(getActivity().getWindow().getDecorView().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||||
|
private float getScreenCornerRadius(WindowInsets insets, int pos){
|
||||||
|
RoundedCorner corner=insets.getRoundedCorner(pos);
|
||||||
|
if(corner==null)
|
||||||
|
return 0;
|
||||||
|
return corner.getRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Animator createTransition(View prev, View container, boolean enter){
|
||||||
|
int[] loc={0, 0};
|
||||||
|
View searchBtn=prev.findViewById(R.id.search_wrap);
|
||||||
|
searchBtn.getLocationInWindow(loc);
|
||||||
|
int btnLeft=loc[0], btnTop=loc[1];
|
||||||
|
container.getLocationInWindow(loc);
|
||||||
|
int offX=btnLeft-loc[0], offY=btnTop-loc[1];
|
||||||
|
|
||||||
|
float screenRadius;
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S){
|
||||||
|
WindowInsets insets=container.getRootWindowInsets();
|
||||||
|
screenRadius=Math.min(
|
||||||
|
Math.min(getScreenCornerRadius(insets, RoundedCorner.POSITION_TOP_LEFT), getScreenCornerRadius(insets, RoundedCorner.POSITION_TOP_RIGHT)),
|
||||||
|
Math.min(getScreenCornerRadius(insets, RoundedCorner.POSITION_BOTTOM_LEFT), getScreenCornerRadius(insets, RoundedCorner.POSITION_BOTTOM_RIGHT))
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
screenRadius=0;
|
||||||
|
}
|
||||||
|
float buttonRadius=V.dp(26);
|
||||||
|
|
||||||
|
Rect buttonBounds=new Rect(offX, offY, offX+searchBtn.getWidth(), offY+searchBtn.getHeight());
|
||||||
|
Rect containerBounds=new Rect(0, 0, container.getWidth(), container.getHeight());
|
||||||
|
AnimatableOutlineProvider outlineProvider=new AnimatableOutlineProvider(enter ? buttonBounds : containerBounds, enter ? containerBounds : buttonBounds, enter ? buttonRadius : screenRadius);
|
||||||
|
container.setOutlineProvider(outlineProvider);
|
||||||
|
container.setClipToOutline(true);
|
||||||
|
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
ObjectAnimator boundsAnim;
|
||||||
|
|
||||||
|
Toolbar toolbar=getToolbar();
|
||||||
|
float toolbarTX=offX-toolbar.getX();
|
||||||
|
float toolbarTY=offY-toolbar.getY()+(searchBtn.getHeight()-toolbar.getHeight())/2f;
|
||||||
|
ArrayList<Animator> anims=new ArrayList<>();
|
||||||
|
anims.add(boundsAnim=ObjectAnimator.ofFloat(outlineProvider, "boundsFraction", 0f, 1f));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(outlineProvider, "radius", enter ? buttonRadius : screenRadius, enter ? screenRadius : buttonRadius));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(toolbar, View.TRANSLATION_X, enter ? toolbarTX : 0, enter ? 0 : toolbarTX));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(toolbar, View.TRANSLATION_Y, enter ? toolbarTY : 0, enter ? 0 : toolbarTY));
|
||||||
|
anims.add(ObjectAnimator.ofFloat(searchViewHelper.getDivider(), View.ALPHA, enter ? 0 : 1, enter ? 1 : 0));
|
||||||
|
View parentContent=prev.findViewById(R.id.discover_content);
|
||||||
|
View parentContentParent=(View) parentContent.getParent();
|
||||||
|
parentContentParent.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Surface));
|
||||||
|
if(enter){
|
||||||
|
anims.add(ObjectAnimator.ofFloat(contentWrap, View.TRANSLATION_Y, V.dp(-16), 0));
|
||||||
|
}else{
|
||||||
|
}
|
||||||
|
anims.add(ObjectAnimator.ofFloat(contentWrap, View.ALPHA, enter ? 0 : 1, enter ? 1 : 0));
|
||||||
|
for(Animator anim:anims){
|
||||||
|
anim.setDuration(enter ? 700 : 300);
|
||||||
|
}
|
||||||
|
if(TextUtils.isEmpty(currentQuery)){
|
||||||
|
anims.add(ObjectAnimator.ofInt(searchIcon, "alpha", enter ? 255 : 0, enter ? 0 : 255).setDuration(200));
|
||||||
|
anims.add(ObjectAnimator.ofInt(backIcon, "alpha", enter ? 0 : 255, enter ? 255 : 0).setDuration(200));
|
||||||
|
}
|
||||||
|
ObjectAnimator parentContentFade;
|
||||||
|
anims.add(parentContentFade=ObjectAnimator.ofFloat(parentContent, View.ALPHA, enter ? 1 : 0, enter ? 0 : 1).setDuration(enter ? 350 : 250));
|
||||||
|
if(!enter){
|
||||||
|
parentContentFade.setStartDelay(50);
|
||||||
|
ObjectAnimator parentContentTY;
|
||||||
|
anims.add(parentContentTY=ObjectAnimator.ofFloat(parentContent, View.TRANSLATION_Y, V.dp(16), 0).setDuration(250));
|
||||||
|
parentContentTY.setStartDelay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
set.playTogether(anims);
|
||||||
|
set.setInterpolator(AnimationUtils.loadInterpolator(getActivity(), R.interpolator.m3_sys_motion_easing_emphasized_decelerate));
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
container.setOutlineProvider(null);
|
||||||
|
container.setClipToOutline(false);
|
||||||
|
parentContentParent.setBackground(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
boundsAnim.addUpdateListener(animation->{
|
||||||
|
container.invalidateOutline();
|
||||||
|
navigationIcon.invalidateSelf();
|
||||||
|
});
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openHashtag(SearchResult res){
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInRecentMode(){
|
||||||
|
return TextUtils.isEmpty(currentQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSearchViewEnter(){
|
||||||
|
deliverResult(currentQuery, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onOpenURLClick(){
|
||||||
|
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onGoToHashtagClick(){
|
||||||
|
String q=searchViewHelper.getQuery();
|
||||||
|
if(q.startsWith("#"))
|
||||||
|
q=q.substring(1);
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onGoToAccountClick(){
|
||||||
|
String q=searchViewHelper.getQuery();
|
||||||
|
if(!q.startsWith("@")){
|
||||||
|
q="@"+q;
|
||||||
|
}
|
||||||
|
if(q.lastIndexOf('@')==0){
|
||||||
|
q+="@"+AccountSessionManager.get(accountID).domain;
|
||||||
|
}
|
||||||
|
UiUtils.lookupAccountHandle(getContext(), accountID, q, (clazz, args) -> {
|
||||||
|
if (!args.containsKey("profileAccount")) {
|
||||||
|
Toast.makeText(getContext(), R.string.no_search_results, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Nav.go((Activity) getContext(), ProfileFragment.class, args);
|
||||||
|
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onGoToStatusSearchClick(){
|
||||||
|
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onGoToAccountSearchClick(){
|
||||||
|
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClearRecentClick(){
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().clearRecentSearches();
|
||||||
|
if(isInRecentMode()){
|
||||||
|
data.clear();
|
||||||
|
recentsHeader.setVisible(false);
|
||||||
|
mergeAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deliverResult(String query, SearchResult.Type typeFilter){
|
||||||
|
Bundle res=new Bundle();
|
||||||
|
res.putString("query", query);
|
||||||
|
if(typeFilter!=null)
|
||||||
|
res.putInt("filter", typeFilter.ordinal());
|
||||||
|
setResult(true, res);
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onBackPressed(){
|
||||||
|
String initialQuery=getArguments().getString("query");
|
||||||
|
searchViewHelper.setQuery(TextUtils.isEmpty(initialQuery) ? "" : initialQuery);
|
||||||
|
currentQuery=initialQuery;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AnimatableOutlineProvider extends ViewOutlineProvider{
|
||||||
|
private float boundsFraction, radius;
|
||||||
|
private final Rect boundsFrom, boundsTo;
|
||||||
|
|
||||||
|
private AnimatableOutlineProvider(Rect boundsFrom, Rect boundsTo, float radius){
|
||||||
|
this.boundsFrom=boundsFrom;
|
||||||
|
this.boundsTo=boundsTo;
|
||||||
|
this.radius=radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline){
|
||||||
|
outline.setRoundRect(
|
||||||
|
UiUtils.lerp(boundsFrom.left, boundsTo.left, boundsFraction),
|
||||||
|
UiUtils.lerp(boundsFrom.top, boundsTo.top, boundsFraction),
|
||||||
|
UiUtils.lerp(boundsFrom.right, boundsTo.right, boundsFraction),
|
||||||
|
UiUtils.lerp(boundsFrom.bottom, boundsTo.bottom, boundsFraction),
|
||||||
|
radius
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public float getBoundsFraction(){
|
||||||
|
return boundsFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public void setBoundsFraction(float boundsFraction){
|
||||||
|
this.boundsFraction=boundsFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public float getRadius(){
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public void setRadius(float radius){
|
||||||
|
this.radius=radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SearchResultsAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
public SearchResultsAdapter(){
|
||||||
|
super(imgLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
if(viewType==R.id.list_item_account){
|
||||||
|
return new CustomAccountViewHolder(SearchQueryFragment.this, parent, null);
|
||||||
|
}else if(viewType==R.id.list_item_simple){
|
||||||
|
return new SimpleListItemViewHolder(parent.getContext(), parent);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
|
||||||
|
if(holder instanceof CustomAccountViewHolder avh){
|
||||||
|
avh.bind(data.get(position).account);
|
||||||
|
avh.searchResult=data.get(position).result;
|
||||||
|
}else if(holder instanceof SimpleListItemViewHolder ivh){
|
||||||
|
ivh.bind(data.get(position).hashtagItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return switch(data.get(position).result.type){
|
||||||
|
case ACCOUNT -> R.id.list_item_account;
|
||||||
|
case HASHTAG -> R.id.list_item_simple;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+data.get(position).result.type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageCountForItem(int position){
|
||||||
|
SearchResultViewModel vm=data.get(position);
|
||||||
|
if(vm.account!=null)
|
||||||
|
return vm.account.emojiHelper.getImageCount()+1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
|
SearchResultViewModel vm=data.get(position);
|
||||||
|
if(vm.account!=null){
|
||||||
|
if(image==0)
|
||||||
|
return vm.account.avaRequest;
|
||||||
|
return vm.account.emojiHelper.getImageRequest(image-1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomAccountViewHolder extends AccountViewHolder{
|
||||||
|
public SearchResult searchResult;
|
||||||
|
|
||||||
|
public CustomAccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships){
|
||||||
|
super(fragment, list, relationships);
|
||||||
|
setStyle(AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
super.onClick();
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(searchResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,5 @@
|
||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withHistoryParams;
|
|
||||||
import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withoutHistoryParams;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -11,29 +7,26 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.HashtagChartView;
|
import org.joinmastodon.android.ui.views.HashtagChartView;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop, ProvidesAssistContent.ProvidesWebUri {
|
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
|
||||||
|
|
||||||
public DiscoverHashtagsFragment(){
|
public TrendingHashtagsFragment(){
|
||||||
super(10);
|
super(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +42,6 @@ public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Hashtag> result){
|
public void onSuccess(List<Hashtag> result){
|
||||||
if (getActivity() == null) return;
|
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -61,33 +53,11 @@ public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||||
return new HashtagsAdapter();
|
return new HashtagsAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, .5f, 16, 16));
|
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccountID() {
|
|
||||||
return accountID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
|
||||||
return isInstanceAkkoma() ? null : base.path("/explore/tags").build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,11 +93,9 @@ public class DiscoverHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
||||||
if (item.history == null || item.history.isEmpty()) {
|
if (item.history == null || item.history.isEmpty()) {
|
||||||
subtitle.setText(null);
|
subtitle.setText(null);
|
||||||
chart.setVisibility(View.GONE);
|
chart.setVisibility(View.GONE);
|
||||||
title.setLayoutParams(withoutHistoryParams);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chart.setVisibility(View.VISIBLE);
|
chart.setVisibility(View.VISIBLE);
|
||||||
title.setLayoutParams(withHistoryParams);
|
|
||||||
int numPeople=item.history.get(0).accounts;
|
int numPeople=item.history.get(0).accounts;
|
||||||
if(item.history.size()>1)
|
if(item.history.size()>1)
|
||||||
numPeople+=item.history.get(1).accounts;
|
numPeople+=item.history.get(1).accounts;
|
|
@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
@ -23,8 +22,7 @@ import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
@ -38,7 +36,6 @@ import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class AccountActivationFragment extends ToolbarFragment{
|
public class AccountActivationFragment extends ToolbarFragment{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
@ -50,6 +47,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
private APIRequest currentRequest;
|
private APIRequest currentRequest;
|
||||||
private Runnable resendTimer=this::updateResendTimer;
|
private Runnable resendTimer=this::updateResendTimer;
|
||||||
private long lastResendTime;
|
private long lastResendTime;
|
||||||
|
private boolean visible;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
@ -70,7 +68,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
openEmailBtn.setOnLongClickListener(v->{
|
openEmailBtn.setOnLongClickListener(v->{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
Nav.go(getActivity(), SettingsMainFragment.class, args);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
resendBtn=view.findViewById(R.id.btn_resend);
|
resendBtn=view.findViewById(R.id.btn_resend);
|
||||||
|
@ -108,24 +106,20 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(contentView, insets));
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
|
||||||
contentView.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
|
||||||
}else{
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
|
visible=true;
|
||||||
tryGetAccount();
|
tryGetAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onHidden(){
|
protected void onHidden(){
|
||||||
super.onHidden();
|
super.onHidden();
|
||||||
|
visible=false;
|
||||||
if(currentRequest!=null){
|
if(currentRequest!=null){
|
||||||
currentRequest.cancel();
|
currentRequest.cancel();
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
|
@ -238,6 +232,8 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void proceed(){
|
private void proceed(){
|
||||||
|
if(!visible)
|
||||||
|
return;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||||
|
|
|
@ -122,7 +122,7 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Surface));
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
((UsableRecyclerView) list).setSelector(null);
|
((UsableRecyclerView) list).setSelector(null);
|
||||||
}
|
}
|
||||||
|
@ -131,17 +131,15 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||||
protected void doLoadData(int offset, int count) {}
|
protected void doLoadData(int offset, int count) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter<?> getAdapter(){
|
||||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_welcome_custom, list, false);
|
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_welcome_custom, list, false);
|
||||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||||
|
|
||||||
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
||||||
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
|
((TextView) headerView.findViewById(R.id.time_and_username)).setText(R.string.sk_app_username);
|
||||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
||||||
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
||||||
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
||||||
|
@ -203,7 +201,6 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||||
|
|
||||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView title, description, userCount, lang;
|
private final TextView title, description, userCount, lang;
|
||||||
private final RadioButton radioButton;
|
|
||||||
|
|
||||||
public InstanceViewHolder(){
|
public InstanceViewHolder(){
|
||||||
super(getActivity(), R.layout.item_instance_custom, list);
|
super(getActivity(), R.layout.item_instance_custom, list);
|
||||||
|
@ -211,7 +208,6 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||||
description=findViewById(R.id.description);
|
description=findViewById(R.id.description);
|
||||||
userCount=findViewById(R.id.user_count);
|
userCount=findViewById(R.id.user_count);
|
||||||
lang=findViewById(R.id.lang);
|
lang=findViewById(R.id.lang);
|
||||||
radioButton=findViewById(R.id.radiobtn);
|
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
||||||
|
@ -231,22 +227,10 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
||||||
lang.setText(item.language.toUpperCase());
|
lang.setText(item.language.toUpperCase());
|
||||||
}
|
}
|
||||||
radioButton.setChecked(chosenInstance==item);
|
|
||||||
radioButton.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
if(chosenInstance!=null){
|
|
||||||
int idx=filteredData.indexOf(chosenInstance);
|
|
||||||
if(idx!=-1){
|
|
||||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
|
||||||
if(holder instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.radioButton.setChecked(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radioButton.setChecked(true);
|
|
||||||
if(chosenInstance==null)
|
if(chosenInstance==null)
|
||||||
nextButton.setEnabled(true);
|
nextButton.setEnabled(true);
|
||||||
chosenInstance=item;
|
chosenInstance=item;
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorM3Surface));
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
|
|
||||||
items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
||||||
|
@ -123,16 +123,12 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onUpdateToolbar(){
|
protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
super.onUpdateToolbar();
|
||||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
|
||||||
getToolbar().setElevation(0);
|
|
||||||
if(onScrollListener!=null){
|
if(onScrollListener!=null){
|
||||||
onScrollListener.setViews(buttonBar, getToolbar());
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
}
|
}
|
||||||
|
@ -155,13 +151,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
|
||||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
|
||||||
}else{
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadServerPrivacyPolicy(){
|
private void loadServerPrivacyPolicy(){
|
||||||
|
|
|
@ -17,10 +17,11 @@ import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
@ -52,7 +53,7 @@ import okhttp3.Call;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance> {
|
abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogInstance> {
|
||||||
protected RecyclerView.Adapter adapter;
|
protected RecyclerView.Adapter adapter;
|
||||||
protected MergeRecyclerAdapter mergeAdapter;
|
protected MergeRecyclerAdapter mergeAdapter;
|
||||||
protected CatalogInstance chosenInstance;
|
protected CatalogInstance chosenInstance;
|
||||||
|
@ -227,7 +228,7 @@ abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance>
|
||||||
}
|
}
|
||||||
loadingInstanceDomain=null;
|
loadingInstanceDomain=null;
|
||||||
showInstanceInfoLoadError(domain, error);
|
showInstanceInfoLoadError(domain, error);
|
||||||
if(fakeInstance!=null){
|
if(fakeInstance!=null && getActivity()!=null){
|
||||||
fakeInstance.description=getString(R.string.error);
|
fakeInstance.description=getString(R.string.error);
|
||||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||||
if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder<?> ivh){
|
if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder<?> ivh){
|
||||||
|
@ -330,13 +331,7 @@ abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
|
||||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
|
||||||
}else{
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue