Compare commits

..

No commits in common. "dev" and "songAdapterRefactor" have entirely different histories.

1397 changed files with 85020 additions and 66531 deletions

View file

@ -1,358 +0,0 @@
root = true
[*]
# charset = utf-8
# end_of_line = lf
# indent_size = 4
# indent_style = space
# insert_final_newline = false
# max_line_length = 100
# 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_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_resources = true
# ij_java_align_multiline_ternary_operation = false
# ij_java_align_multiline_text_blocks = false
# ij_java_align_multiline_throws_list = false
# ij_java_align_subsequent_simple_methods = false
# ij_java_align_throws_keyword = false
# ij_java_annotation_parameter_wrap = off
# ij_java_array_initializer_new_line_after_left_brace = false
# ij_java_array_initializer_right_brace_on_new_line = false
# ij_java_array_initializer_wrap = off
# ij_java_assert_statement_colon_on_next_line = false
# ij_java_assert_statement_wrap = off
# ij_java_assignment_wrap = off
# ij_java_binary_operation_sign_on_next_line = false
# ij_java_binary_operation_wrap = off
# ij_java_blank_lines_after_anonymous_class_header = 0
# ij_java_blank_lines_after_class_header = 0
# ij_java_blank_lines_after_imports = 1
# ij_java_blank_lines_after_package = 1
# ij_java_blank_lines_around_class = 1
# ij_java_blank_lines_around_field = 0
# ij_java_blank_lines_around_field_in_interface = 0
# ij_java_blank_lines_around_initializer = 1
# ij_java_blank_lines_around_method = 1
# ij_java_blank_lines_around_method_in_interface = 1
# ij_java_blank_lines_before_class_end = 0
# ij_java_blank_lines_before_imports = 1
# ij_java_blank_lines_before_method_body = 0
# ij_java_blank_lines_before_package = 0
# ij_java_block_brace_style = end_of_line
# ij_java_block_comment_at_first_column = true
# ij_java_call_parameters_new_line_after_left_paren = false
# ij_java_call_parameters_right_paren_on_new_line = false
# ij_java_call_parameters_wrap = off
# ij_java_case_statement_on_separate_line = true
# ij_java_catch_on_new_line = false
# ij_java_class_annotation_wrap = split_into_lines
# ij_java_class_brace_style = end_of_line
# ij_java_class_count_to_use_import_on_demand = 99
# ij_java_class_names_in_javadoc = 1
# ij_java_do_not_indent_top_level_class_members = false
# ij_java_do_not_wrap_after_single_annotation = false
# ij_java_do_while_brace_force = never
# ij_java_doc_add_blank_line_after_description = true
# ij_java_doc_add_blank_line_after_param_comments = false
# ij_java_doc_add_blank_line_after_return = false
# ij_java_doc_add_p_tag_on_empty_lines = true
# ij_java_doc_align_exception_comments = true
# ij_java_doc_align_param_comments = true
# ij_java_doc_do_not_wrap_if_one_line = false
# ij_java_doc_enable_formatting = true
# ij_java_doc_enable_leading_asterisks = true
# ij_java_doc_indent_on_continuation = false
# ij_java_doc_keep_empty_lines = true
# ij_java_doc_keep_empty_parameter_tag = true
# ij_java_doc_keep_empty_return_tag = true
# ij_java_doc_keep_empty_throws_tag = true
# ij_java_doc_keep_invalid_tags = true
# ij_java_doc_param_description_on_new_line = false
# ij_java_doc_preserve_line_breaks = false
# ij_java_doc_use_throws_not_exception_tag = true
# ij_java_else_on_new_line = false
# ij_java_enum_constants_wrap = off
# ij_java_extends_keyword_wrap = off
# ij_java_extends_list_wrap = off
# ij_java_field_annotation_wrap = split_into_lines
# ij_java_finally_on_new_line = false
# ij_java_for_brace_force = never
# ij_java_for_statement_new_line_after_left_paren = false
# ij_java_for_statement_right_paren_on_new_line = false
# ij_java_for_statement_wrap = off
# ij_java_generate_final_locals = false
# ij_java_generate_final_parameters = false
# ij_java_if_brace_force = never
# ij_java_imports_layout = android.**,|,androidx.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,|,$*,|
# ij_java_indent_case_from_switch = true
# ij_java_insert_inner_class_imports = false
# ij_java_insert_override_annotation = true
# ij_java_keep_blank_lines_before_right_brace = 2
# ij_java_keep_blank_lines_between_package_declaration_and_header = 2
# ij_java_keep_blank_lines_in_code = 2
# ij_java_keep_blank_lines_in_declarations = 2
# ij_java_keep_control_statement_in_one_line = true
# ij_java_keep_first_column_comment = true
# ij_java_keep_indents_on_empty_lines = false
# ij_java_keep_line_breaks = true
# ij_java_keep_multiple_expressions_in_one_line = false
# ij_java_keep_simple_blocks_in_one_line = false
# ij_java_keep_simple_classes_in_one_line = false
# ij_java_keep_simple_lambdas_in_one_line = false
# ij_java_keep_simple_methods_in_one_line = false
# ij_java_label_indent_absolute = false
# ij_java_label_indent_size = 0
# ij_java_lambda_brace_style = end_of_line
# ij_java_layout_static_imports_separately = true
# ij_java_line_comment_add_space = false
# ij_java_line_comment_at_first_column = true
# ij_java_method_annotation_wrap = split_into_lines
# ij_java_method_brace_style = end_of_line
# ij_java_method_call_chain_wrap = off
# ij_java_method_parameters_new_line_after_left_paren = false
# ij_java_method_parameters_right_paren_on_new_line = false
# ij_java_method_parameters_wrap = off
# ij_java_modifier_list_wrap = false
# ij_java_names_count_to_use_import_on_demand = 99
# ij_java_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_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_space_after_closing_angle_bracket_in_type_argument = false
# ij_java_space_after_colon = true
# ij_java_space_after_comma = true
# ij_java_space_after_comma_in_type_arguments = true
# ij_java_space_after_for_semicolon = true
# ij_java_space_after_quest = true
# ij_java_space_after_type_cast = true
# ij_java_space_before_annotation_array_initializer_left_brace = false
# ij_java_space_before_annotation_parameter_list = false
# ij_java_space_before_array_initializer_left_brace = false
# ij_java_space_before_catch_keyword = true
# ij_java_space_before_catch_left_brace = true
# ij_java_space_before_catch_parentheses = true
# ij_java_space_before_class_left_brace = true
# ij_java_space_before_colon = true
# ij_java_space_before_colon_in_foreach = true
# ij_java_space_before_comma = false
# ij_java_space_before_do_left_brace = true
# ij_java_space_before_else_keyword = true
# ij_java_space_before_else_left_brace = true
# ij_java_space_before_finally_keyword = true
# ij_java_space_before_finally_left_brace = true
# ij_java_space_before_for_left_brace = true
# ij_java_space_before_for_parentheses = true
# ij_java_space_before_for_semicolon = false
# ij_java_space_before_if_left_brace = true
# ij_java_space_before_if_parentheses = true
# ij_java_space_before_method_call_parentheses = false
# ij_java_space_before_method_left_brace = true
# ij_java_space_before_method_parentheses = false
# ij_java_space_before_opening_angle_bracket_in_type_parameter = false
# ij_java_space_before_quest = true
# ij_java_space_before_switch_left_brace = true
# ij_java_space_before_switch_parentheses = true
# ij_java_space_before_synchronized_left_brace = true
# ij_java_space_before_synchronized_parentheses = true
# ij_java_space_before_try_left_brace = true
# ij_java_space_before_try_parentheses = true
# ij_java_space_before_type_parameter_list = false
# ij_java_space_before_while_keyword = true
# ij_java_space_before_while_left_brace = true
# ij_java_space_before_while_parentheses = true
# ij_java_space_inside_one_line_enum_braces = false
# ij_java_space_within_empty_array_initializer_braces = false
# ij_java_space_within_empty_method_call_parentheses = false
# ij_java_space_within_empty_method_parentheses = false
# ij_java_spaces_around_additive_operators = true
# ij_java_spaces_around_assignment_operators = true
# ij_java_spaces_around_bitwise_operators = true
# ij_java_spaces_around_equality_operators = true
# ij_java_spaces_around_lambda_arrow = true
# ij_java_spaces_around_logical_operators = true
# ij_java_spaces_around_method_ref_dbl_colon = false
# ij_java_spaces_around_multiplicative_operators = true
# ij_java_spaces_around_relational_operators = true
# ij_java_spaces_around_shift_operators = true
# ij_java_spaces_around_type_bounds_in_type_parameters = true
# ij_java_spaces_around_unary_operator = false
# ij_java_spaces_within_angle_brackets = false
# ij_java_spaces_within_annotation_parentheses = false
# ij_java_spaces_within_array_initializer_braces = false
# ij_java_spaces_within_braces = false
# ij_java_spaces_within_brackets = false
# ij_java_spaces_within_cast_parentheses = false
# ij_java_spaces_within_catch_parentheses = false
# ij_java_spaces_within_for_parentheses = false
# ij_java_spaces_within_if_parentheses = false
# ij_java_spaces_within_method_call_parentheses = false
# ij_java_spaces_within_method_parentheses = false
# ij_java_spaces_within_parentheses = false
# ij_java_spaces_within_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
[{*.htm,*.shtm,*.sht,*.shtml,*.html}]
# ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
# ij_html_align_attributes = true
# ij_html_align_text = false
# ij_html_attribute_wrap = normal
# ij_html_block_comment_at_first_column = true
# ij_html_do_not_align_children_of_min_lines = 0
# ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
# ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
# ij_html_enforce_quotes = false
# ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
# ij_html_keep_blank_lines = 2
# ij_html_keep_indents_on_empty_lines = false
# ij_html_keep_line_breaks = true
# ij_html_keep_line_breaks_in_text = true
# ij_html_keep_whitespaces = false
# ij_html_keep_whitespaces_inside = span,pre,textarea
# ij_html_line_comment_at_first_column = true
# ij_html_new_line_after_last_attribute = never
# ij_html_new_line_before_first_attribute = never
# ij_html_quote_style = double
# ij_html_remove_new_line_before_tags = br
# ij_html_space_after_tag_name = false
# ij_html_space_around_equality_in_attribute = false
# ij_html_space_inside_empty_tag = false
# ij_html_text_wrap = normal
[{*.jhm,*.xslt,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.wsdl,*.jrxml,*.xml,*.jnlp,*.xul}]
# ij_continuation_indent_size = 4
# ij_xml_block_comment_at_first_column = true
# ij_xml_keep_indents_on_empty_lines = false
# ij_xml_line_comment_at_first_column = true
# ij_xml_use_custom_settings = true
# ij_continuation_indent_size = 4
# ij_xml_block_comment_at_first_column = true
# ij_xml_keep_indents_on_empty_lines = false
# ij_xml_line_comment_at_first_column = true
# ij_xml_use_custom_settings = true
[{*.kt,*.kts}]
# ij_kotlin_align_in_columns_case_branch = false
# ij_kotlin_align_multiline_binary_operation = false
# ij_kotlin_align_multiline_extends_list = false
# ij_kotlin_align_multiline_method_parentheses = false
# ij_kotlin_align_multiline_parameters = true
# ij_kotlin_align_multiline_parameters_in_calls = false
# ij_kotlin_assignment_wrap = normal
# ij_kotlin_blank_lines_after_class_header = 0
# ij_kotlin_blank_lines_around_block_when_branches = 0
# ij_kotlin_block_comment_at_first_column = true
# ij_kotlin_call_parameters_new_line_after_left_paren = true
# ij_kotlin_call_parameters_right_paren_on_new_line = true
# ij_kotlin_call_parameters_wrap = on_every_item
# ij_kotlin_catch_on_new_line = false
# ij_kotlin_class_annotation_wrap = split_into_lines
# ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
# ij_kotlin_continuation_indent_for_chained_calls = false
# ij_kotlin_continuation_indent_for_expression_bodies = false
# ij_kotlin_continuation_indent_in_argument_lists = false
# ij_kotlin_continuation_indent_in_elvis = false
# ij_kotlin_continuation_indent_in_if_conditions = false
# ij_kotlin_continuation_indent_in_parameter_lists = false
# ij_kotlin_continuation_indent_in_supertype_lists = false
# ij_kotlin_else_on_new_line = false
# ij_kotlin_enum_constants_wrap = off
# ij_kotlin_extends_list_wrap = normal
# ij_kotlin_field_annotation_wrap = split_into_lines
# ij_kotlin_finally_on_new_line = false
# ij_kotlin_if_rparen_on_new_line = true
# ij_kotlin_import_nested_classes = false
# ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
# ij_kotlin_keep_blank_lines_before_right_brace = 2
# ij_kotlin_keep_blank_lines_in_code = 2
# ij_kotlin_keep_blank_lines_in_declarations = 2
# ij_kotlin_keep_first_column_comment = true
# ij_kotlin_keep_indents_on_empty_lines = false
# ij_kotlin_keep_line_breaks = true
# ij_kotlin_lbrace_on_next_line = false
# ij_kotlin_line_comment_add_space = false
# ij_kotlin_line_comment_at_first_column = true
# ij_kotlin_method_annotation_wrap = split_into_lines
# ij_kotlin_method_call_chain_wrap = normal
# ij_kotlin_method_parameters_new_line_after_left_paren = true
# ij_kotlin_method_parameters_right_paren_on_new_line = true
# ij_kotlin_method_parameters_wrap = on_every_item
# ij_kotlin_name_count_to_use_star_import = 5
# ij_kotlin_name_count_to_use_star_import_for_members = 3
# ij_kotlin_parameter_annotation_wrap = off
# ij_kotlin_space_after_comma = true
# ij_kotlin_space_after_extend_colon = true
# ij_kotlin_space_after_type_colon = true
# ij_kotlin_space_before_catch_parentheses = true
# ij_kotlin_space_before_comma = false
# ij_kotlin_space_before_extend_colon = true
# ij_kotlin_space_before_for_parentheses = true
# ij_kotlin_space_before_if_parentheses = true
# ij_kotlin_space_before_lambda_arrow = true
# ij_kotlin_space_before_type_colon = false
# ij_kotlin_space_before_when_parentheses = true
# ij_kotlin_space_before_while_parentheses = true
# ij_kotlin_spaces_around_additive_operators = true
# ij_kotlin_spaces_around_assignment_operators = true
# ij_kotlin_spaces_around_equality_operators = true
# ij_kotlin_spaces_around_function_type_arrow = true
# ij_kotlin_spaces_around_logical_operators = true
# ij_kotlin_spaces_around_multiplicative_operators = true
# ij_kotlin_spaces_around_range = false
# ij_kotlin_spaces_around_relational_operators = true
# ij_kotlin_spaces_around_unary_operator = false
# ij_kotlin_spaces_around_when_arrow = true
# ij_kotlin_variable_annotation_wrap = off
# ij_kotlin_while_on_new_line = false
# ij_kotlin_wrap_elvis_expressions = 1
# ij_kotlin_wrap_expression_body_functions = 1
# ij_kotlin_wrap_first_method_in_call_chain = false

4
.gitattributes vendored
View file

@ -1,4 +0,0 @@
* text=auto eol=lf
*.bat text eol=crlf
*.jar binary

View file

@ -1,51 +0,0 @@
---
name: Bug report
about: Create a report to help us improve. Please write in English only.
title: ''
labels: bug
assignees: ''
---
<!--
Don't report bugs that are already in the issue list of Retro Music: https://github.com/h4h13/RetroMusicPlayer/issues
[ ] Yes
-->
**Have you read the [FAQ](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/FAQ.md)?**
[Yes/No]
**Has the bug already been reported? Please search in GitHub issue tab before creating an issue.**
[Yes/No]
**Describe the bug**
A clear and concise description of what the bug is.
**How To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
4.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Crash log**
If the app is crashing, add a crash log
<details>
<summary>Click to view logs</summary>
PASTE YOUR LOGS HERE.
</details>
**Device info:**
- Device: [e.g. OnePlus 7]
- Android version: [e.g. Android 9]
- App version [e.g. 3.5.300_0517]
**Additional context**
Add any other context about the problem here.

View file

@ -1,26 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project. Please write in English only.
title: ''
labels: enhancement
assignees: ''
---
<!--
Don't report bugs that are already in the issue list of Retro Music: https://github.com/h4h13/RetroMusicPlayer/issues
[ ] Yes
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View file

@ -1,34 +0,0 @@
name: Android CI
on:
push:
branches: [ dev ]
pull_request:
branches: [ dev ]
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- uses: gradle/gradle-build-action@v2
- name: Lint Android
run: ./gradlew lint
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'zulu'
- uses: gradle/gradle-build-action@v2
- name: Build
run: ./gradlew app:assemble

6
.gitignore vendored
View file

@ -38,4 +38,8 @@ obj/
captures captures
app/normal/release/ app/normal/release/
/models/ /models/
/app/release/
app/font/
app/src/debug/
/app/nofont/
/crowdin.properties

View file

@ -1,35 +0,0 @@
# Contributing
## Using the issue tracker
The [issue tracker](https://github.com/RetroMusicPlayer/RetroMusicPlayer/issues) is the preferred channel for bug reports, feature requests and submitting pull requests, but please follow these rules:
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
* Please **do not** post comments consisting solely of "+1" or "👍". Use [GitHub's "reactions" feature](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) instead.
* Please **do not** write [enhancement]/[bug]/[feature request] or similar stuff in the title of issues, as there are labels for that purpose that will be added by devs or collaborators.
## Bug reports
A bug is a _demonstrable problem_. Good bug reports are extremely helpful, so thanks!
Guidelines for bug reports:
* Use the GitHub issue search, check if the issue has already been reported both in the open issues and in the closed ones, if there are some missing details, add them in issue comments, without creating a new one
* Check if the issue has been fixed — try to reproduce it using the latest available build
* Isolate the problem — ideally create a reproducible scenario and a live example
* Do not report multiple bugs in a single ticket, otherwise it will be confusing.
A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report.
## Feature requests
Feature requests are welcome, please make sure to be as detailed as possible and use screenshots, videos, GIFs, to demonstrate it better, if possible.
## Pull requests
**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that developers might not want to merge into the project. To avoid that, you can join the official [Telegram group](https://t.me/retromusicapp) or open an issue.
## License
By contributing your code, you agree to license your contribution under the [GNU General Public License](https://github.com/RetroMusicPlayer/RetroMusicPlayer/blob/master/LICENSE.md).
Please note we have a code of conduct, please follow it in all your interactions with the project.

175
FAQ.md
View file

@ -1,157 +1,80 @@
## **Q: How do I get the beta version of Retro Music?** #### Q: How do I get the beta version of Retro Music?
You can opt-in for the beta build by clicking on this link: https://play.google.com/apps/testing/code.name.monkey.retromusic You can opt-in for the beta build by clicking on this link: https://play.google.com/apps/testing/code.name.monkey.retromusic
___ #### Q: How do I use offline synced lyrics?
There are two methods for how to get offline synced lyrics.
## **Q: How to restore my purchases?** #### Method 1:-
Make sure to switch and use your account in the Play Store app through which you purchased before installing Retro Music. The Google account used to install the app is also used to purchase/restore the pro license. ##### STEP 1:
Find the time stamped lyrics for your songs which don't have lyrics already. A time stamped lyrics looks like this, "[00:04:02] Some lyrics text" for example.
If you've already installed the app, remove all other accounts except the one from which you purchased premium, and then restore the purchase. ##### STEP 2:
Copy these time stamped lyrics.
___ ##### STEP 3:
## Q: **How do I use offline synced lyrics?**
There are three methods for adding offline synced lyrics in Retro Music.
### ***Method 1:-***
#### STEP 1:
Find the time-stamped lyrics for your songs that don't have lyrics already. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
#### STEP 2:
Copy these time-stamped lyrics.
#### STEP 3:
Open retro music and head to the song synced lyrics editor. Open retro music and head to the song synced lyrics editor.
#### STEP 4: ##### STEP 4:
Paste the lyrics there normally and exit the editor Paste the lyrics there normally and exit the editor
#### STEP 5: ##### STEP 5:
Open lyrics and you should see your time-stamped lyrics there. Open lyrics and you should see your time stamped lyrics there.
### ***Method 2:-*** #### Method 2:-
#### STEP 1: ##### STEP 1:
Download the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example. Find the ".lrc" files for your songs which doesn't have lyrics already.
#### STEP 2: ##### STEP 2:
A ".lrc" is like a text file that contains the time-stamped lyrics for example, "[00:04:02] Some lyrics text". Save your time-stamped lyrics are ".lrc" file. A ".lrc" is like a text file which contains the time stamped lyrics for example, "[00:04:02] Some lyrics text"
#### STEP 3: ##### STEP 3:
Now you have to rename the file you created in this way: <song_name> - <artist_name>.lrc or for better matching copy the <song_name> and the <artist_name> from the tag editor and then rename the file. Now you have to rename the file you created in this way: <song_name> - <artist_name>.lrc or for better matching copy the <song_name> and the <artist_name> from the tag editor and then rename the file.
### STEP 4: ##### STEP 4:
Now paste the LRC files to the following path: /sdcard/Retromusic/lyrics/ You have to copy or move this file into a location on the SD Card: whatever_sdcard/RetroMusic/lyrics/ and paste it there.
Here sdcard is your internal storage. Finally you will be able to see the lyrics working.
### ***Method 3:- (Requires third-party app)*** > If you want to skip to particular time stamp, simply scroll to the time stamp from where you want to start from and a 'Play' icon will appear left to the particular stamp. Tap on play button to play from there.
#### STEP 1:
Download automatag or autotagger from Play Store.
#### STEP 2:
Find the time-stamped lyrics for your songs that don't have lyrics already. You can find ".lrc" files for popular songs at either of the websites given below. A time-stamped lyric looks like this, "[00:04:02] Some lyrics text" for example.
#### STEP 3:
Find your song to edit and paste the synced lyrics.
> These apps add those synced lyrics in the music file itself instead of creating a ".lrc file for it."
**Lyrics Website Links:**
- https://www.lyricsify.com/
- https://www.syair.info
**Some Important Notes:** #### Q: Why isn't the artist's image downloading?
- If you want to skip to a particular timestamp, simply scroll to the time stamp from where you want to start and a 'Play' icon will appear left to the particular stamp. Tap on the play button to play from there. Last.fm has disabled the download of artist's images for the time being, whether functionality for this will be restored in future is uncertain.
- When you save lyrics by pasting lyrics in lyrics editor, close the lyrics and open again for lyrics to show.
- For those who are having difficulty in making the synced lyrics work, we have a short tutorial video on it. Hope this helps you. {[Link in the note or here](https://youtu.be/1oIOTGWhNMY)}
___ #### Q: How do I change the theme?
## **Q: How do I change the theme?**
Settings -> Look and feel -> Select your theme. Settings -> Look and feel -> Select your theme.
___
## **Q: Equalizer is very laggy and unstable or I am getting a "No equalizer found" error. Why?** #### Q: Why aren't last added songs showing?
- The Retro music in-built equalizer was removed updates ago so the only equalizer you will have by your OEM or Android native equalizer which isn't made by us and have no control over them. So you can report those issues to your OEM so that they can provide a fix in the next updates.
- If you are seeing "No Equalizer Found" in your device, this means your device doesn't have a stock equalizer "MusicFx" Equalizer. You can try using this one. It's made by AEX ROM developers.
https://drive.google.com/file/d/1_1bpsn6roeEyElGKikbU39lVKUH8O3xp/view?**usp=drivesdk
___
## **Q: Why aren't last added songs showing?**
Settings -> Other -> Last added playlist interval -> Select an option from the list. Settings -> Other -> Last added playlist interval -> Select an option from the list.
___
## **Q: How do I enable fullscreen lock screen controls?** #### Q: How do I enable fullscreen lockscreen controls?
Settings -> Personalize -> Fullscreen controls -> Enable (this will only be visible when songs are playing from Retro Music). Settings -> Personalize -> Fullscreen controls -> Enable (this will only be visible when songs are playing from Retro Music).
___
## **Q: Why are my gallery or random pictures showing up as album art?** #### Q: Why are gallery or random pictures showing up as album art?
Settings -> Images -> Ignore media store covers -> Enable Settings -> Images -> Ignore media store covers -> Enable
___
## **Q: Which file types are supported?** #### Q: Which file types are supported?
Retro Music uses the native media player that comes with your Android phone, so as long as a file type is supported by your phone, it's supported by Retro Music. Retro Music uses the native media player that comes with your Android phone, so as long as a file type is supported by your phone, it's supported by Retro Music.
___
## **Q: Why is my device slowing down when I'm using the app?** #### Q: Why is my device slowing down when I'm using the app?
Retro Music is image intensive, it keeps images in the cache for quick loading. Retro Music is image intensive, it keeps images in the cache for quick loading.
___
## **Q: The title "Retro Music" is showing on the top of the app, how can I fix this?** #### Q: The title "Retro Music" is showing on the top of the app, how can i fix this?
Clear the app's cache and data. Clear the app's cache and data.
___
## **Q: My app is crashing, how do I fix this?** (Sorry, settings have changed internally) ##### Q: My app is crashing, how do i fix this? (Sorry, settings have changed internally)
Please try to clear the data of the app. If it doesn't work, reinstalling fresh from the play store should help. Reinstalling the app should fix this issue.
___
## **Q: Why has all the text gone white/disappeared?** ##### Q: Why has all the text gone white/dissapeared?
Change the theme to Black or Dark and change it back to what you had before. Change the theme to Black or Dark and change it back to what you had before.
___
## **Q: Why some of my songs are not showing in my library?** ##### Q: Why some of my songs are not showing in my library?
- Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs that should start appearing in the library. Try checking up if those songs are not less than 30 seconds, if so head to settings -> other -> filter song duration. Put this to zero and see the songs should start appearimg in the library.
If this doesn't work out for you, re-scanning the media folder should help and subsquently rebooting the device to refresh media store.
At last resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card.
- If this doesn't work out for you, re-scanning the media folder should help and subsequently rebooting the device to refresh the media store. ##### Q: Why my playlist/playlist songs keep disappearing?
Playlist/Playlist songs disappearing is based on android media store system. Save those playlist as file(Tap on three dot menu next to available playlist and save as file) and it should get fixed.
- At last, resort, If nothing worked and your audio files are stored in SD card. Try moving them to internal memory then back to SD card. ##### Q: Why does my library shows song files twice or no song at all?
If you are seeing duplication of songs in the library or no songs at all, then it's because of Media Store issue which got affected by some other app.
To fix this:
• Head to your device settings
• Open up "Apps & notifications" (This name depends from ROM to ROM)
• Find 'Media storage' app and clear storage (both data and cache) of it.
• Then open Retro Music app and manually scan your music from your storage.
• Reboot the device to refresh media store (Not sure if this is necessary)
___ NOTE: Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device.
## **Q: Why does my library shows song files twice or no song at all?**
If you are seeing duplication of songs in the library or no songs at all, then it's because of the Media Store issue which got affected by some other app.
***To fix this:***
1. Head to your device settings
1. Open up "Apps & notifications" (This name depends from ROM to ROM)
1. Find the 'Media storage' app and clear storage (both data and cache) of it.
1. Then open the Retro Music app and manually scan your music from your storage.
1. Reboot the device to refresh the media store (Not sure if this is necessary)
**NOTE:** Don't panic when you will open Retro Music and see "Zero" songs there in the library. It's because you cleared Media Store which is responsible for recognising files on your device.
___
## **Q: I can't find the folder menu anymore after the latest update?**
Head to settings -> personalise. And select folders from "library categories". If there is no option of folders, tap on reset and select folders.
___
## **Q: After updating the app to the latest version, the font got removed. Why?**
- Retro Music's font has now been replaced with system font, which means the default font your system uses will be used by Retro Music too. It fixes all font-related issues you used to face/are facing in the app.
- With the recent Retro Music v5 release, we have a built-in optional font "Manrope font" which you can toggle from Settings > Look & Feel > Toggle "Use manrope font".
- If you think the font looks ugly, then you just need to change the default font from your Android settings (or use any Magisk module). If you can't, there's nothing we can do about it.
___
## **Q: How to export playlist?**
- ***From Retro Music:***
Head to the playlists tab > tap on the three-dot menu on the playlist you want to export > save as a file.
- ***From Other Music Players:***
In your built-in music player, there should be an option to save that playlist as a file. Save them and import them from the file manager by opening it into retro music.
> Note that such playlist must be of your offline music only since retro music is an offline music player, not an online music player. So if your playlist is of online music, it can't be opened on other offline players nor can be exported
___
## **Q: How to restore/import playlist?**
Retro Music will automatically detect any playlist file when that playlist file is stored in internal storage/Playlist. However, if it doesn't, just open any "File manager" and open that playlist file with Retro Music.
For restoring playlists successfully, the location of songs must be the same in both the "Playlist" file and in your storage. For example, If your music is in "Internal storage/Music" and the playlist file has songs location "Internal storage/Songs". Then it will not be going to work since both these locations are different.

111
README.md
View file

@ -1,97 +1,36 @@
# Metro # Retro Music Player
Material Design music player for Android music lovers [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://github.com/h4h13/RetroMusicPlayer/blob/master/LICENSE.txt)
## Downloads **Android Material Design music player**
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" Retro Music is an open source, local music player for Android.
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/io.github.muntashirakon.Music/)
## Differences between Metro and [RetroMusicPlayer](https://github.com/h4h13/RetroMusicPlayer) #### Features
- Google Play libraries removed (fully libre)
- Pro features available for free
- Fully offline (INTERNET permission removed)
- Bug fixes
- Minor differences in UI
## 📱 Screenshots - Local playback only (based on the MediaStore)
### App Themes
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg" width="200"/> | <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg" width="200"/> |
|:---:|:---:|:---:|
|Clearly white| Kinda dark | Just black|
### Player screen
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/7.jpg" width="200"/>| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/8.jpg" width="200"/>|
|:---:|:---:|:---:|:---:|:---:|
| Home | Songs | Albums | Artists | Settings |
### Synced lyrics screen (Over Cover)
| <img src="screenshots/synced_over_light.jpg" width="200"/>| <img src="screenshots/synced_over_dark.jpg" width="200"/>| <img src="screenshots/synced_over_black.jpg" width="200"/>|
|:---:|:---:|:---:|
| Synced Over Cover light | Synced Over Cover dark | Synced Over Cover black |
### Synced lyrics screen (Replace Cover)
| <img src="screenshots/synced_replace_light.jpg" width="200"/>| <img src="screenshots/synced_replace_dark.jpg" width="200"/>| <img src="screenshots/synced_replace_black.jpg" width="200"/>|
|:---:|:---:|:---:|
| Synced Replace Cover light | Synced Replace Cover dark | Synced Replace Cover black |
### 10+ Now playing themes
| <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg" width="200"/> |<img src="screenshots/fit.jpg" width="200"/>| <img src="screenshots/flat.jpg" width="200"/> | <img src="screenshots/color.jpg" width="200"/> | <img src="screenshots/material.jpg" width="200"/> |
|:-----: |:-----: |:-----: |:-----: |:-----: |
| Normal | Fit | Flat | Color | Material |
| <img src="screenshots/classic.jpg" width="200"/> |<img src="screenshots/adaptive.jpg" width="200"/>| <img src="screenshots/blur.jpg" width="200"/> | <img src="screenshots/tiny.jpg" width="200"/> | <img src="screenshots/peek.jpg" width="200"/> |
|:-----: |:-----: |:-----: |:-----: |:-----: |
| Classic | Adaptive | Blur | Tiny | Peek |
## 🧭 Navigation never made easier
Self-explanatory interface without overloaded menus.
## 🎨 Colorful
You can choose between three different main themes: Clearly White, Kinda
Dark and Just Black for AMOLED displays. Select your favorite accent
color from a color palette.
## 🏠 Home
Where you can view your recently/top played artists, albums and
favorite songs. No other music player has this feature.
## 📦 Included Features
- Base 3 themes (Clearly White, Kinda Dark and Just Black)
- Choose from 10+ now playing themes
- Driving Mode
- Headset/Bluetooth support
- Music duration filter
- Android auto support
- Wallpaper accent picker on Android 8.1+
- Material You support on Android 12+
- Monet themed icon support on Android 13+
- Folder support - Play songs by folder
- Gapless playback
- Volume controls
- Carousel effect for album covers
- Home screen widgets
- Lock screen playback controls
- Lyrics screen (download and sync with music)
- Sleep timer - Sleep timer
- Easy drag to sort playlist & play queue - Folder browser
- Tag editor - Home screen widgets
- Create, edit and import playlists - Themes (Just Black, Kinda Dark, Clearly White)
- Playing queue with reorder - Now playing themes
- User profile - Volume controls
- 30+ languages support - Blacklist
- Browse and play your music by songs, albums, artists, playlists and - Album artist support
genre
- Smart Auto Playlists - Recently played, most played and history
- Build your playlist on the go
We are trying our best to bring you the best user experience. The app is regularly being updated for bug fixes and new features. #### Download
## 🗂️ License <a href='https://play.google.com/store/apps/details?id=code.name.monkey.retromusic&hl=en&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="250" alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png'/></a>
Metro is released under the GNU General Public License v3.0 #### Translations
(GPLv3), which can be found [here](LICENSE.md) Help us translate the app to your language [here](http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534).
> Please note: Metro is an offline music player app. It doesn't support music downloading or online music streaming. #### License
Retro Music Player is released under the GNU General Public License v3.0 (GPLv3), which can be found here: [License](LICENSE.md)
#### FAQ [Here](FAQ.md)
#### Change-Log and Release [Here](https://github.com/h4h13/RetroMusicPlayer/releases)

View file

@ -1,30 +1,51 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: "androidx.navigation.safeargs.kotlin" apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.devtools.ksp' apply plugin: "ru.cleverpumpkin.proguard-dictionaries-generator"
proguardDictionaries {
dictionaryNames = [
"build/class-dictionary",
"build/package-dictionary",
"build/obfuscation-dictionary"
]
}
android { android {
compileSdk 33 compileSdkVersion 29
namespace "code.name.monkey.retromusic" buildToolsVersion = '29.0.3'
defaultConfig { defaultConfig {
minSdk 21 minSdkVersion 21
targetSdk 33 targetSdkVersion 29
renderscriptTargetApi 29 //must match target sdk and build tools
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId 'io.github.muntashirakon.Music' applicationId "code.name.monkey.retromusic"
versionCode 10603 versionCode 417
versionName '6.1.0' versionName '3.5.100'
multiDexEnabled true multiDexEnabled true
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
}
signingConfigs {
release {
Properties properties = getProperties('/Users/hemanths/Desktop/KeepSafe/retro.properties')
storeFile file(getProperty(properties, 'storeFile'))
keyAlias getProperty(properties, 'keyAlias')
storePassword getProperty(properties, 'storePassword')
keyPassword getProperty(properties, 'keyPassword')
}
} }
buildTypes { buildTypes {
release { release {
shrinkResources true
minifyEnabled true minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
} }
debug { debug {
applicationIdSuffix '.debug' applicationIdSuffix '.debug'
@ -32,101 +53,140 @@ android {
} }
} }
buildFeatures {
viewBinding true flavorDimensions "default"
productFlavors {
normal {
versionCode defaultConfig.versionCode + 10000
versionName defaultConfig.versionName + "_" + getDate()
dimension "default"
} }
font {
versionCode defaultConfig.versionCode + 10000
versionName defaultConfig.versionName + "_" + getDate()
dimension "default"
}
nofont {
versionCode defaultConfig.versionCode + 10000
versionName defaultConfig.versionName + "_" + getDate()
dimension "default"
}
}
packagingOptions { packagingOptions {
resources { exclude 'META-INF/LICENSE'
excludes += ['META-INF/LICENSE', 'META-INF/NOTICE', 'META-INF/java.properties'] exclude 'META-INF/NOTICE'
exclude 'META-INF/java.properties'
} }
} lintOptions {
lint { disable 'MissingTranslation'
abortOnError true disable 'InvalidPackage'
warning 'ImpliedQuantity', 'Instantiatable', 'MissingQuantity', 'MissingTranslation' abortOnError false
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility '1.8'
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility '1.8'
} }
kotlinOptions {
jvmTarget = "1.8"
} configurations.all {
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
configurations.configureEach {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
} }
androidExtensions {
experimental = true
}
kapt {
generateStubs = true
}
}
def getProperties(String fileName) {
final Properties properties = new Properties()
def file = file(fileName)
if (file.exists()) {
file.withInputStream { stream -> properties.load(stream) }
}
return properties
}
static def getProperty(Properties properties, String name) {
return properties.getProperty(name) ?: "$name missing"
}
static def getDate() {
new Date().format('MMdd')
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':appthemehelper') implementation project(':appthemehelper')
implementation project(':fonts')
implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.palette:palette:1.0.0"
implementation 'androidx.annotation:annotation:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation "androidx.preference:preference-ktx:$preference_version"
implementation "androidx.core:core-ktx:$core_version"
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
implementation "androidx.viewpager2:viewpager2:1.1.0-alpha01"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.mediarouter:mediarouter:1.3.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.fragment:fragment:1.2.4'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.navigation:navigation-runtime-ktx:$navigation_version" implementation 'com.google.android.material:material:1.2.0-alpha06'
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
def room_version = '2.5.1' def retrofit_version = "2.6.2"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.core:core-splashscreen:1.0.0"
implementation "com.google.android.material:material:$mdc_version"
def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
def material_dialog_version = "3.3.0" def material_dialog_version = "3.1.1"
implementation "com.afollestad.material-dialogs:core:$material_dialog_version" implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:input:$material_dialog_version" implementation "com.afollestad.material-dialogs:input:$material_dialog_version"
implementation "com.afollestad.material-dialogs:color:$material_dialog_version" implementation "com.afollestad.material-dialogs:color:$material_dialog_version"
implementation "com.afollestad.material-dialogs:bottomsheets:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
implementation 'com.afollestad:material-cab:2.0.1' implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') {
transitive = true
def koin_version = '3.4.0' }
implementation "io.insert-koin:koin-core:$koin_version" /*UI Library*/
implementation "io.insert-koin:koin-android:$koin_version" implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
def glide_version = '4.15.1'
implementation "com.github.bumptech.glide:glide:$glide_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
ksp "com.github.bumptech.glide:ksp:$glide_version" def kotlin_coroutines_version = "1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3' implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.1' implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation "dev.chrisbanes.insetter:insetter:0.6.1" implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
implementation 'com.github.Adonai:jaudiotagger:2.3.15' implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:2.0.0' implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.zhanghai.android.fastscroll:library:1.2.0'
implementation 'cat.ereza:customactivityoncrash:2.4.0' def dagger_version = "2.23.1"
implementation 'me.tankery.lib:circularSeekBar:1.4.2' implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
} }

View file

@ -16,9 +16,9 @@
# public *; # public *;
#} #}
# Preserve the line number information for # Uncomment this to preserve the line number information for
# debugging stack traces. # debugging stack traces.
-keepattributes SourceFile,LineNumberTable #-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
@ -26,46 +26,45 @@
-dontwarn java.lang.invoke.* -dontwarn java.lang.invoke.*
-dontwarn **$$Lambda$* -dontwarn **$$Lambda$*
-dontwarn javax.annotation.**
# RetroFit # RetroFit
-dontwarn retrofit.** -dontwarn retrofit.**
-keep class retrofit.** { *; } -keep class retrofit.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-dontwarn javax.annotation.**
# Glide # Glide
-keep public class * implements com.bumptech.glide.module.GlideModule -keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule { -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
<init>(...);
}
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES; **[] $VALUES;
public *; public *;
} }
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
*** rewind();
}
# OkHttp -keep class !android.support.v7.internal.view.menu.**,** {*;}
-keepattributes Signature
-keepattributes *Annotation*
-keep interface com.squareup.okhttp3.** { *; }
-dontwarn com.squareup.okhttp3.**
#-dontwarn -dontwarn
#-ignorewarnings -ignorewarnings
#Jaudiotagger -keep public class android.support.design.widget.BottomNavigationView { *; }
-dontwarn org.jaudiotagger.** -keep public class android.support.design.internal.BottomNavigationMenuView { *; }
-dontwarn org.jcodec.** -keep public class android.support.design.internal.BottomNavigationPresenter { *; }
-keep class org.jaudiotagger.** { *; } -keep public class android.support.design.internal.BottomNavigationItemView { *; }
-keep class org.jcodec.** { *; }
-keepclassmembers enum * { *; } #-dontwarn android.support.v8.renderscript.*
-keepattributes *Annotation*, Signature, Exception #-keepclassmembers class android.support.v8.renderscript.RenderScript {
-keepnames class androidx.navigation.fragment.NavHostFragment # native *** rsn*(...);
-keep class * extends androidx.fragment.app.Fragment{} # native *** n*(...);
-keepnames class * extends android.os.Parcelable #}
-keepnames class * extends java.io.Serializable
-keep class code.name.monkey.retromusic.network.model.** { *; } #-keep class org.jaudiotagger.** { *; }
-keep class code.name.monkey.retromusic.model.** { *; }
-keep class com.google.android.material.bottomsheet.** { *; } #For cast
-keep class code.name.monkey.retromusic.cast.CastOptionsProvider { *; }
-keep class android.support.** { *; }
-keep class com.google.** { *; }
-keep class java.nio.file.** { *; }
-obfuscationdictionary build/obfuscation-dictionary.txt
-classobfuscationdictionary build/class-dictionary.txt
-packageobfuscationdictionary build/package-dictionary.txt

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:font="@font/google_sans_regular"
android:fontWeight="400" />
<font
android:font="@font/google_sans_medium"
android:fontWeight="600" />
<font
android:font="@font/google_sans_bold"
android:fontWeight="700" />
</font-family>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="md3_available">true</bool>
<bool name="allowBackup">false</bool>
</resources>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Metro Debug</string>
</resources>

View file

@ -1,105 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextViewNormal" parent="">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewNormalCompress" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textSize">14sp</item>
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline4.Compress" parent="TextAppearance.MaterialComponents.Headline4">
<item name="fontFamily">@font/sans</item>
<item name="android:textSize">32sp</item>
</style>
<style name="TextViewHeadline5" parent="TextAppearance.MaterialComponents.Headline5">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewCaption" parent="TextAppearance.MaterialComponents.Caption">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline6" parent="TextAppearance.MaterialComponents.Headline6">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline3" parent="TextAppearance.MaterialComponents.Headline3">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewHeadline2" parent="TextAppearance.MaterialComponents.Headline2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewSubtitle1" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewSubtitle2" parent="TextAppearance.MaterialComponents.Subtitle2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewBody1" parent="TextAppearance.MaterialComponents.Body1">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewButton" parent="TextAppearance.MaterialComponents.Button">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewBody2" parent="TextAppearance.MaterialComponents.Body2">
<item name="fontFamily">@font/sans</item>
</style>
<style name="TextViewOverline" parent="TextAppearance.MaterialComponents.Overline">
<item name="fontFamily">@font/sans</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textAppearance">@style/TextViewButton</item>
<item name="fontFamily">@font/sans</item>
<item name="android:textStyle">bold</item>
<item name="android:padding">0dp</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:textAppearance">@style/TextViewBody1</item>
<item name="fontFamily">@font/sans</item>
<item name="android:paddingTop">16dp</item>
</style>
<style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
<item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="android:textStyle">bold</item>
<item name="fontFamily">@font/sans</item>
<item name="android:padding">16dp</item>
</style>
<style name="ToolbarTextAppearanceNormal">
<item name="android:textStyle">bold</item>
<item name="android:textAllCaps">false</item>
<item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="fontFamily">@font/sans</item>
<item name="android:textSize">20sp</item>
<item name="android:letterSpacing">0.0125</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="circleImageView" parent="ShapeAppearance.MaterialComponents">
<item name="cornerSize">40dp</item>
</style>
<style name="BottomSheetItemTextAppearance" parent="Widget.MaterialComponents.BottomNavigationView.Colored">
<item name="android:textSize">13sp</item>
<item name="fontFamily">@font/sans</item>
</style>
</resources>

View file

@ -1,57 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="code.name.monkey.retromusic" package="code.name.monkey.retromusic">
android:installLocation="auto">
<uses-permission <uses-permission android:name="android.permission.BLUETOOTH" />
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
android:name="android.permission.READ_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_SETTINGS" android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="com.android.vending.BILLING" />
<uses-permission
android:name="android.permission.BLUETOOTH_CONNECT"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<application <application
android:name=".App" android:name=".App"
android:allowBackup="@bool/allowBackup" android:allowBackup="true"
android:appCategory="audio"
android:configChanges="locale|layoutDirection"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:restoreAnyVersion="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:configChanges="locale|layoutDirection"
android:theme="@style/Theme.RetroMusic.FollowSystem" android:theme="@style/Theme.RetroMusic.FollowSystem"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="false"
tools:ignore="UnusedAttribute"> tools:ignore="AllowBackup,GoogleAppIndexingWarning"
tools:targetApi="m">
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:exported="true" android:label="@string/app_name"
android:launchMode="singleTop" android:theme="@style/SplashTheme">
android:theme="@style/Theme.RetroMusic.SplashScreen">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MUSIC_PLAYER" /> <action android:name="android.intent.action.MUSIC_PLAYER" />
@ -62,6 +45,7 @@
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" /> <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
@ -121,54 +105,32 @@
<data android:mimeType="vnd.android.cursor.dir/audio" /> <data android:mimeType="vnd.android.cursor.dir/audio" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".activities.AlbumDetailsActivity" />
<activity android:name=".activities.ArtistDetailActivity" />
<activity android:name=".activities.PlaylistDetailActivity" />
<activity android:name=".activities.PlayingQueueActivity" />
<activity android:name=".activities.AboutActivity" />
<activity android:name=".activities.tageditor.AlbumTagEditorActivity" /> <activity android:name=".activities.tageditor.AlbumTagEditorActivity" />
<activity android:name=".activities.tageditor.SongTagEditorActivity" /> <activity android:name=".activities.tageditor.SongTagEditorActivity" />
<activity android:name=".activities.SettingsActivity" />
<activity android:name=".activities.LyricsActivity" />
<activity android:name=".activities.UserInfoActivity" />
<activity android:name=".activities.SupportDevelopmentActivity" />
<activity android:name=".activities.GenreDetailsActivity" />
<activity android:name=".activities.LicenseActivity" /> <activity android:name=".activities.LicenseActivity" />
<activity android:name=".activities.PurchaseActivity" />
<activity android:name=".activities.WhatsNewActivity" />
<activity android:name=".activities.bugreport.BugReportActivity" /> <activity android:name=".activities.bugreport.BugReportActivity" />
<activity android:name=".activities.ShareInstagramStory" /> <activity android:name=".activities.ShareInstagramStory" />
<activity android:name=".activities.DriveModeActivity" /> <activity android:name=".activities.DriveModeActivity" />
<activity android:name=".activities.PermissionActivity" /> <activity
android:name=".activities.SearchActivity"
android:windowSoftInputMode="stateVisible" />
<activity <activity
android:name=".activities.LockScreenActivity" android:name=".activities.LockScreenActivity"
android:excludeFromRecents="true" android:noHistory="true"
android:launchMode="singleTask"
android:showOnLockScreen="true" /> android:showOnLockScreen="true" />
<activity
android:name=".fragments.backup.RestoreActivity"
android:excludeFromRecents="false"
android:exported="true"
android:label="@string/restore"
android:theme="@style/Theme.RetroMusic.Dialog">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="application/octet-stream" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/zip" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="*/*" />
<!--
Work around Android's ugly primitive PatternMatcher
implementation that can't cope with finding a . early in
the path unless it's explicitly matched.
-->
<data android:host="*" />
<data android:pathPattern=".*\\.rmbak" />
<data android:pathPattern=".*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.rmbak" />
</intent-filter>
</activity>
<activity <activity
android:name=".appshortcuts.AppShortcutLauncherActivity" android:name=".appshortcuts.AppShortcutLauncherActivity"
@ -179,13 +141,16 @@
android:name=".activities.saf.SAFGuideActivity" android:name=".activities.saf.SAFGuideActivity"
android:theme="@style/Theme.Intro" /> android:theme="@style/Theme.Intro" />
<activity <provider
android:name=".activities.ErrorActivity" android:name=".misc.GenericFileProvider"
android:exported="true"> android:authorities="${applicationId}.provider"
<intent-filter> android:exported="false"
<action android:name="cat.ereza.customactivityoncrash.RESTART" /> android:grantUriPermissions="true">
</intent-filter> <meta-data
</activity> android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
@ -197,17 +162,13 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<receiver <receiver android:name=".service.MediaButtonIntentReceiver">
android:name=".service.MediaButtonIntentReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <receiver android:name=".appwidgets.BootReceiver">
android:name=".appwidgets.BootReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" />
@ -216,7 +177,7 @@
<receiver <receiver
android:name=".appwidgets.AppWidgetBig" android:name=".appwidgets.AppWidgetBig"
android:exported="true" android:exported="false"
android:label="@string/app_widget_big_name"> android:label="@string/app_widget_big_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -229,7 +190,7 @@
<receiver <receiver
android:name=".appwidgets.AppWidgetClassic" android:name=".appwidgets.AppWidgetClassic"
android:exported="true" android:exported="false"
android:label="@string/app_widget_classic_name"> android:label="@string/app_widget_classic_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -241,7 +202,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetSmall" android:name=".appwidgets.AppWidgetSmall"
android:exported="true" android:exported="false"
android:label="@string/app_widget_small_name"> android:label="@string/app_widget_small_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -253,7 +214,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetText" android:name=".appwidgets.AppWidgetText"
android:exported="true" android:exported="false"
android:label="@string/app_widget_text_name"> android:label="@string/app_widget_text_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -265,7 +226,7 @@
</receiver> </receiver>
<receiver <receiver
android:name=".appwidgets.AppWidgetCard" android:name=".appwidgets.AppWidgetCard"
android:exported="true" android:exported="false"
android:label="@string/app_widget_card_name"> android:label="@string/app_widget_card_name">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -275,36 +236,13 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/app_widget_card_info" /> android:resource="@xml/app_widget_card_info" />
</receiver> </receiver>
<receiver
android:name=".appwidgets.AppWidgetMD3"
android:exported="true"
android:label="@string/app_widget_md3_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_md3_info" />
</receiver>
<receiver
android:name=".appwidgets.AppWidgetCircle"
android:exported="true"
android:label="@string/app_widget_circle_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_circle_info" />
</receiver>
<service <service
android:name=".service.MusicService" android:name=".service.MusicService"
android:enabled="true"
android:exported="true" android:exported="true"
android:foregroundServiceType="mediaPlayback" android:label="@string/app_name"
android:label="@string/app_name"> tools:ignore="ExportedService">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
@ -318,33 +256,15 @@
android:name="com.lge.support.SPLIT_WINDOW" android:name="com.lge.support.SPLIT_WINDOW"
android:value="true" /> android:value="true" />
<!-- Android Auto -->
<meta-data <meta-data
android:name="com.google.android.gms.car.application" android:name="code.name.monkey.retromusic.glide.RetroMusicGlideModule"
android:resource="@xml/automotive_app_desc" /> android:value="GlideModule" />
<meta-data
android:name="com.google.android.gms.car.application.theme"
android:resource="@style/CarTheme" />
<meta-data
android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification" />
<!-- For auto-storage of locale on Android 12 and lower -->
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data <meta-data
android:name="autoStoreLocales" android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" /> android:value="true" />
</service>
</application> </application>
<!--
This is not that important, it's just here so that we can query equalizer package
and check if it's present on A11+ because of Package visibility restrictions.
-->
<queries>
<package android:name="com.android.musicfx" />
</queries>
</manifest> </manifest>

View file

@ -0,0 +1,26 @@
[
{
"name": "Hemanth Savarala",
"summary": "Lead Developer & Designer",
"link": "https://github.com/h4h13",
"profile_image": "https://i.imgur.com/AoVs9oj.jpg"
},
{
"name": "Lennart Glamann",
"summary": "Designer",
"link": "https://t.me/FlixbusLennart",
"profile_image": "https://i.imgur.com/Q5Nsx1R.jpg"
},
{
"name": "GameSpeck",
"summary": "Telegram group maintainer",
"link": "https://t.me/GameSpeck",
"profile_image": "https://imgur.com/B15Kb9a.jpg"
},
{
"name": "Milind Goel",
"summary": "Github & Telegram maintainer",
"link": "https://t.me/MilindGoel15",
"profile_image": "https://i.imgur.com/Bz4De21_d.jpg"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<style media="screen" type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: {link-color};
}
a:active {
color: {link-color-active};
}
ol {
list-style-position: inside;
padding-left: 0;
padding-right: 0;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
Karim Abou Zeid</p>
<p><b><a href="https://github.com/AdrienPoupa/VinylMusicPlayer" title="VinylMusicPlayer">
VinylMusicPlayer</a></b> by Adrien Poupa</p>
<p><b><a href="https://github.com/ReactiveX/RxAndroid" title="RxJava"> RxJava</a></b> by RxJava
authors</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/umano/AndroidSlidingUpPanel" title="Android Sliding Up Panel">
Android Sliding Up Panel</a></b>by The Umano Team</p>
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
title="AOSP Support Libraries"> AOSP Support Libraries</a></b>by AOSP contributors</p>
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</p>
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/hdodenhof/CircleImageView" title="CircleImageView">
CircleImageView</a></b> by Henning Dodenhof</p>
<p><b><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar" title="MaterialProgressBar">
MaterialProgressBar</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/anjlab/android-inapp-billing-v3"
title="Android In-App Billing v3 Library"> Android In-App Billing v3 Library</a></b> by
Henning Dodenhof</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/ksoichiro/Android-ObservableScrollView"
title="Android-ObservableScrollView"> Android-ObservableScrollView</a></b> by Soichiro
Kashima</p>
<p><b><a href="http://www.cufonfonts.com/es/font/14289/circular-std-book" title="Font used"> Font
used(CIRCULAR STD BOOK FONT)</a></b> by NIELSON CAETANO</p>
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p>
</body>
</html>

View file

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<style media="screen" type="text/css">
* {
word-wrap: break-word;
}
{style-placeholder}
a {
color: {link-color};
}
a:active {
color: {link-color-active};
}
ol {
list-style-position: inside;
padding-left: 0;
padding-right: 0;
}
li {
padding-top: 8px;
}
</style>
</head>
<body>
<p><b><a href="https://github.com/kabouzeid/Phonograph" title="Phonograph"> Phonograph</a></b> by
Karim Abou Zeid</p>
<p><b><a href="http://developer.android.com/tools/support-library/index.html"
title="AOSP Support Libraries">AOSP Support Libraries</a></b> by AOSP contributors</p>
<p><b><a href="https://github.com/bumptech/glide" title="Glide"> Glide</a></b> by Sam Judd</p>
<p><b><a href="https://github.com/square/retrofit" title="Retrofit"> Retrofit</a></b> by Square team
</p>
<p><b><a href="http://square.github.io/okhttp/" title="OkHttp"> OkHttp</a></b> by Square team</p>
<p><b><a href="https://github.com/InsertKoinIO/koin"
title="Koin">Koin</a></b> by Arnaud Giuliani</p>
<p><b><a href="https://github.com/afollestad" title="Material Dialogs"> Material Dialogs and Cab</a></b>
by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/afollestad/material-cab" title="Material Contextual Action Bar">
Material Contextual Action Bar</a></b> by Aidan Michael Follestad</p>
<p><b><a href="https://github.com/h6ah4i/android-advancedrecyclerview"
title="Advanced RecyclerView"> Advanced RecyclerView</a></b> by Haruki Hasegawa</p>
<p><b><a href="https://github.com/Ereza/CustomActivityOnCrash"
title="Custom Activity on Crash">Custom Activity on Crash</a></b> by Eduard Ereza Martínez
</p>
<p><b><a href="https://github.com/NanoHttpd/nanohttpd"
title="NanoHttpd">NanoHttpd</a></b> by NanoHttpd Team</p>
<p><b><a href="https://github.com/tankery/CircularSeekBar"
title="Circular Seekbar">Circular Seekbar</a></b> by Tankery</p>
<p><b><a href="https://github.com/Kaned1as/jaudiotagger"
title="jAudioTagger">jAudioTagger</a></b> by Kanedias</p>
<p><b><a href="https://github.com/zhanghai/AndroidFastScroll"
title="Android Fast Scroll">Android Fast Scroll</a></b> by Zhang Hai</p>
<p><b><a href="https://github.com/Dhaval2404/ImagePicker"
title="Image Picker">Image Picker</a></b> by Dhaval Patel</p>
<p><b><a href="https://github.com/heinrichreimer/material-intro"
title="Material Intro">Material Intro</a></b> by Jan Heinrich Reimer</p>
<p><b><a href="https://github.com/r0adkll/Slidr"
title="Slidr">Slidr</a></b> by Drew Heavner</p>
<p><b><a href="https://github.com/bosphere/Android-FadingEdgeLayout"
title="FadingEdgeLayout">FadingEdgeLayout</a></b> by bosphere</p>
<p><b><a href="https://github.com/yshrsmz/KeyboardVisibilityEvent"
title="KeyboardVisibilityEvent">KeyboardVisibilityEvent</a></b> by Yasuhiro SHIMIZU</p>
<p><b><a href="https://github.com/JetradarMobile/android-snowfall"
title="android-snowfall">android-snowfall</a></b> by Jetradar Mobile</p>
<p><b><a href="https://github.com/chrisbanes/insetter"
title="Insetter">Insetter</a></b> by Chris Banes</p>
<p><b><a href="https://materialdesignicons.com" title="Icons"> Icons</a></b> by Austin Andrews</p>
<p><b><a href="https://www.techjuice.pk" title="City wallpaper"> Material Design City Wallpaper</a></b>
</p>
</body>
</html>

View file

@ -1,466 +1 @@
<html> <html> <head> <style type="text/css"> * { word-wrap: break-word; } {style-placeholder} a { color: {link-color}; } a:active { color: {link-color-active}; } ol { list-style-position: inside; padding-left: 0; padding-right: 0; } li { padding-top: 8px; } </style> </head> <body> <h4>v3.5.010</h4> <ul> <li>Added grid size and layout change for songs list</li> <li>Removed menu options (No use of having only settings)</li> <li>Added settings option to toolbar</li> <li>Updated internal library</li> </ul> <h4>v3.4.970</h4> <ul> <li>Added default cover with accent color</li> <li>Added date modified option to sort songs</li> <li>Added toggle bluetooth connection to play on device as soon as it connected</li> <li>Major code refactor for library tabs</li> <li>Folder option now in available in tabs</li> <li>Improved drive mode page</li> <li>Improved fit, full, card themes for status visibility</li> <li>Fix tablet version of app</li> <li>Fix Album and Artist details toolbar full width for better accessibility</li> <li>Updated internal libraries</li> </ul> <h4>v3.4.900</h4> <ul> <li>Added playlist search</li> <li>Added Drive mode</li> <li>Added Album and Artist layout change option in library</li> <li>Added Show more album and artist information in details</li> <li>Added Pixel style scroller bar</li> <li>Added current now playing share</li> <li>Fix font issues and colors in some places</li> <li>Improved Full now playing theme</li> </ul> <p>If you see entire app white or dark or black select same theme in settings to fix </p> <p style="line-height:150%"><a href="https://github.com/h4h13/RetroMusicPlayer/wiki/FAQ">FAQ's</a> </p> <p style="line-height:150%">*If you face any UI related issues you clear app data and cache, if its not working try to uninstall and install again. </p> </body>
<head>
<style type="text/css">
* {
word-wrap: break-word;
}
div {
margin: 20px 10px;
padding: 10px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
}
h2 {
margin-block-end: 0rem;
margin-block-start: 0rem;
display: inline-block;
vertical-align: center;
}
li {
font-size: 0.85rem;
padding: 0.5rem 0;
}
ul {
padding-left: 1.5rem;
margin-top: 0rem;
margin-bottom: 0rem;
}
p {
font-size: 0.8rem;
}
span {
font-size: 0.7rem;
}
h5 {
margin-block-start: 0;
margin-block-end: 0.5rem;
}
h3 {
margin: 10px 0px;
font-size: 1rem;
}
.tag {
border-radius: 5px;
margin-left: 10px;
padding: 5px;
display: inline-block;
vertical-align: top;
}
{style-placeholder}
</style>
</head>
<body>
<div>
<h5>March 30, 2023</h5>
<h2>v6.1.0</h2>
<h3>What's New</h3>
<ul>
<li>App now targets Android 13, support for Granular media permissions, Photo picker, Per-app language preferences & Predictive back gesture</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed playlist reordering crash</li>
<li>Other minor bugs fixes and improvements</li>
</ul>
</div>
<div>
<h5>March 13, 2023</h5>
<h2>v6.0.4</h2>
<h3>What's New</h3>
<ul>
<li>Minor redesign in Playlist details screen</li>
<li>Updated translations</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed file popup menu actions in Folders tab</li>
<li>Fixed playlist image loading</li>
<li>Fixed blurry album art in Android 13</li>
<li>Minor bug fixes and improvements</li>
</ul>
</div>
<div>
<h5>July 10, 2022</h5>
<h2>v6.0.3<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Migrated icons to Material symbols</li>
</ul>
</div>
<div>
<h5>June 21, 2022</h5>
<h2>v6.0.2<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Minor bug fixes and improvements</li>
</ul>
</div>
<div>
<h5>June 13, 2022</h5>
<h2>v6.0.1<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Fixed ChromeCast crash</li>
<li>Fixed Slider crashes</li>
<li>Fixed storage related crashes on Android 10</li>
<li>Fixed CrossFade not working Fade Audio is not working</li>
</ul>
</div>
<div>
<h5>June 7, 2022</h5>
<h2>v6.0.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Better Cast</li>
<li>Mini player in settings screen</li>
<li>Changed Seekbar with Sliders</li>
<li>Added NavigationRailView for Landscape</li>
<li>Show remaining time in Sleep timer dialog</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Top/Recent Artists/Albums not updating (Wrong sort order)</li>
<li>Fixed all Blacklist related crashes</li>
<li>Fix restart button not working in crash activity</li>
</ul>
</div>
<div>
<h5>May 25, 2022</h5>
<h2>v5.8.5</h2>
<h3>What's New</h3>
<ul>
<li>Display song images along in the artist and album details pages</li>
<li>Removed the Internet permissions and all the associated features</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed crashing occurs during changing screen orientation</li>
</ul>
</div>
<div>
<h5>May 13, 2022</h5>
<h2>v5.8.4</h2>
<h3>What's New</h3>
<ul>
<li>Added a toggle to enable/disable swipe down to dismiss mini player</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playback speed and pitch not working when CrossFade is enabled</li>
<li>Fixed crash when adding folders to blacklist</li>
<li>Fix bugs in MD3 theme</li>
</ul>
</div>
<div>
<h5>May 07, 2022</h5>
<h2>v5.8.3</h2>
<h3>What's New</h3>
<ul>
<li>Added a new MD3 now playing theme</li>
<li>Swipe down to dismiss Mini player</li>
<li>Add support for Just Black with Material You</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed sharing of files from SD Card</li>
<li>Fix ChromeCast crash and bugs</li>
<li>Fix Audio Crossfade</li>
<li>Tried to fix incorrect song data in notification</li>
</ul>
</div>
<div>
<h5>April 8, 2022</h5>
<h2>v5.8.0</h2>
<h3>What's New</h3>
<ul>
<li>Updated translations</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Classic Notification crash</li>
<li>Fixed crash when clicking on Playlist in the Search Tab</li>
<li>Fixed settings change not reflecting immediately</li>
<li>Fixed shuffle</li>
<li>Fixed Song duration not visible in Card & Blur card themes</li>
<li>Fixed some Album skip styles</li>
<li>Minor bug fixes & UI improvements</li>
</ul>
</div>
<div>
<h5>March 13, 2022</h5>
<h2>v5.7.3</h2>
<h3>What's New</h3>
<ul>
<li>Added adaptive color in Material now playing theme</li>
<li>Added an option to share crash report</li>
<li>Added options to clear, pause history</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Adapt Wallpaper accent for better readability</li>
<li>Optimized search</li>
<li>Made sleep timer precise</li>
</ul>
</div>
<div>
<h5>February 13, 2022</h5>
<h2>v5.7.2<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Animated splash screen on Android 12 devices</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed crash when removing song from Playing queue</li>
<li>Fixed lyrics editing crash</li>
<li>Fixed shuffle button not working</li>
<li>Fixed crash on song deletion</li>
</ul>
</div>
<div>
<h5>February 1, 2022</h5>
<h2>v5.7.1<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added option to disable changing song by swiping anywhere on the now playing screen</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playlist save on A11+</li>
<li>Fixed Just Black theme</li>
<li>Fixed some UI issues</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improvements to search</li>
</ul>
</div>
<div>
<h5>January 25, 2022</h5>
<h2>v5.7.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Android Auto</li>
<li>Added accent color extraction on Android 8.1+ devices</li>
<li>Added new Circle widget</li>
<li>Added Collapsing appbar to library tabs with an option to switch back to simple appbar
</li>
<li>Added Search tab</li>
<li>Added option to use circular play button</li>
<li>Added lyrics editing on A11+ devices again</li>
<li>Added Long Press to forward, rewind current song</li>
<li>Added ability to set Playback speed and pitch</li>
<li>Added option to show lyrics over Cover</li>
<li>Added option to keep screen on when showing lyrics</li>
<li>Added option to switch to Manrope font</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Gapless Playback</li>
<li>Fixed Shuffle FAB going behind Mini Player</li>
<li>Fixed crashes on Pre-marshmallow devices</li>
<li>Blacklisted songs can't be played after opening from outside the app</li>
<li>Fixed various small bugs and some minor improvements</li>
</ul>
</div>
<div>
<h5>January 1, 2021</h5>
<h2>v5.6.1</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed artist covers not updating and showing album cover images.</li>
<li>Fixed FAB's not visible (Shuffle, Save, etc.)</li>
<li>Fixed a crash when a Song is deleted in Artist Details</li>
<li>Fixed Snowfall effect</li>
<li>Fixed empty notification when queue is cleared</li>
</ul>
</div>
<div>
<h5>December 25, 2021</h5>
<h2>v5.6.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Artwork editing for songs</li>
<li>Circle theme has album art now</li>
<li>Upgraded tag editor library, we should support reading tags of more formats now e.g.
opus.
</li>
<li>Added Snowfall effect</li>
<li>Crossfade effect for background when Song is changed for Blur, Blur card themes</li>
<li>Album art is hidden when Show lyrics is enabled</li>
<li>Added fastscroll in Playlists tab</li>
<li>Added Chooser to choose what to restore</li>
<li>Hide BottomNavigation when only one tab is selected in Library Categories(This was
already there but when changing screens it was getting visible)
</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Ringtone crash</li>
<li>Fixed blank album cover bug</li>
<li>Fixed bottom navigation visible in Playing Queue</li>
<li>Fixed lockscreen dragging glitch</li>
<li>Fixed incorrect colors when no cover art is available</li>
<li>Fixed favorite not updating when song is changed</li>
<li>Fixed playlist not getting created & playlist creation crash with same name</li>
<li>Fixed a bug in "Plain" Now playing theme where onClick event is consumed by the views
behind the bottom sheet
</li>
</ul>
</div>
<div>
<h5>December 6, 2021</h5>
<h2>v5.4.2<span class="tag"><i>Beta</i></span></h2>
<h3>Fixed</h3>
<ul>
<li>Bug Fixes</li>
</ul>
</div>
<div>
<h5>December 1, 2021</h5>
<h2>v5.4.1<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added file selection from system file picker for restore</li>
<li>Added Grid size selection for Playlists</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Enable Material You by default on Android 12</li>
<li>Reworked Grid Style saving</li>
<li>Improved Playlist preview images</li>
<li>UI improvements</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Fixed File deletion on Android 10</li>
<li>Fixed Sleep Timer crash</li>
<li>Fixed Bottom Toolbar not clickable in now playing when Shuffle is clicked</li>
<li>Fixed Album Artist sort order</li>
<li>Fixed a crash when clicking the "Clear All" button in the Blacklist folder selection
</li>
<li>Fixed Continuous crashes on A12 when the song ends</li>
<li>Fixed New Music Mix multiple clicks crash</li>
</ul>
</div>
<div>
<h5>November 22, 2021</h5>
<h2>v5.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li><b>Material You</b></li>
<li>Going Edge-to-Edge</li>
<li>Added Backup & restore</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved Animations</li>
<li>Improved Crossfade</li>
</ul>
</div>
<div>
<h5>September 06, 2021</h5>
<h2>v5.0.0</h2>
<h3>What's New</h3>
<ul>
<li>Added ability to Remember last tab</li>
<li>Added Whitelisting songs</li>
<li>You can now browse SDCard from Folders Tab</li>
</ul>
</div>
<div>
<h5>August 22, 2021</h5>
<h2>v4.4.0<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added Crossfade</li>
<li>Multi-select in Album and Artist Details</li>
<li>Albums now show Album Artists instead of artist of first song</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Playlist Preview Images</li>
</ul>
</div>
<div>
<h5>August 11, 2021</h5>
<h2>v4.2.30<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Revamped Playlist Tab</li>
<li>Revamped Genres Tab</li>
<li>Added support for embedded Synced Lyrics</li>
<li>Added animated icons</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed Language Switching</li>
<li>Fixed some reported bugs</li>
</ul>
</div>
<div>
<h5>July 18, 2021</h5>
<h2>v4.2.020<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Added ChromeCast Support</li>
<li>Added Lyrics Editor for Normal and Synced Lyrics</li>
<li>Added Ripple Animation for Color Theme</li>
<li>Added Drag to seek in Tiny Theme</li>
<li>Added Playlist Order</li>
<li>Added Search Filters</li>
<li>Added Audio Fade</li>
<li>Synced Lyrics in all Themes</li>
<li>Swipe anywhere to change song</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Fixed Navigate by Album Artist</li>
<li>Changed New Music Mix Actions</li>
<li>Improved Animations</li>
<li>And some minor bug fixes and improvements</li>
</ul>
</div>
<div>
<h5>October 12, 2020</h5>
<h2>v4.0.10</h2>
<h3>What's New</h3>
<ul>
<li>Re-built from scratch using MVVM Architecture and JetPack Components</li>
<li>New Material Design icon</li>
<li>Implemented a custom database for playlists</li>
<li>Added new Material Design motions</li>
<li>Bug fixes & performance improvements</li>
<li>Revamped Home tab UI</li>
<li>Android 11 support</li>
<li>And more!</li>
</ul>
</div>
<div>
<h5>April 30, 2020</h5>
<h2>v3.5.110<span class="tag"><i>Beta</i></span></h2>
<h3>What's New</h3>
<ul>
<li>Changed profile form image to icon</li>
<li>New what's new screen</li>
<li>Added In-App language changer, where you can select language</li>
</ul>
<h3>Improved</h3>
<ul>
<li>Improved loading of Songs, Albums, Artists, Genres, Playlists</li>
</ul>
</div>
</body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

View file

@ -1,67 +1,78 @@
/* /*
* Copyright (c) 2020 Hemanth Savarla. * Copyright (c) 2019 Hemanth Savarala.
* *
* Licensed under the GNU General Public License v3 * Licensed under the GNU General Public License v3
* *
* This is free software: you can redistribute it and/or modify it * This is free software: you can redistribute it and/or modify it under
* under the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
* *
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
*
*/ */
package code.name.monkey.retromusic package code.name.monkey.retromusic
import android.app.Application import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.multidex.MultiDexApplication
import cat.ereza.customactivityoncrash.config.CaocConfig
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.activities.ErrorActivity
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.helper.WallpaperAccentManager import code.name.monkey.retromusic.dagger.DaggerMusicComponent
import org.koin.android.ext.koin.androidContext import code.name.monkey.retromusic.dagger.MusicComponent
import org.koin.core.context.startKoin import code.name.monkey.retromusic.dagger.module.AppModule
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
class App : Application() { class App : MultiDexApplication() {
private val wallpaperAccentManager = WallpaperAccentManager(this) lateinit var billingProcessor: BillingProcessor
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this instance = this
musicComponent = initDagger(this)
startKoin {
androidContext(this@App)
modules(appModules)
}
// default theme // default theme
if (!ThemeStore.isConfigured(this, 3)) { if (!ThemeStore.isConfigured(this, 3)) {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)
.accentColorRes(code.name.monkey.appthemehelper.R.color.md_deep_purple_A200) .accentColorRes(R.color.md_deep_purple_A200)
.coloredNavigationBar(true) .coloredNavigationBar(true)
.commit() .commit()
} }
wallpaperAccentManager.init()
if (VersionUtils.hasNougatMR()) if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).initDynamicShortcuts() DynamicShortcutManager(this).initDynamicShortcuts()
// setting Error activity // automatically restores purchases
CaocConfig.Builder.create().errorActivity(ErrorActivity::class.java) billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
.restartActivity(MainActivity::class.java).apply() object : BillingProcessor.IBillingHandler {
override fun onProductPurchased(productId: String, details: TransactionDetails?) {}
// Set Default values for now playing preferences override fun onPurchaseHistoryRestored() {
// This will reduce startup time for now playing settings fragment as Preference listener of AbsSlidingMusicPanelActivity won't be called Toast.makeText(
PreferenceManager.setDefaultValues(this, R.xml.pref_now_playing_screen, false) this@App,
R.string.restored_previous_purchase_please_restart,
Toast.LENGTH_LONG
)
.show()
} }
override fun onBillingError(errorCode: Int, error: Throwable?) {}
override fun onBillingInitialized() {}
})
}
private fun initDagger(app: App): MusicComponent =
DaggerMusicComponent.builder()
.appModule(AppModule(app))
.build()
override fun onTerminate() { override fun onTerminate() {
super.onTerminate() super.onTerminate()
wallpaperAccentManager.release() billingProcessor.release()
} }
companion object { companion object {
@ -70,5 +81,15 @@ class App : Application() {
fun getContext(): App { fun getContext(): App {
return instance!! return instance!!
} }
fun isProVersion(): Boolean {
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased(
PRO_VERSION_PRODUCT_ID
)
}
lateinit var musicComponent: MusicComponent
const val PRO_VERSION_PRODUCT_ID = "pro_version"
} }
} }

View file

@ -1,154 +1,53 @@
/* /*
* Copyright (c) 2020 Hemanth Savarla. * Copyright (c) 2019 Hemanth Savarala.
* *
* Licensed under the GNU General Public License v3 * Licensed under the GNU General Public License v3
* *
* This is free software: you can redistribute it and/or modify it * This is free software: you can redistribute it and/or modify it under
* under the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
* *
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
*
*/ */
package code.name.monkey.retromusic package code.name.monkey.retromusic
import android.provider.BaseColumns import android.provider.BaseColumns
import android.provider.MediaStore import android.provider.MediaStore
object Constants { object Constants {
const val TRANSLATE = "https://crowdin.com/project/retromusicplayer"
const val GITHUB_PROJECT = "https://github.com/MuntashirAkon/Metro" const val RATE_ON_GOOGLE_PLAY =
const val TELEGRAM_CHANGE_LOG = "https://t.me/AppManagerChannel" "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"
const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
const val USER_PROFILE = "profile.jpg" const val USER_PROFILE = "profile.jpg"
const val USER_BANNER = "banner.jpg" const val USER_BANNER = "banner.jpg"
const val FAQ_LINK = "https://github.com/MuntashirAkon/Metro/blob/master/FAQ.md" const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
const val IS_MUSIC = const val BASE_SELECTION =
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''" MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
const val DATA = "_data"
@Suppress("Deprecation")
val baseProjection = arrayOf( val baseProjection = arrayOf(
BaseColumns._ID, // 0 BaseColumns._ID, // 0
MediaStore.Audio.AudioColumns.TITLE, // 1 MediaStore.Audio.AudioColumns.TITLE, // 1
MediaStore.Audio.AudioColumns.TRACK, // 2 MediaStore.Audio.AudioColumns.TRACK, // 2
MediaStore.Audio.AudioColumns.YEAR, // 3 MediaStore.Audio.AudioColumns.YEAR, // 3
MediaStore.Audio.AudioColumns.DURATION, // 4 MediaStore.Audio.AudioColumns.DURATION, // 4
DATA, // 5 MediaStore.Audio.AudioColumns.DATA, // 5
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6 MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7 MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
MediaStore.Audio.AudioColumns.ALBUM, // 8 MediaStore.Audio.AudioColumns.ALBUM, // 8
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9 MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST,// 10 MediaStore.Audio.AudioColumns.ARTIST,// 10
MediaStore.Audio.AudioColumns.COMPOSER, // 11 MediaStore.Audio.AudioColumns.COMPOSER
ALBUM_ARTIST // 12 )// 11
)
const val NUMBER_OF_TOP_TRACKS = 99 const val NUMBER_OF_TOP_TRACKS = 99
} }
const val EXTRA_PLAYLIST_TYPE = "type"
const val EXTRA_GENRE = "extra_genre"
const val EXTRA_PLAYLIST = "extra_playlist"
const val EXTRA_PLAYLIST_ID = "extra_playlist_id"
const val EXTRA_ALBUM_ID = "extra_album_id"
const val EXTRA_ARTIST_ID = "extra_artist_id"
const val EXTRA_SONG = "extra_songs"
const val EXTRA_PLAYLISTS = "extra_playlists"
const val LIBRARY_CATEGORIES = "library_categories"
const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color"
const val BLACK_THEME = "black_theme"
const val KEEP_SCREEN_ON = "keep_screen_on"
const val TOGGLE_HOME_BANNER = "toggle_home_banner"
const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification"
const val CLASSIC_NOTIFICATION = "classic_notification"
const val GAP_LESS_PLAYBACK = "gapless_playback"
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount"
const val TOGGLE_HEADSET = "toggle_headset"
const val GENERAL_THEME = "general_theme"
const val ACCENT_COLOR = "accent_color"
const val SHOULD_COLOR_APP_SHORTCUTS = "should_color_app_shortcuts"
const val CIRCULAR_ALBUM_ART = "circular_album_art"
const val USER_NAME = "user_name"
const val TOGGLE_FULL_SCREEN = "toggle_full_screen"
const val TOGGLE_VOLUME = "toggle_volume"
const val ADAPTIVE_COLOR_APP = "adaptive_color_app"
const val HOME_ARTIST_GRID_STYLE = "home_artist_grid_style"
const val HOME_ALBUM_GRID_STYLE = "home_album_grid_style"
const val TOGGLE_ADD_CONTROLS = "toggle_add_controls"
const val ALBUM_COVER_STYLE = "album_cover_style_id"
const val ALBUM_COVER_TRANSFORM = "album_cover_transform"
const val TAB_TEXT_MODE = "tab_text_mode"
const val LANGUAGE_NAME = "language_name"
const val LOCALE_AUTO_STORE_ENABLED = "locale_auto_store_enabled"
const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
const val ALBUM_GRID_STYLE = "album_grid_style_home"
const val ARTIST_GRID_STYLE = "artist_grid_style_home"
const val SAF_SDCARD_URI = "saf_sdcard_uri"
const val SONG_SORT_ORDER = "song_sort_order"
const val SONG_GRID_SIZE = "song_grid_size"
const val GENRE_SORT_ORDER = "genre_sort_order"
const val BLUETOOTH_PLAYBACK = "bluetooth_playback"
const val INITIALIZED_BLACKLIST = "initialized_blacklist"
const val ARTIST_SORT_ORDER = "artist_sort_order"
const val ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order"
const val ALBUM_SORT_ORDER = "album_sort_order"
const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
const val ALBUM_SONG_SORT_ORDER = "album_song_sort_order"
const val ARTIST_SONG_SORT_ORDER = "artist_song_sort_order"
const val ALBUM_GRID_SIZE = "album_grid_size"
const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
const val ARTIST_GRID_SIZE = "artist_grid_size"
const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
const val PLAYLIST_GRID_SIZE = "playlist_grid_size"
const val PLAYLIST_GRID_SIZE_LAND = "playlist_grid_size_land"
const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
const val LAST_ADDED_CUTOFF = "last_added_interval"
const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
const val LAST_CHANGELOG_VERSION = "last_changelog_version"
const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen"
const val ALBUM_ARTISTS_ONLY = "album_artists_only"
const val ALBUM_ARTIST = "album_artist"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer"
const val EQUALIZER = "equalizer"
const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song"
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"
const val EXTRA_ARTIST_NAME = "extra_artist_name"
const val TOGGLE_SUGGESTIONS = "toggle_suggestions"
const val AUDIO_FADE_DURATION = "audio_fade_duration"
const val CROSS_FADE_DURATION = "cross_fade_duration"
const val SHOW_LYRICS = "show_lyrics"
const val REMEMBER_LAST_TAB = "remember_last_tab"
const val LAST_USED_TAB = "last_used_tab"
const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall"
const val LYRICS_TYPE = "lyrics_type"
const val PLAYBACK_SPEED = "playback_speed"
const val PLAYBACK_PITCH = "playback_pitch"
const val CUSTOM_FONT = "custom_font"
const val APPBAR_MODE = "appbar_mode"
const val WALLPAPER_ACCENT = "wallpaper_accent"
const val SCREEN_ON_LYRICS = "screen_on_lyrics"
const val CIRCLE_PLAY_BUTTON = "circle_play_button"
const val SWIPE_ANYWHERE_NOW_PLAYING = "swipe_anywhere_now_playing"
const val PAUSE_HISTORY = "pause_history"
const val MANAGE_AUDIO_FOCUS = "manage_audio_focus"
const val SWIPE_DOWN_DISMISS = "swipe_to_dismiss"

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic
import androidx.annotation.IntDef
@IntDef(
RECENT_ALBUMS,
TOP_ALBUMS,
RECENT_ARTISTS,
TOP_ARTISTS,
SUGGESTIONS,
FAVOURITES,
GENRES,
PLAYLISTS
)
@Retention(AnnotationRetention.SOURCE)
annotation class HomeSection
const val RECENT_ALBUMS = 3
const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 0
const val SUGGESTIONS = 5
const val FAVOURITES = 4
const val GENRES = 6
const val PLAYLISTS = 7
const val HISTORY_PLAYLIST = 8
const val LAST_ADDED_PLAYLIST = 9
const val TOP_PLAYED_PLAYLIST = 10

View file

@ -0,0 +1,42 @@
package code.name.monkey.retromusic;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.LocaleList;
import java.util.Locale;
import code.name.monkey.appthemehelper.util.VersionUtils;
public class LanguageContextWrapper extends android.content.ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.INSTANCE.hasNougatMR()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.INSTANCE.hasLollipop()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new LanguageContextWrapper(context);
}
}

View file

@ -1,156 +0,0 @@
package code.name.monkey.retromusic
import androidx.room.Room
import code.name.monkey.retromusic.auto.AutoMusicProvider
import code.name.monkey.retromusic.db.MIGRATION_23_24
import code.name.monkey.retromusic.db.RetroDatabase
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.repository.*
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind
import org.koin.dsl.module
private val roomModule = module {
single {
Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
.addMigrations(MIGRATION_23_24)
.build()
}
factory {
get<RetroDatabase>().playlistDao()
}
factory {
get<RetroDatabase>().playCountDao()
}
factory {
get<RetroDatabase>().historyDao()
}
single {
RealRoomRepository(get(), get(), get())
} bind RoomRepository::class
}
private val autoModule = module {
single {
AutoMusicProvider(
androidContext(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
}
private val mainModule = module {
single {
androidContext().contentResolver
}
}
private val dataModule = module {
single {
RealRepository(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
)
} bind Repository::class
single {
RealSongRepository(get())
} bind SongRepository::class
single {
RealGenreRepository(get(), get())
} bind GenreRepository::class
single {
RealAlbumRepository(get())
} bind AlbumRepository::class
single {
RealArtistRepository(get(), get())
} bind ArtistRepository::class
single {
RealPlaylistRepository(get())
} bind PlaylistRepository::class
single {
RealTopPlayedRepository(get(), get(), get(), get())
} bind TopPlayedRepository::class
single {
RealLastAddedRepository(
get(),
get(),
get()
)
} bind LastAddedRepository::class
single {
RealSearchRepository(
get(),
get(),
get(),
get(),
get()
)
}
}
private val viewModules = module {
viewModel {
LibraryViewModel(get())
}
viewModel { (albumId: Long) ->
AlbumDetailsViewModel(
get(),
albumId
)
}
viewModel { (artistId: Long?, artistName: String?) ->
ArtistDetailsViewModel(
get(),
artistId,
artistName
)
}
viewModel { (playlistId: Long) ->
PlaylistDetailsViewModel(
get(),
playlistId
)
}
viewModel { (genre: Genre) ->
GenreDetailsViewModel(
get(),
genre
)
}
}
val appModules = listOf(mainModule, dataModule, autoModule, viewModules, roomModule)

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic
/**
* Created by hemanths on 2019-10-23.
*/
sealed class Result<out T : Any> {
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable) : Result<Nothing>()
}

View file

@ -0,0 +1,171 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.core.app.ShareCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants.APP_INSTAGRAM_LINK
import code.name.monkey.retromusic.Constants.APP_TELEGRAM_LINK
import code.name.monkey.retromusic.Constants.APP_TWITTER_LINK
import code.name.monkey.retromusic.Constants.FAQ_LINK
import code.name.monkey.retromusic.Constants.GITHUB_PROJECT
import code.name.monkey.retromusic.Constants.PINTEREST
import code.name.monkey.retromusic.Constants.RATE_ON_GOOGLE_PLAY
import code.name.monkey.retromusic.Constants.TELEGRAM_CHANGE_LOG
import code.name.monkey.retromusic.Constants.TRANSLATE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.adapter.ContributorAdapter
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.list.listItems
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.android.synthetic.main.activity_about.*
import kotlinx.android.synthetic.main.card_credit.*
import kotlinx.android.synthetic.main.card_other.*
import kotlinx.android.synthetic.main.card_retro_info.*
import kotlinx.android.synthetic.main.card_social.*
import java.io.IOException
import java.nio.charset.StandardCharsets
class AboutActivity : AbsBaseActivity(), View.OnClickListener {
private val contributorsJson: String?
get() {
val json: String
try {
val inputStream = assets.open("contributors.json")
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
inputStream.close()
json = String(buffer, StandardCharsets.UTF_8)
} catch (ex: IOException) {
ex.printStackTrace()
return null
}
return json
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
version.setSummary(getAppVersion())
setUpView()
loadContributors()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun openUrl(url: String) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
}
private fun setUpView() {
appGithub.setOnClickListener(this)
faqLink.setOnClickListener(this)
telegramLink.setOnClickListener(this)
appRate.setOnClickListener(this)
appTranslation.setOnClickListener(this)
appShare.setOnClickListener(this)
donateLink.setOnClickListener(this)
instagramLink.setOnClickListener(this)
twitterLink.setOnClickListener(this)
changelog.setOnClickListener(this)
openSource.setOnClickListener(this)
pinterestLink.setOnClickListener(this)
bugReportLink.setOnClickListener(this)
}
override fun onClick(view: View) {
when (view.id) {
R.id.pinterestLink -> openUrl(PINTEREST)
R.id.faqLink -> openUrl(FAQ_LINK)
R.id.telegramLink -> openUrl(APP_TELEGRAM_LINK)
R.id.appGithub -> openUrl(GITHUB_PROJECT)
R.id.appTranslation -> openUrl(TRANSLATE)
R.id.appRate -> openUrl(RATE_ON_GOOGLE_PLAY)
R.id.appShare -> shareApp()
R.id.donateLink -> NavigationUtil.goToSupportDevelopment(this)
R.id.instagramLink -> openUrl(APP_INSTAGRAM_LINK)
R.id.twitterLink -> openUrl(APP_TWITTER_LINK)
R.id.changelog -> showChangeLogOptions()
R.id.openSource -> NavigationUtil.goToOpenSource(this)
R.id.bugReportLink -> NavigationUtil.bugReport(this)
}
}
private fun showChangeLogOptions() {
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
cornerRadius(PreferenceUtil.getInstance(this@AboutActivity).dialogCorner)
listItems(items = listOf("Telegram Channel", "App")) { _, position, _ ->
if (position == 0) {
openUrl(TELEGRAM_CHANGE_LOG)
} else {
NavigationUtil.gotoWhatNews(this@AboutActivity)
}
}
}
}
private fun getAppVersion(): String {
return try {
val isPro = if (App.isProVersion()) "Pro" else "Free"
val packageInfo = packageManager.getPackageInfo(packageName, 0)
"${packageInfo.versionName} $isPro"
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"0.0.0"
}
}
private fun shareApp() {
ShareCompat.IntentBuilder.from(this).setType("text/plain")
.setChooserTitle(R.string.share_app)
.setText(String.format(getString(R.string.app_share), packageName)).startChooser()
}
private fun loadContributors() {
val type = object : TypeToken<List<Contributor>>() {
}.type
val contributors = Gson().fromJson<List<Contributor>>(contributorsJson, type)
val contributorAdapter = ContributorAdapter(contributors)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = contributorAdapter
}
}

View file

@ -0,0 +1,396 @@
package code.name.monkey.retromusic.activities
import android.app.ActivityOptions
import android.content.Intent
import android.os.Bundle
import android.transition.Slide
import android.view.Menu
import android.view.MenuItem
import android.view.SubMenu
import android.view.View
import android.widget.ImageView
import androidx.core.app.ActivityCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsView
import code.name.monkey.retromusic.rest.model.LastFmAlbum
import code.name.monkey.retromusic.util.*
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_album.*
import kotlinx.android.synthetic.main.activity_album_content.*
import java.util.*
import javax.inject.Inject
import android.util.Pair as UtilPair
class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) it.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
)
.start(callback)
return cab as MaterialCab
}
private lateinit var simpleSongAdapter: SimpleSongAdapter
private lateinit var album: Album
private lateinit var artistImage: ImageView
private var cab: MaterialCab? = null
private val savedSortOrder: String
get() = PreferenceUtil.getInstance(this).albumDetailSongSortOrder
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_album)
}
@Inject
lateinit var albumDetailsPresenter: AlbumDetailsPresenter
private fun windowEnterTransition() {
val slide = Slide()
slide.excludeTarget(R.id.appBarLayout, true)
slide.excludeTarget(R.id.status_bar, true)
slide.excludeTarget(android.R.id.statusBarBackground, true)
slide.excludeTarget(android.R.id.navigationBarBackground, true)
window.enterTransition = slide
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
toggleBottomNavigationView(true)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
window.sharedElementsUseOverlay = true
App.musicComponent.inject(this)
albumDetailsPresenter.attachView(this)
if (intent.extras!!.containsKey(EXTRA_ALBUM_ID)) {
intent.extras?.getInt(EXTRA_ALBUM_ID)?.let {
albumDetailsPresenter.loadAlbum(it)
albumCoverContainer?.transitionName =
"${getString(R.string.transition_album_art)}_$it"
}
} else {
finish()
}
windowEnterTransition()
ActivityCompat.postponeEnterTransition(this)
artistImage = findViewById(R.id.artistImage)
setupRecyclerView()
artistImage.setOnClickListener {
val artistPairs = ActivityOptions.makeSceneTransitionAnimation(
this,
UtilPair.create(
artistImage,
"${getString(R.string.transition_artist_image)}_${album.artistId}"
)
)
NavigationUtil.goToArtistOptions(this, album.artistId, artistPairs)
}
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(album.songs!!, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(album.songs!!, true) }
}
aboutAlbumText.setOnClickListener {
if (aboutAlbumText.maxLines == 4) {
aboutAlbumText.maxLines = Integer.MAX_VALUE
} else {
aboutAlbumText.maxLines = 4
}
}
}
private fun setupRecyclerView() {
simpleSongAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, this)
recyclerView.apply {
layoutManager = LinearLayoutManager(this@AlbumDetailsActivity)
itemAnimator = DefaultItemAnimator()
isNestedScrollingEnabled = false
adapter = simpleSongAdapter
}
}
override fun onDestroy() {
super.onDestroy()
albumDetailsPresenter.detachView()
}
override fun complete() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun album(album: Album) {
complete()
if (album.songs!!.isEmpty()) {
finish()
return
}
this.album = album
albumTitle.text = album.title
if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format(
"%s • %s",
album.artistName,
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
)
} else {
albumText.text = String.format(
"%s • %s • %s",
album.artistName,
MusicUtil.getYearString(album.year),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
)
}
loadAlbumCover()
simpleSongAdapter.swapDataSet(album.songs)
albumDetailsPresenter.loadMore(album.artistId)
albumDetailsPresenter.aboutAlbum(album.artistName!!, album.title!!)
}
override fun moreAlbums(albums: List<Album>) {
moreTitle.show()
moreRecyclerView.show()
moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName)
val albumAdapter = HorizontalAlbumAdapter(this, albums, null)
moreRecyclerView.layoutManager = GridLayoutManager(
this,
1,
GridLayoutManager.HORIZONTAL,
false
)
moreRecyclerView.adapter = albumAdapter
}
override fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) {
if (lastFmAlbum.album.wiki != null) {
aboutAlbumText.show()
aboutAlbumTitle.show()
aboutAlbumTitle.text = String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
aboutAlbumText.text = lastFmAlbum.album.wiki.content
}
if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
}
}
}
override fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(this), artist)
.generatePalette(this)
.build()
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(artistImage) {
override fun onColorReady(color: Int) {
}
})
}
private fun loadAlbumCover() {
AlbumGlideRequest.Builder.from(Glide.with(this), album.safeGetFirstSong())
.checkIgnoreMediaStore(this)
.ignoreMediaStore(PreferenceUtil.getInstance(this).ignoreMediaStoreArtwork())
.generatePalette(this)
.build()
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
val textColor = if (PreferenceUtil.getInstance(this).adaptiveColor)
color.ripAlpha()
else
ATHUtil.resolveColor(this, android.R.attr.textColorPrimary)
songTitle.setTextColor(textColor)
moreTitle.setTextColor(textColor)
aboutAlbumTitle.setTextColor(textColor)
val buttonColor = if (PreferenceUtil.getInstance(this).adaptiveColor)
color.ripAlpha()
else
ATHUtil.resolveColor(this, R.attr.colorSurface)
MaterialUtil.setTint(button = shuffleAction, color = buttonColor)
MaterialUtil.setTint(button = playAction, color = buttonColor)
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
setSupportActionBar(toolbar)
supportActionBar?.title = null
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_album_detail, menu)
val sortOrder = menu.findItem(R.id.action_sort_order)
setUpSortOrderMenu(sortOrder.subMenu)
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
this,
toolbar,
menu,
getToolbarBackgroundColor(toolbar)
)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
var sortOrder: String? = null
val songs = simpleSongAdapter.dataSet
when (item.itemId) {
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_delete_from_device -> {
DeleteSongsDialog.create(songs).show(supportFragmentManager, "DELETE_SONGS")
return true
}
android.R.id.home -> {
super.onBackPressed()
return true
}
R.id.action_tag_editor -> {
val intent = Intent(this, AlbumTagEditorActivity::class.java)
intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album.id)
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
albumCoverContainer,
"${getString(R.string.transition_album_art)}_${album.id}"
)
startActivityForResult(intent, TAG_EDITOR_REQUEST, options.toBundle())
return true
}
/*Sort*/
R.id.action_sort_order_title -> sortOrder = AlbumSongSortOrder.SONG_A_Z
R.id.action_sort_order_title_desc -> sortOrder = AlbumSongSortOrder.SONG_Z_A
R.id.action_sort_order_track_list -> sortOrder = AlbumSongSortOrder.SONG_TRACK_LIST
R.id.action_sort_order_artist_song_duration -> sortOrder =
AlbumSongSortOrder.SONG_DURATION
}
if (sortOrder != null) {
item.isChecked = true
setSaveSortOrder(sortOrder)
}
return true
}
private fun setUpSortOrderMenu(sortOrder: SubMenu) {
when (savedSortOrder) {
AlbumSongSortOrder.SONG_A_Z -> sortOrder.findItem(R.id.action_sort_order_title)
.isChecked = true
AlbumSongSortOrder.SONG_Z_A -> sortOrder.findItem(R.id.action_sort_order_title_desc)
.isChecked = true
AlbumSongSortOrder.SONG_TRACK_LIST -> sortOrder.findItem(R.id.action_sort_order_track_list)
.isChecked =
true
AlbumSongSortOrder.SONG_DURATION -> sortOrder.findItem(R.id.action_sort_order_artist_song_duration)
.isChecked = true
}
}
private fun setSaveSortOrder(sortOrder: String?) {
PreferenceUtil.getInstance(this).albumDetailSongSortOrder = sortOrder
reload()
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
reload()
}
private fun reload() {
if (intent.extras!!.containsKey(EXTRA_ALBUM_ID)) {
intent.extras?.getInt(EXTRA_ALBUM_ID)?.let { albumDetailsPresenter.loadAlbum(it) }
} else {
finish()
}
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) {
cab?.finish()
} else {
super.onBackPressed()
}
}
companion object {
const val EXTRA_ALBUM_ID = "extra_album_id"
private const val TAG_EDITOR_REQUEST = 2001
}
}

View file

@ -0,0 +1,347 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.text.Spanned
import android.transition.Slide
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.text.HtmlCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsView
import code.name.monkey.retromusic.rest.model.LastFmArtist
import code.name.monkey.retromusic.util.*
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_artist_content.*
import kotlinx.android.synthetic.main.activity_artist_details.*
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView, CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) it.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
)
.start(callback)
return cab as MaterialCab
}
private var cab: MaterialCab? = null
private var biography: Spanned? = null
private lateinit var artist: Artist
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_artist_details)
}
@Inject
lateinit var artistDetailsPresenter: ArtistDetailsPresenter
private fun windowEnterTransition() {
val slide = Slide()
slide.excludeTarget(R.id.appBarLayout, true)
slide.excludeTarget(R.id.status_bar, true)
slide.excludeTarget(android.R.id.statusBarBackground, true)
slide.excludeTarget(android.R.id.navigationBarBackground, true)
window.enterTransition = slide
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
toggleBottomNavigationView(true)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
window.sharedElementsUseOverlay = true
App.musicComponent.inject(this)
artistDetailsPresenter.attachView(this)
if (intent.extras!!.containsKey(EXTRA_ARTIST_ID)) {
intent.extras?.getInt(EXTRA_ARTIST_ID)?.let {
artistDetailsPresenter.loadArtist(it)
val name = "${getString(R.string.transition_artist_image)}_$it"
artistCoverContainer?.transitionName = name
}
} else {
finish()
}
windowEnterTransition()
ActivityCompat.postponeEnterTransition(this)
setupRecyclerView()
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
biographyText.setOnClickListener {
if (biographyText.maxLines == 4) {
biographyText.maxLines = Integer.MAX_VALUE
} else {
biographyText.maxLines = 4
}
}
}
override fun onDestroy() {
super.onDestroy()
artistDetailsPresenter.detachView()
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(this, ArrayList(), null)
albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, this)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let {
CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
reload()
}
}
}
override fun showEmptyView() {
}
override fun complete() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun artist(artist: Artist) {
complete()
if (artist.songCount <= 0) {
finish()
}
this.artist = artist
loadArtistImage()
if (RetroUtil.isAllowedToDownloadMetadata(this)) {
loadBiography(artist.name)
}
artistTitle.text = artist.name
text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(this, artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
songAdapter.swapDataSet(artist.songs)
albumAdapter.swapDataSet(artist.albums!!)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
artistDetailsPresenter.loadBiography(name, lang, null)
}
override fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
biographyText.visibility = View.VISIBLE
biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent,HtmlCompat.FROM_HTML_MODE_LEGACY)
biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private var lang: String? = null
private fun loadArtistImage() {
ArtistGlideRequest.Builder.from(Glide.with(this), artist).generatePalette(this).build()
.dontAnimate().into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
val textColor = if (PreferenceUtil.getInstance(this).adaptiveColor)
color.ripAlpha()
else
ATHUtil.resolveColor(this, android.R.attr.textColorPrimary)
albumTitle.setTextColor(textColor)
songTitle.setTextColor(textColor)
biographyTitle.setTextColor(textColor)
val buttonColor = if (PreferenceUtil.getInstance(this).adaptiveColor)
color.ripAlpha()
else
ATHUtil.resolveColor(this, R.attr.colorSurface)
MaterialUtil.setTint(button = shuffleAction, color = buttonColor)
MaterialUtil.setTint(button = playAction, color = buttonColor)
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
setSupportActionBar(toolbar)
supportActionBar?.title = null
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.songs
when (item.itemId) {
android.R.id.home -> {
super.onBackPressed()
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(intent, getString(R.string.pick_from_local_storage)),
REQUEST_CODE_SELECT_IMAGE
)
return true
}
R.id.action_reset_artist_image -> {
Toast.makeText(
this@ArtistDetailActivity,
resources.getString(R.string.updating),
Toast.LENGTH_SHORT
)
.show()
CustomArtistImageUtil.getInstance(this@ArtistDetailActivity)
.resetCustomArtistImage(artist)
forceDownload = true
return true
}
}
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_artist_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
reload()
}
private fun reload() {
if (intent.extras!!.containsKey(EXTRA_ARTIST_ID)) {
intent.extras?.getInt(EXTRA_ARTIST_ID)?.let { artistDetailsPresenter.loadArtist(it) }
} else {
finish()
}
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) {
cab?.finish()
} else {
super.onBackPressed()
}
}
companion object {
const val EXTRA_ARTIST_ID = "extra_artist_id"
const val REQUEST_CODE_SELECT_IMAGE = 9003
}
}

View file

@ -1,48 +1,45 @@
/* /*
* Copyright (c) 2020 Hemanth Savarla. * Copyright (c) 2020 Hemanth Savarala.
* *
* Licensed under the GNU General Public License v3 * Licensed under the GNU General Public License v3
* *
* This is free software: you can redistribute it and/or modify it * This is free software: you can redistribute it and/or modify it under
* under the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
* *
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
*
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.content.Intent import android.animation.ObjectAnimator
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityDriveModeBinding import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.glide.BlurTransformation import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.slider.Slider import kotlinx.android.synthetic.main.activity_drive_mode.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
@ -50,24 +47,21 @@ import org.koin.android.ext.android.inject
class DriveModeActivity : AbsMusicServiceActivity(), Callback { class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private lateinit var binding: ActivityDriveModeBinding
private var lastPlaybackControlsColor: Int = Color.GRAY private var lastPlaybackControlsColor: Int = Color.GRAY
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
private val repository: RealRepository by inject()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityDriveModeBinding.inflate(layoutInflater) setContentView(R.layout.activity_drive_mode)
setContentView(binding.root)
setUpMusicControllers() setUpMusicControllers()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this) progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
lastPlaybackControlsColor = accentColor() lastPlaybackControlsColor = ThemeStore.accentColor(this)
binding.close.setOnClickListener { close.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressed()
} }
binding.repeatButton.drawAboveSystemBars()
} }
private fun setUpMusicControllers() { private fun setUpMusicControllers() {
@ -80,45 +74,36 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
private fun setupFavouriteToggle() { private fun setupFavouriteToggle() {
binding.songFavourite.setOnClickListener { songFavourite.setOnClickListener {
toggleFavorite(MusicPlayerRemote.currentSong) MusicUtil.toggleFavorite(
this@DriveModeActivity,
MusicPlayerRemote.currentSong
)
} }
} }
private fun toggleFavorite(song: Song) { private fun toggleFavourite() {
lifecycleScope.launch(Dispatchers.IO) { CoroutineScope(Dispatchers.IO).launch {
val playlist = repository.favoritePlaylist() val isFavourite =
val songEntity = song.toSongEntity(playlist.playListId) MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
val isFavorite = repository.isSongFavorite(song.id)
if (isFavorite) {
repository.removeSongFromPlaylist(songEntity)
} else {
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
}
sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
}
private fun updateFavorite() {
lifecycleScope.launch(Dispatchers.IO) {
val isFavorite: Boolean =
repository.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.songFavourite.setImageResource(if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border) songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite_white_24dp else R.drawable.ic_favorite_border_white_24dp)
} }
} }
} }
private fun setUpProgressSlider() { private fun setUpProgressSlider() {
binding.progressSlider.addOnChangeListener { _: Slider, progress: Float, fromUser: Boolean -> progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) { if (fromUser) {
MusicPlayerRemote.seekTo(progress.toInt()) MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews( onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis, MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis MusicPlayerRemote.songDurationMillis
) )
} }
} }
})
} }
override fun onPause() { override fun onPause() {
@ -132,20 +117,21 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
} }
private fun setUpPrevNext() { private fun setUpPrevNext() {
binding.nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
binding.previousButton.setOnClickListener { MusicPlayerRemote.back() } nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
previousButton.setOnClickListener { MusicPlayerRemote.back() }
} }
private fun setUpShuffleButton() { private fun setUpShuffleButton() {
binding.shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() } shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
} }
private fun setUpRepeatButton() { private fun setUpRepeatButton() {
binding.repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() } repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
} }
private fun setUpPlayPauseFab() { private fun setUpPlayPauseFab() {
binding.playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler()) playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
} }
override fun onRepeatModeChanged() { override fun onRepeatModeChanged() {
@ -169,25 +155,24 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
updateSong() updateSong()
updateRepeatState() updateRepeatState()
updateShuffleState() updateShuffleState()
updateFavorite() toggleFavourite()
} }
private fun updatePlayPauseDrawableState() { private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) { if (MusicPlayerRemote.isPlaying) {
binding.playPauseButton.setImageResource(R.drawable.ic_pause) playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
} else { } else {
binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow) playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp)
} }
} }
fun updateShuffleState() { fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) { when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE -> binding.shuffleButton.setColorFilter( MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(
lastPlaybackControlsColor, lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
else -> shuffleButton.setColorFilter(
else -> binding.shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
@ -197,27 +182,19 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private fun updateRepeatState() { private fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) { when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> { MusicService.REPEAT_MODE_NONE -> {
binding.repeatButton.setImageResource(R.drawable.ic_repeat) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
binding.repeatButton.setColorFilter( repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor, lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN PorterDuff.Mode.SRC_IN
) )
} }
MusicService.REPEAT_MODE_ALL -> { MusicService.REPEAT_MODE_ALL -> {
binding.repeatButton.setImageResource(R.drawable.ic_repeat) repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
binding.repeatButton.setColorFilter( repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
MusicService.REPEAT_MODE_THIS -> { MusicService.REPEAT_MODE_THIS -> {
binding.repeatButton.setImageResource(R.drawable.ic_repeat_one) repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp)
binding.repeatButton.setColorFilter( repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
} }
} }
} }
@ -225,34 +202,35 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateSong() updateSong()
updateFavorite() toggleFavourite()
}
override fun onFavoriteStateChanged() {
super.onFavoriteStateChanged()
updateFavorite()
} }
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
binding.songTitle.text = song.title songTitle.text = song.title
binding.songText.text = song.artistName songText.text = song.artistName
Glide.with(this) SongGlideRequest.Builder.from(Glide.with(this), song)
.load(RetroGlideExtension.getSongModel(song)) .checkIgnoreMediaStore(this)
.songCoverOptions(song) .generatePalette(this)
.build()
.transform(BlurTransformation.Builder(this).build()) .transform(BlurTransformation.Builder(this).build())
.into(binding.image) .into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: Int) {
}
})
} }
override fun onUpdateProgressViews(progress: Int, total: Int) { override fun onUpdateProgressViews(progress: Int, total: Int) {
binding.progressSlider.run { progressSlider.max = total
valueTo = total.toFloat()
value = progress.toFloat().coerceIn(valueFrom, valueTo)
}
binding.songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong()) val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
binding.songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong()) animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
} }
} }

View file

@ -1,80 +0,0 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import cat.ereza.customactivityoncrash.CustomActivityOnCrash
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.FileUtils.createFile
import code.name.monkey.retromusic.util.Share.shareFile
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class ErrorActivity : AppCompatActivity() {
private val dayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
private val reportPrefix = "bug_report-"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(cat.ereza.customactivityoncrash.R.layout.customactivityoncrash_default_error_activity)
val restartButton =
findViewById<Button>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_restart_button)
val config = CustomActivityOnCrash.getConfigFromIntent(intent)
if (config == null) {
finish()
return
}
restartButton.setText(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_restart_app)
restartButton.setOnClickListener {
CustomActivityOnCrash.restartApplication(
this@ErrorActivity,
config
)
}
val moreInfoButton =
findViewById<Button>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_more_info_button)
moreInfoButton.setOnClickListener { //We retrieve all the error data and show it
MaterialAlertDialogBuilder(this@ErrorActivity)
.setTitle(cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_title)
.setMessage(
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
this@ErrorActivity,
intent
)
)
.setPositiveButton(
cat.ereza.customactivityoncrash.R.string.customactivityoncrash_error_activity_error_details_close,
null
)
.setNeutralButton(
R.string.customactivityoncrash_error_activity_error_details_share
) { _, _ ->
val bugReport = createFile(
context = this,
"Bug Report",
"$reportPrefix${dayFormat.format(Date())}",
CustomActivityOnCrash.getAllErrorDetailsFromIntent(
this@ErrorActivity,
intent
), ".txt"
)
shareFile(this, bugReport, "text/*")
}
.show()
}
val errorActivityDrawableId = config.errorDrawable
val errorImageView =
findViewById<ImageView>(cat.ereza.customactivityoncrash.R.id.customactivityoncrash_error_activity_image)
if (errorActivityDrawableId != null) {
errorImageView.setImageResource(
errorActivityDrawableId
)
}
}
}

View file

@ -0,0 +1,163 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter
import code.name.monkey.retromusic.helper.menu.GenreMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsView
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab
import kotlinx.android.synthetic.main.activity_playlist_detail.*
import java.util.*
import javax.inject.Inject
/**
* @author Hemanth S (h4h13).
*/
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView {
@Inject
lateinit var genreDetailsPresenter: GenreDetailsPresenter
private lateinit var genre: Genre
private lateinit var songAdapter: ShuffleButtonSongAdapter
private var cab: MaterialCab? = null
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty?.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun checkForPadding() {
val height = DensityUtil.dip2px(this, 52f)
recyclerView.setPadding(0, 0, 0, (height))
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
toggleBottomNavigationView(true)
if (intent.extras != null) {
genre = intent?.extras?.getParcelable(EXTRA_GENRE_ID)!!
} else {
finish()
}
setUpToolBar()
setupRecyclerView()
App.musicComponent.inject(this)
genreDetailsPresenter.attachView(this)
}
private fun setUpToolBar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
title = genre.name
}
override fun onResume() {
super.onResume()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
override fun onDestroy() {
super.onDestroy()
genreDetailsPresenter.detachView()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
override fun showEmptyView() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_genre_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return GenreMenuHelper.handleMenuClick(this, genre, item)
}
private fun setupRecyclerView() {
songAdapter = ShuffleButtonSongAdapter(this, ArrayList(), R.layout.item_list, this)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this@GenreDetailsActivity)
adapter = songAdapter
}
songAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
}
override fun songs(songs: List<Song>) {
songAdapter.swapDataSet(songs)
}
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
if (cab != null && cab!!.isActive) cab?.finish()
cab = MaterialCab(this, R.id.cab_stub).setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
).start(callback)
return cab!!
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) cab!!.finish()
else {
recyclerView!!.stopScroll()
super.onBackPressed()
}
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
companion object {
const val EXTRA_GENRE_ID = "extra_genre_id"
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
/**
* Created by hemanths on 2019-09-27.
*/
public class LicenseActivity extends AbsBaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_license);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setLightNavigationBar(true);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ToolbarContentTintHelper.colorBackButton(toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
WebView webView = findViewById(R.id.license);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("index.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface,
Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String changeLog = buf.toString()
.replace("{style-placeholder}",
String.format("body { background-color: %s; color: %s; }", backgroundColor, contentColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace("{link-color-active}",
colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData("<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private String colorToCSS(int color) {
return String.format("rgb(%d, %d, %d)", Color.red(color), Color.green(color),
Color.blue(color)); // on API 29, WebView doesn't load with hex colors
}
}

View file

@ -1,94 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities
import android.graphics.Color
import android.os.Bundle
import android.view.MenuItem
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.databinding.ActivityLicenseBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.drawAboveSystemBars
import code.name.monkey.retromusic.extensions.surfaceColor
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
/** Created by hemanths on 2019-09-27. */
class LicenseActivity : AbsThemeActivity() {
private lateinit var binding: ActivityLicenseBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLicenseBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
ToolbarContentTintHelper.colorBackButton(binding.toolbar)
try {
val buf = StringBuilder()
val json = assets.open("license.html")
BufferedReader(InputStreamReader(json, StandardCharsets.UTF_8)).use { br ->
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
}
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(this)
val backgroundColor = colorToCSS(
surfaceColor(Color.parseColor(if (isDark) "#424242" else "#ffffff"))
)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val changeLog = buf.toString()
.replace(
"{style-placeholder}", String.format(
"body { background-color: %s; color: %s; }", backgroundColor, contentColor
)
)
.replace("{link-color}", colorToCSS(accentColor()))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor())
)
)
binding.license.loadData(changeLog, "text/html", "UTF-8")
} catch (e: Throwable) {
binding.license.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
binding.license.drawAboveSystemBars()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun colorToCSS(color: Int): String {
return String.format(
"rgb(%d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color)
) // on API 29, WebView doesn't load with hex colors
}
}

View file

@ -1,54 +1,47 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.core.content.getSystemService import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityLockScreenBinding import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenPlayerControlsFragment
import code.name.monkey.retromusic.extensions.hideStatusBar
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto
import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenControlsFragment
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.r0adkll.slidr.Slidr import com.r0adkll.slidr.Slidr
import com.r0adkll.slidr.model.SlidrConfig import com.r0adkll.slidr.model.SlidrConfig
import com.r0adkll.slidr.model.SlidrListener import com.r0adkll.slidr.model.SlidrListener
import com.r0adkll.slidr.model.SlidrPosition import com.r0adkll.slidr.model.SlidrPosition
import kotlinx.android.synthetic.main.activity_lock_screen.*
class LockScreenActivity : AbsMusicServiceActivity() { class LockScreenActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityLockScreenBinding private var fragment: LockScreenPlayerControlsFragment? = null
private var fragment: LockScreenControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lockScreenInit() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
binding = ActivityLockScreenBinding.inflate(layoutInflater) setShowWhenLocked(true)
setContentView(binding.root) setTurnScreenOn(true)
} else {
this.window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
setDrawUnderStatusBar()
setContentView(R.layout.activity_lock_screen)
hideStatusBar() hideStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true)
val config = SlidrConfig.Builder().listener(object : SlidrListener { val config = SlidrConfig.Builder().listener(object : SlidrListener {
override fun onSlideStateChanged(state: Int) { override fun onSlideStateChanged(state: Int) {
@ -61,10 +54,10 @@ class LockScreenActivity : AbsMusicServiceActivity() {
} }
override fun onSlideClosed(): Boolean { override fun onSlideClosed(): Boolean {
if (VersionUtils.hasOreo()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val keyguardManager = val keyguardManager =
getSystemService<KeyguardManager>() getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager?.requestDismissKeyguard(this@LockScreenActivity, null) keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
} }
finish() finish()
return true return true
@ -73,25 +66,13 @@ class LockScreenActivity : AbsMusicServiceActivity() {
Slidr.attach(this, config) Slidr.attach(this, config)
fragment = whichFragment<LockScreenControlsFragment>(R.id.playback_controls_fragment) fragment =
supportFragmentManager.findFragmentById(R.id.playback_controls_fragment) as LockScreenPlayerControlsFragment?
binding.slide.apply { findViewById<View>(R.id.slide).apply {
translationY = 100f translationY = 100f
alpha = 0f alpha = 0f
animate().translationY(0f).alpha(1f).setDuration(1500).start() ViewCompat.animate(this).translationY(0f).alpha(1f).setDuration(1500).start()
}
}
@Suppress("Deprecation")
private fun lockScreenInit() {
if (VersionUtils.hasOreoMR1()) {
setShowWhenLocked(true)
//setTurnScreenOn(true)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
// or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
} }
} }
@ -107,14 +88,11 @@ class LockScreenActivity : AbsMusicServiceActivity() {
private fun updateSongs() { private fun updateSongs() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
Glide.with(this) SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this)
.asBitmapPalette() .generatePalette(this).build().dontAnimate()
.songCoverOptions(song) .into(object : RetroMusicColoredTarget(image) {
.load(RetroGlideExtension.getSongModel(song)) override fun onColorReady(color: Int) {
.dontAnimate() fragment?.setDark(color)
.into(object : RetroMusicColoredTarget(binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
fragment?.setColor(colors)
} }
}) })
} }

View file

@ -0,0 +1,428 @@
package code.name.monkey.retromusic.activities
import android.R.attr
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.text.TextUtils
import android.view.*
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.*
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcHelper
import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.input.getInputLayout
import com.afollestad.materialdialogs.input.input
import kotlinx.android.synthetic.main.activity_lyrics.*
import kotlinx.android.synthetic.main.fragment_lyrics.*
import kotlinx.android.synthetic.main.fragment_synced.*
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.set
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
when (state) {
ViewPager.SCROLL_STATE_IDLE -> fab.show()
ViewPager.SCROLL_STATE_DRAGGING,
ViewPager.SCROLL_STATE_SETTLING -> fab.hide()
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
PreferenceUtil.getInstance(this).lyricsOptions = position
if (position == 0) fab.text = getString(R.string.synced_lyrics)
else if (position == 1) fab.text = getString(R.string.lyrics)
}
override fun onClick(v: View?) {
when (viewPager.currentItem) {
0 -> showSyncedLyrics()
1 -> showLyricsSaveDialog()
}
}
private lateinit var song: Song
private var lyricsString: String? = null
private val googleSearchLrcUrl: String
get() {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " .lrc"
baseUrl += query
return baseUrl
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lyrics)
setStatusbarColorAuto()
setTaskDescriptionColorAuto()
setNavigationbarColorAuto()
fab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
)
.apply {
fab.setTextColor(this)
fab.iconTint = this
}
setupWakelock()
viewPager.apply {
adapter = PagerAdapter(supportFragmentManager)
currentItem = PreferenceUtil.getInstance(this@LyricsActivity).lyricsOptions
addOnPageChangeListener(this@LyricsActivity)
}
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
tabs.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
tabs.setupWithViewPager(viewPager)
tabs.setSelectedTabIndicator(
TintHelper.createTintedDrawable(
ContextCompat.getDrawable(
this,
R.drawable.tab_indicator
), ThemeStore.accentColor(this)
)
)
tabs.setTabTextColors(
ATHUtil.resolveColor(this, android.R.attr.textColorSecondary),
ThemeStore.accentColor(this)
)
tabs.setSelectedTabIndicatorColor(ThemeStore.accentColor(this))
fab.setOnClickListener(this)
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateTitleSong()
}
override fun onServiceConnected() {
super.onServiceConnected()
updateTitleSong()
}
private fun updateTitleSong() {
song = MusicPlayerRemote.currentSong
toolbar.title = song.title
toolbar.subtitle = song.artistName
}
private fun setupWakelock() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
private fun showSyncedLyrics() {
var content = ""
try {
content = LyricUtil.getStringFromFile(song.title, song.artistName)
} catch (e: Exception) {
e.printStackTrace()
}
val materialDialog = MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT))
.show {
cornerRadius(PreferenceUtil.getInstance(this@LyricsActivity).dialogCorner)
title(R.string.add_time_framed_lryics)
negativeButton(R.string.action_search) {
RetroUtil.openUrl(this@LyricsActivity, googleSearchLrcUrl)
}
input(
hint = getString(R.string.paste_lyrics_here),
prefill = content,
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
) { _, input ->
LyricUtil.writeLrcToLoc(song.title, song.artistName, input.toString())
}
positiveButton(android.R.string.ok) {
updateSong()
}
}
MaterialUtil.setTint(materialDialog.getInputLayout(), false)
}
private fun updateSong() {
val page =
supportFragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + viewPager.currentItem)
if (viewPager.currentItem == 0 && page != null) {
(page as BaseLyricsFragment).upDateSong()
}
}
private fun showLyricsSaveDialog() {
val content: String = if (lyricsString == null) {
""
} else {
lyricsString!!
}
val materialDialog = MaterialDialog(
this, BottomSheet(LayoutMode.WRAP_CONTENT)
).show {
cornerRadius(PreferenceUtil.getInstance(this@LyricsActivity).dialogCorner)
title(R.string.add_lyrics)
negativeButton(R.string.action_search) {
RetroUtil.openUrl(this@LyricsActivity, getGoogleSearchUrl())
}
input(
hint = getString(R.string.paste_lyrics_here),
prefill = content,
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
WriteTagsAsyncTask(this@LyricsActivity).execute(
WriteTagsAsyncTask.LoadingInfo(
getSongPaths(song), fieldKeyValueMap, null
)
)
}
positiveButton(android.R.string.ok) {
updateSong()
}
}
MaterialUtil.setTint(materialDialog.getInputLayout(), false)
}
private fun getSongPaths(song: Song): ArrayList<String> {
val paths = ArrayList<String>(1)
paths.add(song.data)
return paths
}
private fun getGoogleSearchUrl(): String {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " lyrics"
baseUrl += query
return baseUrl
}
class PagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(
fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
class Tabs(
@StringRes val title: Int, val fragment: Fragment
)
private var tabs = ArrayList<Tabs>()
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
tabs.add(Tabs(R.string.synced_lyrics, SyncedLyricsFragment()))
}
tabs.add(Tabs(R.string.normal_lyrics, OfflineLyricsFragment()))
}
override fun getItem(position: Int): Fragment {
return tabs[position].fragment
}
override fun getPageTitle(position: Int): CharSequence? {
return App.getContext().getString(tabs[position].title)
}
override fun getCount(): Int {
return tabs.size
}
}
abstract class BaseLyricsFragment : AbsMusicServiceFragment() {
abstract fun upDateSong()
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
upDateSong()
}
override fun onServiceConnected() {
super.onServiceConnected()
upDateSong()
}
}
class OfflineLyricsFragment : BaseLyricsFragment() {
override fun upDateSong() {
loadSongLyrics()
}
private var updateLyricsAsyncTask: AsyncTask<*, *, *>? = null
private var lyrics: Lyrics? = null
@SuppressLint("StaticFieldLeak")
private fun loadSongLyrics() {
if (updateLyricsAsyncTask != null) {
updateLyricsAsyncTask!!.cancel(false)
}
val song = MusicPlayerRemote.currentSong
updateLyricsAsyncTask = object : AsyncTask<Void?, Void?, Lyrics?>() {
override fun doInBackground(vararg params: Void?): Lyrics? {
val data = MusicUtil.getLyrics(song)
return if (TextUtils.isEmpty(data)) {
null
} else Lyrics.parse(song, data!!)
}
override fun onPreExecute() {
super.onPreExecute()
lyrics = null
}
override fun onPostExecute(l: Lyrics?) {
lyrics = l
offlineLyrics?.visibility = View.VISIBLE
if (l == null) {
offlineLyrics?.setText(R.string.no_lyrics_found)
return
}
(activity as LyricsActivity).lyricsString = l.text
offlineLyrics?.text = l.text
}
override fun onCancelled(s: Lyrics?) {
onPostExecute(null)
}
}.execute()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loadSongLyrics()
}
override fun onDestroyView() {
super.onDestroyView()
if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask!!.isCancelled) {
updateLyricsAsyncTask?.cancel(true)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_lyrics, container, false)
}
}
class SyncedLyricsFragment : BaseLyricsFragment(), MusicProgressViewUpdateHelper.Callback {
override fun upDateSong() {
loadLRCLyrics()
}
private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_synced, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupLyricsView()
}
private fun setupLyricsView() {
lyricsView.apply {
setCurrentPlayLineColor(ThemeStore.accentColor(requireContext()))
setIndicatorTextColor(ThemeStore.accentColor(requireContext()))
setCurrentIndicateLineTextColor(
resolveColor(
requireContext(),
attr.textColorPrimary
)
)
setNoLrcTextColor(resolveColor(requireContext(), attr.textColorPrimary))
setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener {
override fun onPlay(time: Long, content: String) {
MusicPlayerRemote.seekTo(time.toInt())
}
})
}
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.resetView("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
showLyricsLocal(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
private fun showLyricsLocal(file: File?) {
if (file != null) {
lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file))
}
}
}
}

View file

@ -0,0 +1,780 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities;
import android.app.ActivityOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.transition.TransitionManager;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialcab.MaterialCab.Callback;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.textview.MaterialTextView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity;
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog;
import code.name.monkey.retromusic.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment;
import code.name.monkey.retromusic.fragments.mainactivity.AlbumsFragment;
import code.name.monkey.retromusic.fragments.mainactivity.ArtistsFragment;
import code.name.monkey.retromusic.fragments.mainactivity.BannerHomeFragment;
import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment;
import code.name.monkey.retromusic.fragments.mainactivity.GenresFragment;
import code.name.monkey.retromusic.fragments.mainactivity.PlayingQueueFragment;
import code.name.monkey.retromusic.fragments.mainactivity.PlaylistsFragment;
import code.name.monkey.retromusic.fragments.mainactivity.SongsFragment;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.SearchQueryHelper;
import code.name.monkey.retromusic.helper.SortOrder.AlbumSortOrder;
import code.name.monkey.retromusic.helper.SortOrder.ArtistSortOrder;
import code.name.monkey.retromusic.helper.SortOrder.SongSortOrder;
import code.name.monkey.retromusic.interfaces.CabHolder;
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks;
import code.name.monkey.retromusic.loaders.AlbumLoader;
import code.name.monkey.retromusic.loaders.ArtistLoader;
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.service.MusicService;
import code.name.monkey.retromusic.util.AppRater;
import code.name.monkey.retromusic.util.NavigationUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
import code.name.monkey.retromusic.util.RetroColorUtil;
import code.name.monkey.retromusic.util.RetroUtil;
/**
* Created by hemanths on 2020-02-19.
*/
public class MainActivity extends AbsSlidingMusicPanelActivity
implements CabHolder, SharedPreferences.OnSharedPreferenceChangeListener {
public static final String TAG = MainActivity.class.getSimpleName();
public static final int APP_INTRO_REQUEST = 100;
public static final String EXPAND_PANEL = "expand_panel";
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
String action = intent.getAction();
if (action != null && action.equals(Intent.ACTION_SCREEN_OFF)) {
if (PreferenceUtil.getInstance(context).getLockScreen() && MusicPlayerRemote.isPlaying()) {
final Intent activity = new Intent(context, LockScreenActivity.class);
activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
ActivityCompat.startActivity(context, activity, null);
}
}
}
};
private final IntentFilter mIntentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
@Nullable
MainActivityFragmentCallbacks currentFragment;
private boolean blockRequestPermissions = false;
private MaterialCab cab;
private AppBarLayout mAppBarLayout;
private MaterialTextView mAppTitle;
private Toolbar mToolbar;
private MaterialCardView mToolbarContainer;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setLightNavigationBar(true);
setTaskDescriptionColorAuto();
hideStatusBar();
setBottomBarVisibility(View.VISIBLE);
getBottomNavigationView().setSelectedItemId(PreferenceUtil.getInstance(this).getLastPage());
getBottomNavigationView().setOnNavigationItemSelectedListener(item -> {
PreferenceUtil.getInstance(MainActivity.this).setLastPage(item.getItemId());
selectedFragment(item.getItemId());
return true;
});
if (savedInstanceState == null) {
selectedFragment(PreferenceUtil.getInstance(this).getLastPage());
} else {
restoreCurrentFragment();
}
mToolbarContainer = findViewById(R.id.toolbarContainer);
mAppTitle = findViewById(R.id.appTitle);
mToolbar = findViewById(R.id.toolbar);
mAppBarLayout = findViewById(R.id.appBarLayout);
checkShowChangelog();
AppRater.appLaunched(this);
setupToolbar();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == APP_INTRO_REQUEST) {
blockRequestPermissions = false;
if (!hasPermissions()) {
requestPermissions();
}
}
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mBroadcastReceiver, mIntentFilter);
PreferenceUtil.getInstance(this).registerOnSharedPreferenceChangedListener(this);
if (getIntent().hasExtra(EXPAND_PANEL) && PreferenceUtil.getInstance(this).isExpandPanel()) {
if (getIntent().getBooleanExtra(EXPAND_PANEL, false)) {
expandPanel();
getIntent().putExtra(EXPAND_PANEL, false);
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mBroadcastReceiver);
PreferenceUtil.getInstance(this).unregisterOnSharedPreferenceChangedListener(this);
}
public void addOnAppBarOffsetChangedListener(
@NonNull AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) {
mAppBarLayout.addOnOffsetChangedListener(onOffsetChangedListener);
}
public int getTotalAppBarScrollingRange() {
return mAppBarLayout.getTotalScrollRange();
}
@Override
public boolean handleBackPress() {
if (cab != null && cab.isActive()) {
cab.finish();
return true;
}
return super.handleBackPress() || (currentFragment != null && currentFragment.handleBackPress());
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
if (isPlaylistPage()) {
menu.add(0, R.id.action_new_playlist, 0, R.string.new_playlist_title)
.setIcon(R.drawable.ic_playlist_add_white_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
if (isHomePage()) {
menu.add(0, R.id.action_search, 0, getString(R.string.action_search))
.setIcon(R.drawable.ic_mic_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
if (isFolderPage()) {
menu.add(0, R.id.action_scan, 0, R.string.scan_media)
.setIcon(R.drawable.ic_scanner_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, R.id.action_go_to_start_directory, 0, R.string.action_go_to_start_directory)
.setIcon(R.drawable.ic_bookmark_music_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
Fragment fragment = getCurrentFragment();
if (fragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment) {
AbsLibraryPagerRecyclerViewCustomGridSizeFragment currentFragment
= (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) fragment;
MenuItem gridSizeItem = menu.findItem(R.id.action_grid_size);
if (RetroUtil.isLandscape()) {
gridSizeItem.setTitle(R.string.action_grid_size_land);
}
setUpGridSizeMenu(currentFragment, gridSizeItem.getSubMenu());
MenuItem layoutItem = menu.findItem(R.id.action_layout_type);
setupLayoutMenu(currentFragment, layoutItem.getSubMenu());
setUpSortOrderMenu(currentFragment, menu.findItem(R.id.action_sort_order).getSubMenu());
} else {
menu.removeItem(R.id.action_layout_type);
menu.removeItem(R.id.action_grid_size);
menu.removeItem(R.id.action_sort_order);
}
menu.add(0, R.id.action_settings, 0, getString(R.string.action_settings))
.setIcon(R.drawable.ic_settings_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
Fragment fragment = getCurrentFragment();
if (fragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment) {
AbsLibraryPagerRecyclerViewCustomGridSizeFragment currentFragment
= (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) fragment;
if (handleGridSizeMenuItem(currentFragment, item)) {
return true;
}
if (handleLayoutResType(currentFragment, item)) {
return true;
}
if (handleSortOrderMenuItem(currentFragment, item)) {
return true;
}
}
int id = item.getItemId();
switch (id) {
case R.id.action_search:
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, mToolbarContainer,
getString(R.string.transition_toolbar));
NavigationUtil.goToSearch(this, true, options);
break;
case R.id.action_new_playlist:
CreatePlaylistDialog.create().show(getSupportFragmentManager(), "CREATE_PLAYLIST");
return true;
case R.id.action_settings:
NavigationUtil.goToSettings(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(this, mToolbar);
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onServiceConnected() {
super.onServiceConnected();
handlePlaybackIntent(getIntent());
}
@Override
public void onSharedPreferenceChanged(final @NonNull SharedPreferences sharedPreferences,
final @NonNull String key) {
if (key.equals(PreferenceUtil.GENERAL_THEME) || key.equals(PreferenceUtil.BLACK_THEME) ||
key.equals(PreferenceUtil.ADAPTIVE_COLOR_APP) || key.equals(PreferenceUtil.DOMINANT_COLOR) ||
key.equals(PreferenceUtil.USER_NAME) || key.equals(PreferenceUtil.TOGGLE_FULL_SCREEN) ||
key.equals(PreferenceUtil.TOGGLE_VOLUME) || key.equals(PreferenceUtil.ROUND_CORNERS) ||
key.equals(PreferenceUtil.CAROUSEL_EFFECT) || key.equals(PreferenceUtil.NOW_PLAYING_SCREEN_ID) ||
key.equals(PreferenceUtil.TOGGLE_GENRE) || key.equals(PreferenceUtil.BANNER_IMAGE_PATH) ||
key.equals(PreferenceUtil.PROFILE_IMAGE_PATH) || key.equals(PreferenceUtil.CIRCULAR_ALBUM_ART) ||
key.equals(PreferenceUtil.KEEP_SCREEN_ON) || key.equals(PreferenceUtil.TOGGLE_SEPARATE_LINE) ||
key.equals(PreferenceUtil.TOGGLE_HOME_BANNER) || key.equals(PreferenceUtil.TOGGLE_ADD_CONTROLS) ||
key.equals(PreferenceUtil.ALBUM_COVER_STYLE) || key.equals(PreferenceUtil.HOME_ARTIST_GRID_STYLE) ||
key.equals(PreferenceUtil.ALBUM_COVER_TRANSFORM) || key.equals(PreferenceUtil.DESATURATED_COLOR) ||
key.equals(PreferenceUtil.TAB_TEXT_MODE) || key.equals(PreferenceUtil.LANGUAGE_NAME) ||
key.equals(PreferenceUtil.LIBRARY_CATEGORIES)
) {
postRecreate();
}
}
@NotNull
@Override
public MaterialCab openCab(final int menuRes, @NotNull final Callback callback) {
if (cab != null && cab.isActive()) {
cab.finish();
}
cab = new MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)))
.start(callback);
return cab;
}
public void removeOnAppBarOffsetChangedListener(
@NonNull AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) {
mAppBarLayout.removeOnOffsetChangedListener(onOffsetChangedListener);
}
public void setCurrentFragment(@NonNull Fragment fragment, @NonNull String tag) {
String currentTag = null;
if (getSupportFragmentManager().findFragmentByTag(tag) != null) {
currentTag = getSupportFragmentManager().findFragmentByTag(tag).getTag();
}
if (!tag.equals(currentTag)) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment, tag)
.commit();
currentFragment = (MainActivityFragmentCallbacks) fragment;
}
}
@NotNull
@Override
protected View createContentView() {
return wrapSlidingMusicPanel(R.layout.activity_main_content);
}
@Override
protected void requestPermissions() {
if (!blockRequestPermissions) {
super.requestPermissions();
}
}
private void checkShowChangelog() {
try {
final PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
final int currentVersion = pInfo.versionCode;
if (currentVersion != PreferenceUtil.getInstance(this).getLastChangelogVersion()) {
startActivityForResult(new Intent(this, WhatsNewActivity.class), APP_INTRO_REQUEST);
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
@Nullable
private Fragment getCurrentFragment() {
return getSupportFragmentManager().findFragmentById(R.id.fragment_container);
}
private boolean handleGridSizeMenuItem(
@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment,
@NonNull MenuItem item) {
int gridSize = 0;
switch (item.getItemId()) {
case R.id.action_grid_size_1:
gridSize = 1;
break;
case R.id.action_grid_size_2:
gridSize = 2;
break;
case R.id.action_grid_size_3:
gridSize = 3;
break;
case R.id.action_grid_size_4:
gridSize = 4;
break;
case R.id.action_grid_size_5:
gridSize = 5;
break;
case R.id.action_grid_size_6:
gridSize = 6;
break;
case R.id.action_grid_size_7:
gridSize = 7;
break;
case R.id.action_grid_size_8:
gridSize = 8;
break;
}
if (gridSize > 0) {
item.setChecked(true);
fragment.setAndSaveGridSize(gridSize);
return true;
}
return false;
}
private boolean handleLayoutResType(
final AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment,
final MenuItem item) {
int layoutRes = -1;
switch (item.getItemId()) {
case R.id.action_layout_normal:
layoutRes = R.layout.item_grid;
break;
case R.id.action_layout_card:
layoutRes = R.layout.item_card;
break;
case R.id.action_layout_colored_card:
layoutRes = R.layout.item_card_color;
break;
case R.id.action_layout_circular:
layoutRes = R.layout.item_grid_circle;
break;
case R.id.action_layout_image:
layoutRes = R.layout.image;
break;
case R.id.action_layout_gradient_image:
layoutRes = R.layout.item_image_gradient;
break;
}
if (layoutRes != -1) {
item.setChecked(true);
fragment.setAndSaveLayoutRes(layoutRes);
return true;
}
return false;
}
private void handlePlaybackIntent(@Nullable Intent intent) {
if (intent == null) {
return;
}
Uri uri = intent.getData();
String mimeType = intent.getType();
boolean handled = false;
if (intent.getAction() != null && intent.getAction()
.equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH)) {
final List<Song> songs = SearchQueryHelper.getSongs(this, intent.getExtras());
if (MusicPlayerRemote.getShuffleMode() == MusicService.SHUFFLE_MODE_SHUFFLE) {
MusicPlayerRemote.openAndShuffleQueue(songs, true);
} else {
MusicPlayerRemote.openQueue(songs, 0, true);
}
handled = true;
}
if (uri != null && uri.toString().length() > 0) {
MusicPlayerRemote.playFromUri(uri);
handled = true;
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) {
final int id = (int) parseIdFromIntent(intent, "playlistId", "playlist");
if (id >= 0) {
int position = intent.getIntExtra("position", 0);
List<Song> songs = new ArrayList<>(PlaylistSongsLoader.getPlaylistSongList(this, id));
MusicPlayerRemote.openQueue(songs, position, true);
handled = true;
}
} else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) {
final int id = (int) parseIdFromIntent(intent, "albumId", "album");
if (id >= 0) {
int position = intent.getIntExtra("position", 0);
MusicPlayerRemote.openQueue(AlbumLoader.getAlbum(this, id).getSongs(), position, true);
handled = true;
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) {
final int id = (int) parseIdFromIntent(intent, "artistId", "artist");
if (id >= 0) {
int position = intent.getIntExtra("position", 0);
MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).getSongs(), position, true);
handled = true;
}
}
if (handled) {
setIntent(new Intent());
}
}
private boolean handleSortOrderMenuItem(
@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment
fragment, @NonNull MenuItem item) {
String sortOrder = null;
if (fragment instanceof AlbumsFragment) {
switch (item.getItemId()) {
case R.id.action_album_sort_order_asc:
sortOrder = AlbumSortOrder.ALBUM_A_Z;
break;
case R.id.action_album_sort_order_desc:
sortOrder = AlbumSortOrder.ALBUM_Z_A;
break;
case R.id.action_album_sort_order_artist:
sortOrder = AlbumSortOrder.ALBUM_ARTIST;
break;
case R.id.action_album_sort_order_year:
sortOrder = AlbumSortOrder.ALBUM_YEAR;
break;
}
} else if (fragment instanceof ArtistsFragment) {
switch (item.getItemId()) {
case R.id.action_artist_sort_order_asc:
sortOrder = ArtistSortOrder.ARTIST_A_Z;
break;
case R.id.action_artist_sort_order_desc:
sortOrder = ArtistSortOrder.ARTIST_Z_A;
break;
}
} else if (fragment instanceof SongsFragment) {
switch (item.getItemId()) {
case R.id.action_song_sort_order_asc:
sortOrder = SongSortOrder.SONG_A_Z;
break;
case R.id.action_song_sort_order_desc:
sortOrder = SongSortOrder.SONG_Z_A;
break;
case R.id.action_song_sort_order_artist:
sortOrder = SongSortOrder.SONG_ARTIST;
break;
case R.id.action_song_sort_order_album:
sortOrder = SongSortOrder.SONG_ALBUM;
break;
case R.id.action_song_sort_order_year:
sortOrder = SongSortOrder.SONG_YEAR;
break;
case R.id.action_song_sort_order_date:
sortOrder = SongSortOrder.SONG_DATE;
break;
case R.id.action_song_sort_order_composer:
sortOrder = SongSortOrder.COMPOSER;
break;
case R.id.action_song_sort_order_date_modified:
sortOrder = SongSortOrder.SONG_DATE_MODIFIED;
break;
}
}
if (sortOrder != null) {
item.setChecked(true);
fragment.setAndSaveSortOrder(sortOrder);
return true;
}
return false;
}
private boolean isFolderPage() {
return getSupportFragmentManager().findFragmentByTag(FoldersFragment.TAG) instanceof FoldersFragment;
}
private boolean isHomePage() {
return getSupportFragmentManager().findFragmentByTag(BannerHomeFragment.TAG) instanceof BannerHomeFragment;
}
private boolean isPlaylistPage() {
return getSupportFragmentManager().findFragmentByTag(PlaylistsFragment.TAG) instanceof PlaylistsFragment;
}
private long parseIdFromIntent(@NonNull Intent intent, String longKey,
String stringKey) {
long id = intent.getLongExtra(longKey, -1);
if (id < 0) {
String idString = intent.getStringExtra(stringKey);
if (idString != null) {
try {
id = Long.parseLong(idString);
} catch (NumberFormatException e) {
Log.e(TAG, e.getMessage());
}
}
}
return id;
}
private void restoreCurrentFragment() {
currentFragment = (MainActivityFragmentCallbacks) getSupportFragmentManager()
.findFragmentById(R.id.fragment_container);
}
private void selectedFragment(final int itemId) {
switch (itemId) {
case R.id.action_album:
setCurrentFragment(AlbumsFragment.newInstance(), AlbumsFragment.TAG);
break;
case R.id.action_artist:
setCurrentFragment(ArtistsFragment.newInstance(), ArtistsFragment.TAG);
break;
case R.id.action_playlist:
setCurrentFragment(PlaylistsFragment.newInstance(), PlaylistsFragment.TAG);
break;
case R.id.action_genre:
setCurrentFragment(GenresFragment.newInstance(), GenresFragment.TAG);
break;
case R.id.action_playing_queue:
setCurrentFragment(PlayingQueueFragment.newInstance(), PlayingQueueFragment.TAG);
break;
case R.id.action_song:
setCurrentFragment(SongsFragment.newInstance(), SongsFragment.TAG);
break;
case R.id.action_folder:
setCurrentFragment(FoldersFragment.newInstance(this), FoldersFragment.TAG);
break;
default:
case R.id.action_home:
setCurrentFragment(BannerHomeFragment.newInstance(), BannerHomeFragment.TAG);
break;
}
}
private void setUpGridSizeMenu(@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment,
@NonNull SubMenu gridSizeMenu) {
switch (fragment.getGridSize()) {
case 1:
gridSizeMenu.findItem(R.id.action_grid_size_1).setChecked(true);
break;
case 2:
gridSizeMenu.findItem(R.id.action_grid_size_2).setChecked(true);
break;
case 3:
gridSizeMenu.findItem(R.id.action_grid_size_3).setChecked(true);
break;
case 4:
gridSizeMenu.findItem(R.id.action_grid_size_4).setChecked(true);
break;
case 5:
gridSizeMenu.findItem(R.id.action_grid_size_5).setChecked(true);
break;
case 6:
gridSizeMenu.findItem(R.id.action_grid_size_6).setChecked(true);
break;
case 7:
gridSizeMenu.findItem(R.id.action_grid_size_7).setChecked(true);
break;
case 8:
gridSizeMenu.findItem(R.id.action_grid_size_8).setChecked(true);
break;
}
int maxGridSize = fragment.getMaxGridSize();
if (maxGridSize < 8) {
gridSizeMenu.findItem(R.id.action_grid_size_8).setVisible(false);
}
if (maxGridSize < 7) {
gridSizeMenu.findItem(R.id.action_grid_size_7).setVisible(false);
}
if (maxGridSize < 6) {
gridSizeMenu.findItem(R.id.action_grid_size_6).setVisible(false);
}
if (maxGridSize < 5) {
gridSizeMenu.findItem(R.id.action_grid_size_5).setVisible(false);
}
if (maxGridSize < 4) {
gridSizeMenu.findItem(R.id.action_grid_size_4).setVisible(false);
}
if (maxGridSize < 3) {
gridSizeMenu.findItem(R.id.action_grid_size_3).setVisible(false);
}
}
private void setUpSortOrderMenu(
@NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment,
@NonNull SubMenu sortOrderMenu) {
String currentSortOrder = fragment.getSortOrder();
sortOrderMenu.clear();
if (fragment instanceof AlbumsFragment) {
sortOrderMenu.add(0, R.id.action_album_sort_order_asc, 0, R.string.sort_order_a_z)
.setChecked(currentSortOrder.equals(AlbumSortOrder.ALBUM_A_Z));
sortOrderMenu.add(0, R.id.action_album_sort_order_desc, 1, R.string.sort_order_z_a)
.setChecked(currentSortOrder.equals(AlbumSortOrder.ALBUM_Z_A));
sortOrderMenu.add(0, R.id.action_album_sort_order_artist, 2, R.string.sort_order_artist)
.setChecked(currentSortOrder.equals(AlbumSortOrder.ALBUM_ARTIST));
sortOrderMenu.add(0, R.id.action_album_sort_order_year, 3, R.string.sort_order_year)
.setChecked(currentSortOrder.equals(AlbumSortOrder.ALBUM_YEAR));
} else if (fragment instanceof ArtistsFragment) {
sortOrderMenu.add(0, R.id.action_artist_sort_order_asc, 0, R.string.sort_order_a_z)
.setChecked(currentSortOrder.equals(ArtistSortOrder.ARTIST_A_Z));
sortOrderMenu.add(0, R.id.action_artist_sort_order_desc, 1, R.string.sort_order_z_a)
.setChecked(currentSortOrder.equals(ArtistSortOrder.ARTIST_Z_A));
} else if (fragment instanceof SongsFragment) {
sortOrderMenu.add(0, R.id.action_song_sort_order_asc, 0, R.string.sort_order_a_z)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_A_Z));
sortOrderMenu.add(0, R.id.action_song_sort_order_desc, 1, R.string.sort_order_z_a)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_Z_A));
sortOrderMenu.add(0, R.id.action_song_sort_order_artist, 2, R.string.sort_order_artist)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_ARTIST));
sortOrderMenu.add(0, R.id.action_song_sort_order_album, 3, R.string.sort_order_album)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_ALBUM));
sortOrderMenu.add(0, R.id.action_song_sort_order_year, 4, R.string.sort_order_year)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_YEAR));
sortOrderMenu.add(0, R.id.action_song_sort_order_date, 5, R.string.sort_order_date)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_DATE));
sortOrderMenu.add(0, R.id.action_song_sort_order_date_modified, 6, R.string.sort_order_date_modified)
.setChecked(currentSortOrder.equals(SongSortOrder.SONG_DATE_MODIFIED));
sortOrderMenu.add(0, R.id.action_song_sort_order_composer, 7, R.string.sort_order_composer)
.setChecked(currentSortOrder.equals(SongSortOrder.COMPOSER));
}
sortOrderMenu.setGroupCheckable(0, true, true);
}
private void setupLayoutMenu(
@NonNull final AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment,
@NonNull final SubMenu subMenu) {
switch (fragment.itemLayoutRes()) {
case R.layout.item_card:
subMenu.findItem(R.id.action_layout_card).setChecked(true);
break;
case R.layout.item_grid:
subMenu.findItem(R.id.action_layout_normal).setChecked(true);
break;
case R.layout.item_card_color:
subMenu.findItem(R.id.action_layout_colored_card).setChecked(true);
break;
case R.layout.item_grid_circle:
subMenu.findItem(R.id.action_layout_circular).setChecked(true);
break;
case R.layout.image:
subMenu.findItem(R.id.action_layout_image).setChecked(true);
break;
case R.layout.item_image_gradient:
subMenu.findItem(R.id.action_layout_gradient_image).setChecked(true);
break;
}
}
private void setupToolbar() {
setTitle(null);
mToolbar.setBackgroundColor(Color.TRANSPARENT);
mToolbarContainer.setCardBackgroundColor(
ColorStateList.valueOf(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface)));
setSupportActionBar(mToolbar);
mToolbar.setOnClickListener(v -> {
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, mToolbarContainer, getString(R.string.transition_toolbar));
NavigationUtil.goToSearch(this, options);
});
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
TransitionManager.beginDelayedTransition(mToolbar);
Log.i(TAG, "run: " + Thread.currentThread().getName());
mAppTitle.setVisibility(View.GONE);
setTitle(R.string.action_search);
}
}, 3000);
Locale[] locales = Locale.getAvailableLocales();
for (Locale l : locales) {
Log.d(TAG, "setupToolbar: " + l.toLanguageTag());
}
}
}

View file

@ -1,214 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.lifecycle.lifecycleScope
import androidx.navigation.contains
import androidx.navigation.ui.setupWithNavController
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.interfaces.IScrollHelper
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.logE
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
class MainActivity : AbsSlidingMusicPanelActivity() {
companion object {
const val TAG = "MainActivity"
const val EXPAND_PANEL = "expand_panel"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTaskDescriptionColorAuto()
hideStatusBar()
updateTabs()
setupNavigationController()
WhatsNewFragment.showChangeLog(this)
}
private fun setupNavigationController() {
val navController = findNavController(R.id.fragment_container)
val navInflater = navController.navInflater
val navGraph = navInflater.inflate(R.navigation.main_graph)
val categoryInfo: CategoryInfo = PreferenceUtil.libraryCategory.first { it.visible }
if (categoryInfo.visible) {
if (!navGraph.contains(PreferenceUtil.lastTab)) PreferenceUtil.lastTab =
categoryInfo.category.id
navGraph.setStartDestination(
if (PreferenceUtil.rememberLastTab) {
PreferenceUtil.lastTab.let {
if (it == 0) {
categoryInfo.category.id
} else {
it
}
}
} else categoryInfo.category.id
)
}
navController.graph = navGraph
navigationView.setupWithNavController(navController)
// Scroll Fragment to top
navigationView.setOnItemReselectedListener {
currentFragment(R.id.fragment_container).apply {
if (this is IScrollHelper) {
scrollToTop()
}
}
}
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == navGraph.startDestinationId) {
currentFragment(R.id.fragment_container)?.enterTransition = null
}
when (destination.id) {
R.id.action_home, R.id.action_song, R.id.action_album, R.id.action_artist, R.id.action_folder, R.id.action_playlist, R.id.action_genre, R.id.action_search -> {
// Save the last tab
if (PreferenceUtil.rememberLastTab) {
saveTab(destination.id)
}
// Show Bottom Navigation Bar
setBottomNavVisibility(visible = true, animate = true)
}
R.id.playing_queue_fragment -> {
setBottomNavVisibility(visible = false, hideBottomSheet = true)
}
else -> setBottomNavVisibility(
visible = false,
animate = true
) // Hide Bottom Navigation Bar
}
}
}
private fun saveTab(id: Int) {
if (PreferenceUtil.libraryCategory.firstOrNull { it.category.id == id }?.visible == true) {
PreferenceUtil.lastTab = id
}
}
override fun onSupportNavigateUp(): Boolean =
findNavController(R.id.fragment_container).navigateUp()
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val expand = intent?.extra<Boolean>(EXPAND_PANEL)?.value ?: false
if (expand && PreferenceUtil.isExpandPanel) {
fromNotification = true
slidingPanel.bringToFront()
expandPanel()
intent?.removeExtra(EXPAND_PANEL)
}
}
override fun onServiceConnected() {
super.onServiceConnected()
intent ?: return
handlePlaybackIntent(intent)
}
private fun handlePlaybackIntent(intent: Intent) {
lifecycleScope.launch(IO) {
val uri: Uri? = intent.data
val mimeType: String? = intent.type
var handled = false
if (intent.action != null &&
intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
) {
val songs: List<Song> = getSongs(intent.extras!!)
if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
MusicPlayerRemote.openAndShuffleQueue(songs, true)
} else {
MusicPlayerRemote.openQueue(songs, 0, true)
}
handled = true
}
if (uri != null && uri.toString().isNotEmpty()) {
MusicPlayerRemote.playFromUri(this@MainActivity, uri)
handled = true
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val id = parseLongFromIntent(intent, "playlistId", "playlist")
if (id >= 0L) {
val position: Int = intent.getIntExtra("position", 0)
val songs: List<Song> = PlaylistSongsLoader.getPlaylistSongList(get(), id)
MusicPlayerRemote.openQueue(songs, position, true)
handled = true
}
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
val id = parseLongFromIntent(intent, "albumId", "album")
if (id >= 0L) {
val position: Int = intent.getIntExtra("position", 0)
val songs = libraryViewModel.albumById(id).songs
MusicPlayerRemote.openQueue(
songs,
position,
true
)
handled = true
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
val id = parseLongFromIntent(intent, "artistId", "artist")
if (id >= 0L) {
val position: Int = intent.getIntExtra("position", 0)
val songs: List<Song> = libraryViewModel.artistById(id).songs
MusicPlayerRemote.openQueue(
songs,
position,
true
)
handled = true
}
}
if (handled) {
setIntent(Intent())
}
}
}
private fun parseLongFromIntent(
intent: Intent,
longKey: String,
stringKey: String,
): Long {
var id = intent.getLongExtra(longKey, -1)
if (id < 0) {
val idString = intent.getStringExtra(stringKey)
if (idString != null) {
try {
id = idString.toLong()
} catch (e: NumberFormatException) {
logE(e)
}
}
}
return id
}
}

View file

@ -1,144 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.OnBackPressedCallback
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.net.toUri
import androidx.core.text.parseAsHtml
import androidx.core.view.isVisible
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.databinding.ActivityPermissionBinding
import code.name.monkey.retromusic.extensions.*
class PermissionActivity : AbsMusicServiceActivity() {
private lateinit var binding: ActivityPermissionBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPermissionBinding.inflate(layoutInflater)
setContentView(binding.root)
setStatusBarColorAuto()
setTaskDescriptionColorAuto()
setupTitle()
binding.storagePermission.setButtonClick {
requestPermissions()
}
if (VersionUtils.hasMarshmallow()) {
binding.audioPermission.show()
binding.audioPermission.setButtonClick {
if (!hasAudioPermission()) {
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = ("package:" + applicationContext.packageName).toUri()
startActivity(intent)
}
}
}
if (VersionUtils.hasS()) {
binding.bluetoothPermission.show()
binding.bluetoothPermission.setButtonClick {
ActivityCompat.requestPermissions(
this,
arrayOf(BLUETOOTH_CONNECT),
BLUETOOTH_PERMISSION_REQUEST
)
}
} else {
binding.audioPermission.setNumber("2")
}
binding.finish.accentBackgroundColor()
binding.finish.setOnClickListener {
if (hasPermissions()) {
startActivity(
Intent(this, MainActivity::class.java).addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
)
)
finish()
}
}
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
finishAffinity()
remove()
}
})
}
private fun setupTitle() {
val appName =
getString(
R.string.message_welcome,
"<b>Metro</b>"
)
.parseAsHtml()
binding.appNameText.text = appName
}
override fun onResume() {
super.onResume()
binding.finish.isEnabled = hasStoragePermission()
if (hasStoragePermission()) {
binding.storagePermission.checkImage.isVisible = true
binding.storagePermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
if (VersionUtils.hasMarshmallow()) {
if (hasAudioPermission()) {
binding.audioPermission.checkImage.isVisible = true
binding.audioPermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
}
if (VersionUtils.hasS()) {
if (hasBluetoothPermission()) {
binding.bluetoothPermission.checkImage.isVisible = true
binding.bluetoothPermission.checkImage.imageTintList =
ColorStateList.valueOf(accentColor())
}
}
}
private fun hasStoragePermission(): Boolean {
return hasPermissions()
}
@RequiresApi(Build.VERSION_CODES.S)
private fun hasBluetoothPermission(): Boolean {
return ActivityCompat.checkSelfPermission(
this,
BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
}
@RequiresApi(Build.VERSION_CODES.M)
private fun hasAudioPermission(): Boolean {
return Settings.System.canWrite(this)
}
}

View file

@ -1,47 +1,28 @@
/* package code.name.monkey.retromusic.activities
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.fragments.queue
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.MenuItem
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.databinding.FragmentPlayingQueueBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ThemedFastScroller
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playing_queue.*
class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_queue) { open class PlayingQueueActivity : AbsMusicServiceActivity() {
private var _binding: FragmentPlayingQueueBinding? = null
private val binding get() = _binding!!
private var wrappedAdapter: RecyclerView.Adapter<*>? = null private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
@ -49,9 +30,6 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
private var playingQueueAdapter: PlayingQueueAdapter? = null private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var linearLayoutManager: LinearLayoutManager
val mainActivity: MainActivity
get() = activity as MainActivity
private fun getUpNextAndQueueTime(): String { private fun getUpNextAndQueueTime(): String {
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position) val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
return MusicUtil.buildInfoString( return MusicUtil.buildInfoString(
@ -60,18 +38,32 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
) )
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) setDrawUnderStatusBar()
_binding = FragmentPlayingQueueBinding.bind(view) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar() setupToolbar()
setUpRecyclerView() setUpRecyclerView()
binding.clearQueue.setOnClickListener { clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue() MusicPlayerRemote.clearQueue()
} }
checkForPadding() checkForPadding()
mainActivity.collapsePanel() }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
} }
private fun setUpRecyclerView() { private fun setUpRecyclerView() {
@ -79,8 +71,11 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
recyclerViewDragDropManager = RecyclerViewDragDropManager() recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager() recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
playingQueueAdapter = PlayingQueueAdapter( playingQueueAdapter = PlayingQueueAdapter(
requireActivity(), this,
MusicPlayerRemote.playingQueue.toMutableList(), MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position, MusicPlayerRemote.position,
R.layout.item_queue R.layout.item_queue
@ -88,30 +83,27 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!) wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) } wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
linearLayoutManager = LinearLayoutManager(requireContext()) linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
binding.recyclerView.apply { recyclerView.adapter = wrappedAdapter
layoutManager = linearLayoutManager recyclerView.itemAnimator = animator
adapter = wrappedAdapter recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
itemAnimator = DraggableItemAnimator() recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewTouchActionGuardManager?.attachRecyclerView(this) recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(this)
recyclerViewSwipeManager?.attachRecyclerView(this)
}
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
if (dy > 0) { if (dy > 0) {
binding.clearQueue.shrink() clearQueue.shrink()
} else if (dy < 0) { } else if (dy < 0) {
binding.clearQueue.extend() clearQueue.extend()
} }
} }
}) })
ThemedFastScroller.create(binding.recyclerView) //ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView)
} }
private fun checkForPadding() { private fun checkForPadding() {
@ -119,7 +111,7 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
override fun onQueueChanged() { override fun onQueueChanged() {
if (MusicPlayerRemote.playingQueue.isEmpty()) { if (MusicPlayerRemote.playingQueue.isEmpty()) {
findNavController().navigateUp() finish()
return return
} }
checkForPadding() checkForPadding()
@ -133,7 +125,7 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
} }
private fun updateCurrentSong() { private fun updateCurrentSong() {
binding.appBarLayout.toolbar.subtitle = getUpNextAndQueueTime() toolbar.subtitle = getUpNextAndQueueTime()
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
@ -143,15 +135,16 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
private fun updateQueuePosition() { private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position) playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition() resetToCurrentPosition()
binding.appBarLayout.toolbar.subtitle = getUpNextAndQueueTime() toolbar.subtitle = getUpNextAndQueueTime()
} }
private fun updateQueue() { private fun updateQueue() {
playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position) playingQueueAdapter?.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
} }
private fun resetToCurrentPosition() { private fun resetToCurrentPosition() {
binding.recyclerView.stopScroll() recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
} }
@ -177,33 +170,20 @@ class PlayingQueueFragment : AbsMusicServiceFragment(R.layout.fragment_playing_q
} }
playingQueueAdapter = null playingQueueAdapter = null
super.onDestroy() super.onDestroy()
if (MusicPlayerRemote.playingQueue.isNotEmpty())
mainActivity.expandPanel()
} }
private fun setupToolbar() { private fun setupToolbar() {
binding.appBarLayout.toolbar.subtitle = getUpNextAndQueueTime() toolbar.subtitle = getUpNextAndQueueTime()
binding.appBarLayout.toolbar.isTitleCentered = false
binding.clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
requireContext(),
ColorUtil.isColorLight(accentColor())
)
).apply {
binding.clearQueue.setTextColor(this)
binding.clearQueue.iconTint = this
}
binding.appBarLayout.pinWhenScrolled()
binding.appBarLayout.toolbar.apply {
setNavigationOnClickListener {
findNavController().navigateUp()
}
setTitle(R.string.now_playing_queue)
setTitleTextAppearance(context, R.style.ToolbarTextAppearanceNormal)
setNavigationIcon(R.drawable.ic_arrow_back)
ToolbarContentTintHelper.colorBackButton(this)
}
}
}
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
val accentColor = ThemeStore.accentColor(this)
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor))
).apply {
clearQueue.setTextColor(this)
clearQueue.iconTint = this
}
}
}

View file

@ -0,0 +1,245 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.PlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsView
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PlaylistsUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playlist_detail.*
import javax.inject.Inject
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView {
@Inject
lateinit var playlistSongsPresenter: PlaylistSongsPresenter
private lateinit var playlist: Playlist
private var cab: MaterialCab? = null
private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
toggleBottomNavigationView(true)
App.musicComponent.inject(this)
playlistSongsPresenter.attachView(this)
if (intent.extras != null) {
playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST)!!
} else {
finish()
}
setUpToolBar()
setUpRecyclerView()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this)
if (playlist is AbsCustomPlaylist) {
adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, this)
recyclerView.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter(this,
ArrayList(),
R.layout.item_list,
this,
object : OrderablePlaylistSongAdapter.OnMoveItemListener {
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem(
this@PlaylistDetailActivity,
playlist.id,
fromPosition,
toPosition
)
) {
val song = adapter.dataSet.removeAt(fromPosition)
adapter.dataSet.add(toPosition, song)
adapter.notifyItemMoved(fromPosition, toPosition)
}
}
})
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter)
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
}
override fun onResume() {
super.onResume()
playlistSongsPresenter.loadPlaylistSongs(playlist)
}
private fun setUpToolBar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
title = playlist.name
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(
if (playlist is AbsCustomPlaylist) R.menu.menu_smart_playlist_detail
else R.menu.menu_playlist_detail, menu
)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return PlaylistMenuHelper.handleMenuClick(this, playlist, item)
}
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
if (cab != null && cab!!.isActive) {
cab!!.finish()
}
cab = MaterialCab(this, R.id.cab_stub).setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
).start(callback)
return cab!!
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) {
cab!!.finish()
} else {
recyclerView!!.stopScroll()
super.onBackPressed()
}
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
if (playlist !is AbsCustomPlaylist) {
// Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(this, playlist.id)) {
finish()
return
}
// Playlist renamed
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist.id.toLong())
if (playlistName != playlist.name) {
playlist = PlaylistLoader.getPlaylist(this, playlist.id)
setToolbarTitle(playlist.name)
}
}
playlistSongsPresenter.loadPlaylistSongs(playlist)
}
private fun setToolbarTitle(title: String) {
supportActionBar!!.title = title
}
private fun checkForPadding() {
val height = DensityUtil.dip2px(this, 52f)
recyclerView.setPadding(0, 0, 0, (height))
}
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
public override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (recyclerView != null) {
recyclerView!!.itemAnimator = null
recyclerView!!.adapter = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
super.onDestroy()
playlistSongsPresenter.detachView()
}
override fun showEmptyView() {
empty.visibility = View.VISIBLE
emptyText.visibility = View.VISIBLE
}
override fun songs(songs: List<Song>) {
adapter.swapDataSet(songs)
}
companion object {
var EXTRA_PLAYLIST = "extra_playlist"
}
}

View file

@ -0,0 +1,155 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
import kotlinx.android.synthetic.main.activity_pro_version.*
import java.lang.ref.WeakReference
class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor
private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pro_version)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
restoreButton.isEnabled = false
purchaseButton.isEnabled = false
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
MaterialUtil.setTint(restoreButton, false)
MaterialUtil.setTint(purchaseButton, true)
restoreButton.setOnClickListener {
if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) {
restorePurchase()
}
}
purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, App.PRO_VERSION_PRODUCT_ID)
}
}
private fun restorePurchase() {
if (restorePurchaseAsyncTask != null) {
restorePurchaseAsyncTask!!.cancel(false)
}
restorePurchaseAsyncTask = RestorePurchaseAsyncTask(this).execute()
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
setResult(RESULT_OK)
}
override fun onPurchaseHistoryRestored() {
if (App.isProVersion()) {
Toast.makeText(
this,
R.string.restored_previous_purchase_please_restart,
Toast.LENGTH_LONG
).show()
setResult(RESULT_OK)
} else {
Toast.makeText(this, R.string.no_purchase_found, Toast.LENGTH_SHORT).show()
}
}
override fun onBillingError(errorCode: Int, error: Throwable?) {
Log.e(TAG, "Billing error: code = $errorCode", error)
}
override fun onBillingInitialized() {
restoreButton.isEnabled = true
purchaseButton.isEnabled = true
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
override fun onDestroy() {
billingProcessor.release()
super.onDestroy()
}
private class RestorePurchaseAsyncTask internal constructor(purchaseActivity: PurchaseActivity) :
AsyncTask<Void, Void, Boolean>() {
private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference(
purchaseActivity
)
override fun onPreExecute() {
super.onPreExecute()
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
Toast.makeText(purchaseActivity, R.string.restoring_purchase, Toast.LENGTH_SHORT)
.show()
} else {
cancel(false)
}
}
override fun doInBackground(vararg params: Void): Boolean? {
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
return purchaseActivity.billingProcessor.loadOwnedPurchasesFromGoogle()
}
cancel(false)
return null
}
override fun onPostExecute(b: Boolean?) {
super.onPostExecute(b)
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity == null || b == null) {
return
}
if (b) {
purchaseActivity.onPurchaseHistoryRestored()
} else {
Toast.makeText(
purchaseActivity,
R.string.could_not_restore_purchase,
Toast.LENGTH_SHORT
).show()
}
}
}
companion object {
private const val TAG: String = "PurchaseActivity"
}
}

View file

@ -0,0 +1,226 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.app.Service
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import android.speech.RecognizerIntent
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.TextView.BufferType
import android.widget.Toast
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.SearchAdapter
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter
import code.name.monkey.retromusic.mvp.presenter.SearchView
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.textfield.TextInputEditText
import kotlinx.android.synthetic.main.activity_search.*
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
import kotlin.collections.MutableList
import kotlin.collections.emptyList
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView {
@Inject
lateinit var searchPresenter: SearchPresenter
private var searchAdapter: SearchAdapter? = null
private var query: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
App.musicComponent.inject(this)
searchPresenter.attachView(this)
setupRecyclerView()
setUpToolBar()
setupSearchView()
if (intent.getBooleanExtra(EXTRA_SHOW_MIC, false)) {
startMicSearch()
}
back.setOnClickListener { onBackPressed() }
voiceSearch.setOnClickListener { startMicSearch() }
clearText.setOnClickListener { searchView.clearText() }
searchContainer.backgroundTintList =
ColorStateList.valueOf(ATHUtil.resolveColor(this, R.attr.colorSurface))
keyboardPopup.setOnClickListener {
val inputManager = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT)
}
keyboardPopup.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
).apply {
keyboardPopup.setTextColor(this)
keyboardPopup.iconTint = this
}
if (savedInstanceState != null) {
query = savedInstanceState.getString(QUERY)
}
}
private fun setupRecyclerView() {
searchAdapter = SearchAdapter(this, emptyList())
searchAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
empty.visibility = if (searchAdapter!!.itemCount < 1) View.VISIBLE else View.GONE
}
})
recyclerView.apply {
layoutManager = LinearLayoutManager(this@SearchActivity)
adapter = searchAdapter
}
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
keyboardPopup.shrink()
} else if (dy < 0) {
keyboardPopup.extend()
}
}
})
}
private fun setupSearchView() {
searchView.addTextChangedListener(this)
}
override fun onDestroy() {
super.onDestroy()
searchPresenter.detachView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(QUERY, query)
}
private fun setUpToolBar() {
title = null
}
private fun search(query: String) {
this.query = query
TransitionManager.beginDelayedTransition(appBarLayout)
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
searchPresenter.search(query)
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
query?.let { search(it) }
}
override fun onQueryTextSubmit(query: String): Boolean {
hideSoftKeyboard()
return false
}
override fun onQueryTextChange(newText: String): Boolean {
search(newText)
return false
}
private fun hideSoftKeyboard() {
RetroUtil.hideSoftKeyboard(this@SearchActivity)
if (searchView != null) {
searchView.clearFocus()
}
}
override fun showEmptyView() {
searchAdapter?.swapDataSet(ArrayList())
}
override fun showData(data: MutableList<Any>) {
searchAdapter?.swapDataSet(data)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_CODE_SPEECH_INPUT -> {
if (resultCode == Activity.RESULT_OK && null != data) {
val result: ArrayList<String>? =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
query = result?.get(0)
searchView.setText(query, BufferType.EDITABLE)
searchPresenter.search(query!!)
}
}
}
}
private fun startMicSearch() {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt))
try {
startActivityForResult(intent, REQ_CODE_SPEECH_INPUT)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
Toast.makeText(this, getString(R.string.speech_not_supported), Toast.LENGTH_SHORT)
.show()
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(newText: CharSequence, start: Int, before: Int, count: Int) {
search(newText.toString())
}
override fun afterTextChanged(s: Editable) {
}
companion object {
val TAG: String = SearchActivity::class.java.simpleName
const val EXTRA_SHOW_MIC = "extra_show_mic"
const val QUERY: String = "query"
private const val REQ_CODE_SPEECH_INPUT = 9002
}
}
fun TextInputEditText.clearText() {
text = null
}

View file

@ -0,0 +1,80 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.fragments.settings.MainSettingsFragment
import kotlinx.android.synthetic.main.activity_settings.*
class SettingsActivity : AbsBaseActivity() {
private val fragmentManager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setupToolbar()
if (savedInstanceState == null) {
fragmentManager.beginTransaction().replace(R.id.contentFrame, MainSettingsFragment())
.commit()
}
}
private fun setupToolbar() {
setTitle(R.string.action_settings)
toolbar.apply {
setBackgroundColor(ATHUtil.resolveColor(this@SettingsActivity, R.attr.colorSurface))
setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.colorBackButton(toolbar)
}
setSupportActionBar(toolbar)
}
fun setupFragment(fragment: Fragment, @StringRes titleName: Int) {
val fragmentTransaction = fragmentManager
.beginTransaction()
.setCustomAnimations(
R.anim.sliding_in_left,
R.anim.sliding_out_right,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right
)
fragmentTransaction.replace(R.id.contentFrame, fragment, fragment.tag)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
setTitle(titleName)
}
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount == 0) {
super.onBackPressed()
} else {
setTitle(R.string.action_settings)
fragmentManager.popBackStack()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
companion object {
const val TAG: String = "SettingsActivity"
}
}

View file

@ -1,51 +1,45 @@
/* /*
* Copyright (c) 2020 Hemanth Savarla. * Copyright (c) 2020 Hemanth Savarala.
* *
* Licensed under the GNU General Public License v3 * Licensed under the GNU General Public License v3
* *
* This is free software: you can redistribute it and/or modify it * This is free software: you can redistribute it and/or modify it under
* under the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * the Free Software Foundation either version 3 of the License, or (at your option) any later version.
* *
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * See the GNU General Public License for more details.
*
*/ */
package code.name.monkey.retromusic.activities package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore.Images.Media import android.provider.MediaStore.Images.Media
import android.view.MenuItem import android.view.MenuItem
import androidx.core.net.toUri
import androidx.core.os.BundleCompat
import androidx.core.view.drawToBitmap import androidx.core.view.drawToBitmap
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.activities.base.AbsThemeActivity import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivityShareInstagramBinding import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.setStatusBarColor
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.Share import code.name.monkey.retromusic.util.Share
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_share_instagram.*
/** /**
* Created by hemanths on 2020-02-02. * Created by hemanths on 2020-02-02.
*/ */
class ShareInstagramStory : AbsThemeActivity() { class ShareInstagramStory : AbsBaseActivity() {
private lateinit var binding: ActivityShareInstagramBinding
companion object { companion object {
const val EXTRA_SONG = "extra_song" const val EXTRA_SONG = "extra_song"
@ -53,59 +47,76 @@ class ShareInstagramStory : AbsThemeActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed() onBackPressed()
return true return true
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityShareInstagramBinding.inflate(layoutInflater) setContentView(R.layout.activity_share_instagram)
setContentView(binding.root) setStatusbarColor(Color.TRANSPARENT)
setStatusBarColor(Color.TRANSPARENT) setNavigationbarColor(Color.BLACK)
binding.toolbar.setBackgroundColor(Color.TRANSPARENT) toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(binding.toolbar) setSupportActionBar(toolbar)
val song = intent.extras?.let { BundleCompat.getParcelable(it, EXTRA_SONG, Song::class.java) } val song = intent.extras?.getParcelable<Song>(EXTRA_SONG)
song?.let { songFinal -> song?.let { songFinal ->
Glide.with(this) SongGlideRequest.Builder.from(Glide.with(this), songFinal)
.asBitmapPalette() .checkIgnoreMediaStore(this@ShareInstagramStory)
.songCoverOptions(songFinal) .generatePalette(this@ShareInstagramStory)
.load(RetroGlideExtension.getSongModel(songFinal)) .build()
.into(object : RetroMusicColoredTarget(binding.image) { .into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(color: Int) {
setColors(colors.backgroundColor) val isColorLight = ColorUtil.isColorLight(color)
setColors(isColorLight, color)
} }
}) })
binding.shareTitle.text = songFinal.title shareTitle.text = songFinal.title
binding.shareText.text = songFinal.artistName shareText.text = songFinal.artistName
binding.shareButton.setOnClickListener { shareButton.setOnClickListener {
val path: String = Media.insertImage( val path: String = Media.insertImage(
contentResolver, contentResolver,
binding.mainContent.drawToBitmap(Bitmap.Config.ARGB_8888), mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
"Design", null "Design", null
) )
val uri = Uri.parse(path)
Share.shareStoryToSocial( Share.shareStoryToSocial(
this@ShareInstagramStory, this@ShareInstagramStory,
path.toUri() uri
) )
} }
} }
binding.shareButton.setTextColor( shareButton.setTextColor(
MaterialValueHelper.getPrimaryTextColor( MaterialValueHelper.getPrimaryTextColor(
this, this,
ColorUtil.isColorLight(accentColor()) ColorUtil.isColorLight(ThemeStore.accentColor(this))
) )
) )
binding.shareButton.backgroundTintList = shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(accentColor())
} }
private fun setColors(color: Int) { private fun setColors(colorLight: Boolean, color: Int) {
binding.mainContent.background = setLightStatusbar(colorLight)
toolbar.setTitleTextColor(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
colorLight
)
)
toolbar.navigationIcon?.setTintList(
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
colorLight
)
)
)
mainContent.background =
GradientDrawable( GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM, GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(color, Color.BLACK) intArrayOf(color, Color.BLACK)

View file

@ -0,0 +1,262 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.graphics.Paint
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.dialogs.UpiPaymentBottomSheetDialogFragment
import code.name.monkey.retromusic.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails
import kotlinx.android.synthetic.main.activity_about.toolbar
import kotlinx.android.synthetic.main.activity_donation.*
import java.lang.ref.WeakReference
import java.util.*
class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids
private const val TEZ_REQUEST_CODE = 123
}
var billingProcessor: BillingProcessor? = null
private var skuDetailsLoadAsyncTask: AsyncTask<*, *, *>? = null
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
fun donate(i: Int) {
val ids = resources.getStringArray(DONATION_PRODUCT_IDS)
billingProcessor?.purchase(this, ids[i])
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_donation)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
TintHelper.setTint(progress, ThemeStore.accentColor(this))
donation.setTextColor(ThemeStore.accentColor(this))
MaterialUtil.setTint(upiClick)
upiClick.setOnClickListener {
UpiPaymentBottomSheetDialogFragment().show(
supportFragmentManager,
UpiPaymentBottomSheetDialogFragment.TAG
)
}
}
private fun setupToolbar() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
}
override fun onBillingInitialized() {
loadSkuDetails()
}
private fun loadSkuDetails() {
if (skuDetailsLoadAsyncTask != null) {
skuDetailsLoadAsyncTask!!.cancel(false)
}
skuDetailsLoadAsyncTask = SkuDetailsLoadAsyncTask(this).execute()
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
//loadSkuDetails();
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
}
override fun onBillingError(errorCode: Int, error: Throwable?) {
Log.e(TAG, "Billing error: code = $errorCode", error)
}
override fun onPurchaseHistoryRestored() {
//loadSkuDetails();
Toast.makeText(this, R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor!!.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
if (requestCode == TEZ_REQUEST_CODE) {
// Process based on the data in response.
Log.d("result", data!!.getStringExtra("Status"))
}
}
override fun onDestroy() {
billingProcessor?.release()
skuDetailsLoadAsyncTask?.cancel(true)
super.onDestroy()
}
}
private class SkuDetailsLoadAsyncTask internal constructor(supportDevelopmentActivity: SupportDevelopmentActivity) :
AsyncTask<Void, Void, List<SkuDetails>>() {
private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference(
supportDevelopmentActivity
)
override fun onPreExecute() {
super.onPreExecute()
val supportDevelopmentActivity = weakReference.get() ?: return
supportDevelopmentActivity.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.recyclerView.visibility = View.GONE
}
override fun doInBackground(vararg params: Void): List<SkuDetails>? {
val dialog = weakReference.get()
if (dialog != null) {
val ids =
dialog.resources.getStringArray(SupportDevelopmentActivity.DONATION_PRODUCT_IDS)
return dialog.billingProcessor!!.getPurchaseListingDetails(ArrayList(Arrays.asList(*ids)))
}
cancel(false)
return null
}
override fun onPostExecute(skuDetails: List<SkuDetails>?) {
super.onPostExecute(skuDetails)
val dialog = weakReference.get() ?: return
if (skuDetails == null || skuDetails.isEmpty()) {
dialog.progressContainer.visibility = View.GONE
return
}
dialog.progressContainer.visibility = View.GONE
dialog.recyclerView.itemAnimator = DefaultItemAnimator()
dialog.recyclerView.layoutManager = GridLayoutManager(dialog, 2)
dialog.recyclerView.adapter = SkuDetailsAdapter(dialog, skuDetails)
dialog.recyclerView.visibility = View.VISIBLE
}
}
class SkuDetailsAdapter(
private var donationsDialog: SupportDevelopmentActivity, objects: List<SkuDetails>
) : RecyclerView.Adapter<SkuDetailsAdapter.ViewHolder>() {
private var skuDetailsList: List<SkuDetails> = ArrayList()
init {
skuDetailsList = objects
}
private fun getIcon(position: Int): Int {
return when (position) {
0 -> R.drawable.ic_cookie_white_24dp
1 -> R.drawable.ic_take_away_white_24dp
2 -> R.drawable.ic_take_away_coffe_white_24dp
3 -> R.drawable.ic_beer_white_24dp
4 -> R.drawable.ic_fast_food_meal_white_24dp
5 -> R.drawable.ic_popcorn_white_24dp
6 -> R.drawable.ic_card_giftcard_white_24dp
7 -> R.drawable.ic_food_croissant_white_24dp
else -> R.drawable.ic_card_giftcard_white_24dp
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(donationsDialog).inflate(
LAYOUT_RES_ID,
viewGroup,
false
)
)
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val skuDetails = skuDetailsList[i]
viewHolder.title.text = skuDetails.title.replace("(Retro Music Player)", "")
.trim { it <= ' ' }
viewHolder.text.text = skuDetails.description
viewHolder.text.visibility = View.GONE
viewHolder.price.text = skuDetails.priceText
viewHolder.image.setImageResource(getIcon(i))
val purchased = donationsDialog.billingProcessor!!.isPurchased(skuDetails.productId)
val titleTextColor = if (purchased) ATHUtil.resolveColor(
donationsDialog,
android.R.attr.textColorHint
) else textColorPrimary(donationsDialog)
val contentTextColor =
if (purchased) titleTextColor else textColorSecondary(donationsDialog)
viewHolder.title.setTextColor(titleTextColor)
viewHolder.text.setTextColor(contentTextColor)
viewHolder.price.setTextColor(titleTextColor)
strikeThrough(viewHolder.title, purchased)
strikeThrough(viewHolder.text, purchased)
strikeThrough(viewHolder.price, purchased)
viewHolder.itemView.setOnTouchListener { _, _ -> purchased }
viewHolder.itemView.setOnClickListener { donationsDialog.donate(i) }
}
override fun getItemCount(): Int {
return skuDetailsList.size
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var title: TextView = view.findViewById(R.id.itemTitle)
var text: TextView = view.findViewById(R.id.itemText)
var price: TextView = view.findViewById(R.id.itemPrice)
var image: AppCompatImageView = view.findViewById(R.id.itemImage)
}
companion object {
@LayoutRes
private val LAYOUT_RES_ID = R.layout.item_donation_option
private fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
textView.paintFlags =
if (strikeThrough) textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
}

View file

@ -0,0 +1,267 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore.Images.Media
import android.provider.MediaStore.Images.Media.getBitmap
import android.text.TextUtils
import android.view.MenuItem
import android.widget.Toast
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.util.Compressor
import code.name.monkey.retromusic.util.ImageUtil.getResizedBitmap
import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.afollestad.materialdialogs.list.listItems
import kotlinx.android.synthetic.main.activity_user_info.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
MaterialUtil.setTint(nameContainer, false)
name.setText(PreferenceUtil.getInstance(this).userName)
if (PreferenceUtil.getInstance(this).profileImage.isNotEmpty()) {
loadImageFromStorage(PreferenceUtil.getInstance(this).profileImage)
}
if (PreferenceUtil.getInstance(this).bannerImage.isNotEmpty()) {
loadBannerFromStorage(PreferenceUtil.getInstance(this).bannerImage)
}
userImage.setOnClickListener {
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
cornerRadius(PreferenceUtil.getInstance(this@UserInfoActivity).dialogCorner)
title(text = getString(R.string.set_photo))
listItems(
items = listOf(
getString(R.string.new_profile_photo),
getString(R.string.remove_profile_photo)
)
) { _, position, _ ->
when (position) {
0 -> pickNewPhoto()
1 -> PreferenceUtil.getInstance(this@UserInfoActivity).saveProfileImage("")
}
}
}
}
bannerSelect.setOnClickListener {
MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
cornerRadius(PreferenceUtil.getInstance(this@UserInfoActivity).dialogCorner)
title(R.string.select_banner_photo)
listItems(
items = listOf(
getString(R.string.new_banner_photo),
getString(R.string.remove_banner_photo)
)
)
{ _, position, _ ->
when (position) {
0 -> selectBannerImage()
1 -> PreferenceUtil.getInstance(this@UserInfoActivity).setBannerImagePath("")
}
}
}
}
next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm name is empty", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
PreferenceUtil.getInstance(this).userName = nameString
setResult(Activity.RESULT_OK)
finish()
}
next.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
)
.apply {
next.setTextColor(this)
next.iconTint = this
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private fun selectBannerImage() {
val pickImageIntent = Intent(Intent.ACTION_PICK, Media.EXTERNAL_CONTENT_URI)
pickImageIntent.type = "image/*"
//pickImageIntent.putExtra("crop", "true")
pickImageIntent.putExtra("outputX", 1290)
pickImageIntent.putExtra("outputY", 720)
pickImageIntent.putExtra("aspectX", 16)
pickImageIntent.putExtra("aspectY", 9)
pickImageIntent.putExtra("scale", true)
//intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
Intent.createChooser(pickImageIntent, "Select Picture"),
PICK_BANNER_REQUEST
)
}
private fun pickNewPhoto() {
val pickImageIntent = Intent(Intent.ACTION_PICK, Media.EXTERNAL_CONTENT_URI)
pickImageIntent.type = "image/*"
pickImageIntent.putExtra("crop", "true")
pickImageIntent.putExtra("outputX", 512)
pickImageIntent.putExtra("outputY", 512)
pickImageIntent.putExtra("aspectX", 1)
pickImageIntent.putExtra("aspectY", 1)
pickImageIntent.putExtra("scale", true)
startActivityForResult(
Intent.createChooser(pickImageIntent, "Select Picture"),
PICK_IMAGE_REQUEST
)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null) {
when (requestCode) {
PICK_IMAGE_REQUEST -> {
try {
data.data?.let {
val bitmap = getResizedBitmap(
getBitmap(contentResolver, it),
PROFILE_ICON_SIZE
)
val profileImagePath = saveToInternalStorage(bitmap, USER_PROFILE)
PreferenceUtil.getInstance(this).saveProfileImage(profileImagePath)
loadImageFromStorage(profileImagePath)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
PICK_BANNER_REQUEST -> {
try {
data.data?.let {
val bitmap = getBitmap(contentResolver, it)
val profileImagePath = saveToInternalStorage(bitmap, USER_BANNER)
PreferenceUtil.getInstance(this).setBannerImagePath(profileImagePath)
loadBannerFromStorage(profileImagePath)
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
}
private fun getImagePath(aUri: Uri, aSelection: String?): String? {
var path: String? = null
val cursor = App.getContext().contentResolver.query(aUri, null, aSelection, null, null)
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(Media.DATA))
}
cursor.close()
}
return path
}
private fun loadBannerFromStorage(profileImagePath: String) {
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
val bitmap = Compressor(this@UserInfoActivity).setQuality(100)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.compressToBitmap(File(profileImagePath, USER_BANNER))
withContext(Dispatchers.Main) { bannerImage.setImageBitmap(bitmap) }
}
}
}
private fun loadImageFromStorage(path: String) {
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
val bitmap = Compressor(this@UserInfoActivity)
.setMaxHeight(300)
.setMaxWidth(300)
.setQuality(75)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.compressToBitmap(File(path, USER_PROFILE))
withContext(Dispatchers.Main) { userImage.setImageBitmap(bitmap) }
}
}
}
private fun saveToInternalStorage(bitmapImage: Bitmap, userBanner: String): String {
val cw = ContextWrapper(this)
val directory = cw.getDir("imageDir", Context.MODE_PRIVATE)
val myPath = File(directory, userBanner)
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(myPath)
// Use the compress method on the BitMap object to write image to the OutputStream
bitmapImage.compress(Bitmap.CompressFormat.WEBP, 100, fos)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
fos?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return directory.absolutePath
}
companion object {
private const val PICK_IMAGE_REQUEST = 9002
private const val PICK_BANNER_REQUEST = 9004
private const val PROFILE_ICON_SIZE = 400
}
}
fun Activity.pickImage(requestCode: Int) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
startActivityForResult(this, requestCode)
}
}

View file

@ -0,0 +1,102 @@
package code.name.monkey.retromusic.activities;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.widget.NestedScrollView;
import com.google.android.material.appbar.AppBarLayout;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class WhatsNewActivity extends AbsBaseActivity {
AppBarLayout appBarLayout;
Toolbar toolbar;
WebView webView;
private static String colorToCSS(int color) {
return String.format("rgb(%d, %d, %d)", Color.red(color), Color.green(color),
Color.blue(color)); // on API 29, WebView doesn't load with hex colors
}
private static void setChangelogRead(@NonNull Context context) {
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
int currentVersion = pInfo.versionCode;
PreferenceUtil.getInstance(context).setLastChangeLogVersion(currentVersion);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
webView = findViewById(R.id.webView);
toolbar = findViewById(R.id.toolbar);
appBarLayout = findViewById(R.id.appBarLayout);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
//setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(toolbar);
NestedScrollView nestedScrollView = findViewById(R.id.container);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("retro-changelog.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface,
Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String changeLog = buf.toString()
.replace("{style-placeholder}",
String.format("body { background-color: %s; color: %s; }", backgroundColor, contentColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace("{link-color-active}",
colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData("<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
setChangelogRead(this);
}
}

View file

@ -1,153 +0,0 @@
package code.name.monkey.retromusic.activities
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.core.content.pm.PackageInfoCompat
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.util.ATHUtil.isWindowBackgroundDark
import code.name.monkey.appthemehelper.util.ColorUtil.isColorLight
import code.name.monkey.appthemehelper.util.ColorUtil.lightenColor
import code.name.monkey.appthemehelper.util.MaterialValueHelper.getPrimaryTextColor
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.databinding.FragmentWhatsNewBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.openUrl
import code.name.monkey.retromusic.util.PreferenceUtil.lastVersion
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import java.nio.charset.StandardCharsets
import java.util.*
class WhatsNewFragment : BottomSheetDialogFragment() {
private var _binding: FragmentWhatsNewBinding? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentWhatsNewBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
try {
val buf = StringBuilder()
val stream = requireContext().assets.open("retro-changelog.html")
stream.reader(StandardCharsets.UTF_8).buffered().use { br ->
var str: String?
while (br.readLine().also { str = it } != null) {
buf.append(str)
}
}
// Inject color values for WebView body background and links
val isDark = isWindowBackgroundDark(requireContext())
val accentColor = accentColor()
binding.webView.setBackgroundColor(0)
val contentColor = colorToCSS(Color.parseColor(if (isDark) "#ffffff" else "#000000"))
val textColor = colorToCSS(Color.parseColor(if (isDark) "#60FFFFFF" else "#80000000"))
val accentColorString = colorToCSS(accentColor())
val cardBackgroundColor =
colorToCSS(Color.parseColor(if (isDark) "#353535" else "#ffffff"))
val accentTextColor = colorToCSS(
getPrimaryTextColor(
requireContext(), isColorLight(accentColor)
)
)
val changeLog = buf.toString()
.replace(
"{style-placeholder}",
"body { color: $contentColor; } li {color: $textColor;} h3 {color: $accentColorString;} .tag {background-color: $accentColorString; color: $accentTextColor; } div{background-color: $cardBackgroundColor;}"
)
.replace("{link-color}", colorToCSS(accentColor()))
.replace(
"{link-color-active}",
colorToCSS(
lightenColor(accentColor())
)
)
binding.webView.loadData(changeLog, "text/html", "UTF-8")
binding.webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
val url = request?.url ?: return false
//you can do checks here e.g. url.host equals to target one
startActivity(Intent(Intent.ACTION_VIEW, url))
return true
}
}
} catch (e: Throwable) {
binding.webView.loadData(
"<h1>Unable to load</h1><p>" + e.localizedMessage + "</p>", "text/html", "UTF-8"
)
}
setChangelogRead(requireContext())
binding.tgFab.setOnClickListener {
openUrl(Constants.TELEGRAM_CHANGE_LOG)
}
binding.tgFab.accentColor()
binding.tgFab.shrink()
binding.container.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
val dy = scrollY - oldScrollY
if (dy > 0) {
binding.tgFab.shrink()
} else if (dy < 0) {
binding.tgFab.extend()
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
companion object {
const val TAG = "WhatsNewFragment"
private fun colorToCSS(color: Int): String {
return String.format(
Locale.getDefault(),
"rgba(%d, %d, %d, %d)",
Color.red(color),
Color.green(color),
Color.blue(color),
Color.alpha(color)
) // on API 29, WebView doesn't load with hex colors
}
private fun setChangelogRead(context: Context) {
try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
lastVersion = currentVersion
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
fun showChangeLog(activity: FragmentActivity) {
val pInfo = activity.packageManager.getPackageInfo(activity.packageName, 0)
val currentVersion = PackageInfoCompat.getLongVersionCode(pInfo)
if (currentVersion > lastVersion && !BuildConfig.DEBUG) {
val changelogBottomSheet = WhatsNewFragment()
changelogBottomSheet.show(activity.supportFragmentManager, TAG)
}
}
}
}

View file

@ -1,39 +1,18 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.getSystemService import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.rootView
import code.name.monkey.retromusic.util.logD
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
abstract class AbsBaseActivity : AbsThemeActivity() { abstract class AbsBaseActivity : AbsThemeActivity() {
@ -54,7 +33,7 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
} }
private val snackBarContainer: View private val snackBarContainer: View
get() = rootView get() = window.decorView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -64,12 +43,19 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
permissionDeniedMessage = null permissionDeniedMessage = null
} }
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
if (!hasPermissions()) {
requestPermissions()
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val hasPermissions = hasPermissions() val hasPermissions = hasPermissions()
if (hasPermissions != hadPermissions) { if (hasPermissions != hadPermissions) {
hadPermissions = hasPermissions hadPermissions = hasPermissions
if (VersionUtils.hasMarshmallow()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
onHasPermissionsChanged(hasPermissions) onHasPermissionsChanged(hasPermissions)
} }
} }
@ -77,7 +63,6 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
protected open fun onHasPermissionsChanged(hasPermissions: Boolean) { protected open fun onHasPermissionsChanged(hasPermissions: Boolean) {
// implemented by sub classes // implemented by sub classes
logD(hasPermissions)
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@ -92,51 +77,50 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
} }
protected open fun requestPermissions() { protected open fun requestPermissions() {
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, PERMISSION_REQUEST)
}
} }
protected fun hasPermissions(): Boolean { protected fun hasPermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (permission in permissions) { for (permission in permissions) {
if (ActivityCompat.checkSelfPermission(this, if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permission) != PackageManager.PERMISSION_GRANTED
) {
return false return false
} }
} }
}
return true return true
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,
grantResults: IntArray, grantResults: IntArray
) { ) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST) { if (requestCode == PERMISSION_REQUEST) {
for (grantResult in grantResults) { for (grantResult in grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) { if (grantResult != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale( if (ActivityCompat.shouldShowRequestPermissionRationale(
this@AbsBaseActivity, Manifest.permission.READ_EXTERNAL_STORAGE, this@AbsBaseActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE
) || ActivityCompat.shouldShowRequestPermissionRationale(
this@AbsBaseActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE,
) )
) { ) {
//User has deny from permission dialog //User has deny from permission dialog
Snackbar.make( Snackbar.make(
snackBarContainer, snackBarContainer,
permissionDeniedMessage!!, permissionDeniedMessage!!,
Snackbar.LENGTH_SHORT Snackbar.LENGTH_INDEFINITE
) )
.setAction(R.string.action_grant) { requestPermissions() } .setAction(code.name.monkey.retromusic.R.string.action_grant) { requestPermissions() }
.setActionTextColor(accentColor()).show() .setActionTextColor(ThemeStore.accentColor(this)).show()
} else { } else {
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page // User has deny permission and checked never show permission dialog so you can redirect to Application settings page
Snackbar.make( Snackbar.make(
snackBarContainer, snackBarContainer,
permissionDeniedMessage!!, permissionDeniedMessage!!,
Snackbar.LENGTH_INDEFINITE Snackbar.LENGTH_INDEFINITE
) ).setAction(code.name.monkey.retromusic.R.string.action_settings) {
.setAction(R.string.action_settings) {
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts( val uri = Uri.fromParts(
@ -146,59 +130,17 @@ abstract class AbsBaseActivity : AbsThemeActivity() {
) )
intent.data = uri intent.data = uri
startActivity(intent) startActivity(intent)
}.setActionTextColor(accentColor()).show() }.setActionTextColor(ThemeStore.accentColor(this)).show()
} }
return return
} }
} }
hadPermissions = true hadPermissions = true
onHasPermissionsChanged(true) onHasPermissionsChanged(true)
} else if (requestCode == BLUETOOTH_PERMISSION_REQUEST) {
for (grantResult in grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this@AbsBaseActivity, Manifest.permission.BLUETOOTH_CONNECT
)
) {
// User has deny from permission dialog
Snackbar.make(
snackBarContainer,
R.string.permission_bluetooth_denied,
Snackbar.LENGTH_SHORT
)
.setAction(R.string.action_grant) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
BLUETOOTH_PERMISSION_REQUEST)
}
.setActionTextColor(accentColor()).show()
}
}
}
} }
} }
companion object { companion object {
const val PERMISSION_REQUEST = 100 const val PERMISSION_REQUEST = 100
const val BLUETOOTH_PERMISSION_REQUEST = 101
}
// this lets keyboard close when clicked in background
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
val v = currentFocus
if (v is EditText) {
val outRect = Rect()
v.getGlobalVisibleRect(outRect)
if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
v.clearFocus()
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(
v.windowToken,
0
)
}
}
}
return super.dispatchTouchEvent(event)
} }
} }

View file

@ -1,49 +1,20 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.Manifest import android.Manifest
import android.content.* import android.content.*
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.toPlayCount
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.MEDIA_STORE_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.QUEUE_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.REPEAT_MODE_CHANGED
import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_CHANGED
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.logD
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>()
private val mMusicServiceEventListeners = ArrayList<IMusicServiceEventListener>()
private val repository: RealRepository by inject()
private var serviceToken: MusicPlayerRemote.ServiceToken? = null private var serviceToken: MusicPlayerRemote.ServiceToken? = null
private var musicStateReceiver: MusicStateReceiver? = null private var musicStateReceiver: MusicStateReceiver? = null
private var receiverRegistered: Boolean = false private var receiverRegistered: Boolean = false
@ -72,15 +43,15 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
} }
} }
fun addMusicServiceEventListener(listenerI: IMusicServiceEventListener?) { fun addMusicServiceEventListener(listener: MusicServiceEventListener?) {
if (listenerI != null) { if (listener != null) {
mMusicServiceEventListeners.add(listenerI) mMusicServiceEventListeners.add(listener)
} }
} }
fun removeMusicServiceEventListener(listenerI: IMusicServiceEventListener?) { fun removeMusicServiceEventListener(listener: MusicServiceEventListener?) {
if (listenerI != null) { if (listener != null) {
mMusicServiceEventListeners.remove(listenerI) mMusicServiceEventListeners.remove(listener)
} }
} }
@ -97,7 +68,8 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
filter.addAction(MEDIA_STORE_CHANGED) filter.addAction(MEDIA_STORE_CHANGED)
filter.addAction(FAVORITE_STATE_CHANGED) filter.addAction(FAVORITE_STATE_CHANGED)
ContextCompat.registerReceiver(this, musicStateReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED) registerReceiver(musicStateReceiver, filter)
receiverRegistered = true receiverRegistered = true
} }
@ -121,16 +93,6 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
for (listener in mMusicServiceEventListeners) { for (listener in mMusicServiceEventListeners) {
listener.onPlayingMetaChanged() listener.onPlayingMetaChanged()
} }
lifecycleScope.launch(Dispatchers.IO) {
if (!PreferenceUtil.pauseHistory) {
repository.upsertSongInHistory(MusicPlayerRemote.currentSong)
}
val song = repository.findSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
?.apply { playCount += 1 }
?: MusicPlayerRemote.currentSong.toPlayCount()
repository.upsertSongInPlayCount(song)
}
} }
override fun onQueueChanged() { override fun onQueueChanged() {
@ -163,12 +125,6 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
} }
} }
override fun onFavoriteStateChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onFavoriteStateChanged()
}
}
override fun onHasPermissionsChanged(hasPermissions: Boolean) { override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions) super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED) val intent = Intent(MEDIA_STORE_CHANGED)
@ -177,21 +133,14 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
true true
) // just in case we need to know this at some point ) // just in case we need to know this at some point
sendBroadcast(intent) sendBroadcast(intent)
logD("sendBroadcast $hasPermissions")
} }
override fun getPermissionsToRequest(): Array<String> { override fun getPermissionsToRequest(): Array<String> {
return mutableListOf<String>().apply { return arrayOf(
if (VersionUtils.hasT()) { Manifest.permission.READ_EXTERNAL_STORAGE,
add(Manifest.permission.READ_MEDIA_AUDIO) Manifest.permission.WRITE_EXTERNAL_STORAGE,
add(Manifest.permission.POST_NOTIFICATIONS) Manifest.permission.BLUETOOTH
} else { )
add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (!VersionUtils.hasR()) {
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}.toTypedArray()
} }
private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() { private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
@ -203,8 +152,7 @@ abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventLi
val activity = reference.get() val activity = reference.get()
if (activity != null && action != null) { if (activity != null && action != null) {
when (action) { when (action) {
FAVORITE_STATE_CHANGED -> activity.onFavoriteStateChanged() FAVORITE_STATE_CHANGED, META_CHANGED -> activity.onPlayingMetaChanged()
META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged() QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged() PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged() REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()

View file

@ -1,226 +1,112 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.animation.PathInterpolator
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback import androidx.annotation.LayoutRes
import androidx.core.animation.doOnEnd import androidx.fragment.app.Fragment
import androidx.core.view.* import code.name.monkey.appthemehelper.util.ATHUtil
import androidx.fragment.app.commit import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.ADAPTIVE_COLOR_APP
import code.name.monkey.retromusic.ALBUM_COVER_STYLE
import code.name.monkey.retromusic.ALBUM_COVER_TRANSFORM
import code.name.monkey.retromusic.CAROUSEL_EFFECT
import code.name.monkey.retromusic.CIRCLE_PLAY_BUTTON
import code.name.monkey.retromusic.EXTRA_SONG_INFO
import code.name.monkey.retromusic.KEEP_SCREEN_ON
import code.name.monkey.retromusic.LIBRARY_CATEGORIES
import code.name.monkey.retromusic.NOW_PLAYING_SCREEN_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SCREEN_ON_LYRICS import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.SWIPE_ANYWHERE_NOW_PLAYING import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.SWIPE_DOWN_DISMISS import code.name.monkey.retromusic.fragments.MiniPlayerFragment
import code.name.monkey.retromusic.TAB_TEXT_MODE
import code.name.monkey.retromusic.TOGGLE_ADD_CONTROLS
import code.name.monkey.retromusic.TOGGLE_FULL_SCREEN
import code.name.monkey.retromusic.TOGGLE_VOLUME
import code.name.monkey.retromusic.activities.PermissionActivity
import code.name.monkey.retromusic.databinding.SlidingMusicPanelLayoutBinding
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.NowPlayingScreen import code.name.monkey.retromusic.fragments.NowPlayingScreen
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.other.MiniPlayerFragment
import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
import code.name.monkey.retromusic.fragments.player.card.CardFragment import code.name.monkey.retromusic.fragments.player.card.CardFragment
import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment
import code.name.monkey.retromusic.fragments.player.circle.CirclePlayerFragment import code.name.monkey.retromusic.fragments.player.circle.CirclePlayerFragment
import code.name.monkey.retromusic.fragments.player.classic.ClassicPlayerFragment
import code.name.monkey.retromusic.fragments.player.color.ColorFragment import code.name.monkey.retromusic.fragments.player.color.ColorFragment
import code.name.monkey.retromusic.fragments.player.fit.FitFragment import code.name.monkey.retromusic.fragments.player.fit.FitFragment
import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment
import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment
import code.name.monkey.retromusic.fragments.player.gradient.GradientPlayerFragment
import code.name.monkey.retromusic.fragments.player.material.MaterialFragment import code.name.monkey.retromusic.fragments.player.material.MaterialFragment
import code.name.monkey.retromusic.fragments.player.md3.MD3PlayerFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
import code.name.monkey.retromusic.fragments.player.peek.PeekPlayerFragment import code.name.monkey.retromusic.fragments.player.peak.PeakPlayerFragment
import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.CategoryInfo import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.ViewUtil import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import code.name.monkey.retromusic.util.logD
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_DRAGGING
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_SETTLING
import com.google.android.material.bottomsheet.BottomSheetBehavior.from
import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(), abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
SharedPreferences.OnSharedPreferenceChangeListener { AbsPlayerFragment.Callbacks {
companion object { companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
} }
var fromNotification = false
private var windowInsets: WindowInsetsCompat? = null
protected val libraryViewModel by viewModel<LibraryViewModel>()
private lateinit var bottomSheetBehavior: BottomSheetBehavior<FrameLayout> private lateinit var bottomSheetBehavior: BottomSheetBehavior<FrameLayout>
private lateinit var playerFragment: AbsPlayerFragment
private var miniPlayerFragment: MiniPlayerFragment? = null private var miniPlayerFragment: MiniPlayerFragment? = null
private var nowPlayingScreen: NowPlayingScreen? = null private var playerFragment: AbsPlayerFragment? = null
private var currentNowPlayingScreen: NowPlayingScreen? = null
private var navigationBarColor: Int = 0
private var taskColor: Int = 0 private var taskColor: Int = 0
private var paletteColor: Int = Color.WHITE private var lightStatusBar: Boolean = false
private var navigationBarColor = 0 private var lightNavigationBar: Boolean = false
private var navigationBarColorAnimator: ValueAnimator? = null
protected abstract fun createContentView(): View
private val panelState: Int private val panelState: Int
get() = bottomSheetBehavior.state get() = bottomSheetBehavior.state
private lateinit var binding: SlidingMusicPanelLayoutBinding
private var isInOneTabMode = false
private var navigationBarColorAnimator: ValueAnimator? = null private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
private val argbEvaluator: ArgbEvaluator = ArgbEvaluator()
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
println("Handle back press ${bottomSheetBehavior.state}")
if (!handleBackPress()) {
remove()
onBackPressedDispatcher.onBackPressed()
}
}
}
private val bottomSheetCallbackList by lazy {
object : BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset) setMiniPlayerAlphaProgress(slideOffset)
navigationBarColorAnimator?.cancel() dimBackground.show()
setNavigationBarColorPreOreo( dimBackground.alpha = slideOffset
argbEvaluator.evaluate(
slideOffset,
surfaceColor(),
navigationBarColor
) as Int
)
} }
override fun onStateChanged(bottomSheet: View, newState: Int) { override fun onStateChanged(bottomSheet: View, newState: Int) {
onBackPressedCallback.isEnabled = newState == STATE_EXPANDED
when (newState) { when (newState) {
STATE_EXPANDED -> { BottomSheetBehavior.STATE_EXPANDED -> {
onPanelExpanded() onPanelExpanded()
if (PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) {
keepScreenOn(true)
} }
} BottomSheetBehavior.STATE_COLLAPSED -> {
STATE_COLLAPSED -> {
onPanelCollapsed() onPanelCollapsed()
if ((PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics) || !PreferenceUtil.isScreenOnEnabled) { dimBackground.hide()
keepScreenOn(false)
} }
}
STATE_SETTLING, STATE_DRAGGING -> {
if (fromNotification) {
binding.navigationView.bringToFront()
fromNotification = false
}
}
STATE_HIDDEN -> {
MusicPlayerRemote.clearQueue()
}
else -> { else -> {
logD("Do a flip")
}
}
}
}
}
fun getBottomSheetBehavior() = bottomSheetBehavior }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (!hasPermissions()) { setContentView(createContentView())
startActivity(Intent(this, PermissionActivity::class.java))
finish()
}
binding = SlidingMusicPanelLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.root.setOnApplyWindowInsetsListener { _, insets ->
windowInsets = WindowInsetsCompat.toWindowInsetsCompat(insets)
insets
}
chooseFragmentForTheme() chooseFragmentForTheme()
setupSlidingUpPanel() setupSlidingUpPanel()
setupBottomSheet()
updateColor()
if (!PreferenceUtil.materialYou) {
binding.slidingPanel.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
navigationView.backgroundTintList = ColorStateList.valueOf(darkAccentColor())
}
navigationBarColor = surfaceColor() updateTabs()
onBackPressedDispatcher.addCallback(onBackPressedCallback) bottomSheetBehavior = BottomSheetBehavior.from(slidingPanel)
}
private fun setupBottomSheet() { val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY)
bottomSheetBehavior = from(binding.slidingPanel) dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss
bottomSheetBehavior.significantVelocityThreshold = 300
setMiniPlayerAlphaProgress(0F)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
PreferenceUtil.registerOnSharedPreferenceChangedListener(this) if (currentNowPlayingScreen != PreferenceUtil.getInstance(this).nowPlayingScreen) {
if (nowPlayingScreen != PreferenceUtil.nowPlayingScreen) {
postRecreate() postRecreate()
} }
if (bottomSheetBehavior.state == STATE_EXPANDED) { bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallbackList)
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
setMiniPlayerAlphaProgress(1f) setMiniPlayerAlphaProgress(1f)
} }
} }
@ -228,354 +114,264 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList) bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallbackList)
PreferenceUtil.unregisterOnSharedPreferenceChangedListener(this) if (navigationBarColorAnimator != null) navigationBarColorAnimator?.cancel() // just in case
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { protected fun wrapSlidingMusicPanel(@LayoutRes resId: Int): View {
when (key) { val slidingMusicPanelLayout =
SWIPE_DOWN_DISMISS -> { layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
bottomSheetBehavior.isHideable = PreferenceUtil.swipeDownToDismiss val contentContainer =
slidingMusicPanelLayout.findViewById<ViewGroup>(R.id.mainContentFrame)
layoutInflater.inflate(resId, contentContainer)
return slidingMusicPanelLayout
} }
TOGGLE_ADD_CONTROLS -> { private fun collapsePanel() {
miniPlayerFragment?.setUpButtons() bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
NOW_PLAYING_SCREEN_ID -> {
chooseFragmentForTheme()
binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> {
height = if (nowPlayingScreen != Peek) {
ViewGroup.LayoutParams.MATCH_PARENT
} else {
ViewGroup.LayoutParams.WRAP_CONTENT
}
onServiceConnected()
}
}
ALBUM_COVER_TRANSFORM, CAROUSEL_EFFECT,
ALBUM_COVER_STYLE, TOGGLE_VOLUME, EXTRA_SONG_INFO, CIRCLE_PLAY_BUTTON,
-> {
chooseFragmentForTheme()
onServiceConnected()
}
SWIPE_ANYWHERE_NOW_PLAYING -> {
playerFragment.addSwipeDetector()
}
ADAPTIVE_COLOR_APP -> {
if (PreferenceUtil.nowPlayingScreen in listOf(Normal, Material, Flat)) {
chooseFragmentForTheme()
onServiceConnected()
}
}
LIBRARY_CATEGORIES -> {
updateTabs()
}
TAB_TEXT_MODE -> {
navigationView.labelVisibilityMode = PreferenceUtil.tabTitleMode
}
TOGGLE_FULL_SCREEN -> {
recreate()
}
SCREEN_ON_LYRICS -> {
keepScreenOn(bottomSheetBehavior.state == STATE_EXPANDED && PreferenceUtil.lyricsScreenOn && PreferenceUtil.showLyrics || PreferenceUtil.isScreenOnEnabled)
}
KEEP_SCREEN_ON -> {
maybeSetScreenOn()
}
}
}
fun collapsePanel() {
bottomSheetBehavior.state = STATE_COLLAPSED
} }
fun expandPanel() { fun expandPanel() {
bottomSheetBehavior.state = STATE_EXPANDED bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
setMiniPlayerAlphaProgress(1f)
} }
private fun setMiniPlayerAlphaProgress(progress: Float) { private fun setMiniPlayerAlphaProgress(progress: Float) {
if (progress < 0) return if (miniPlayerFragment?.view == null) return
val alpha = 1 - progress val alpha = 1 - progress
miniPlayerFragment?.view?.alpha = 1 - (progress / 0.2F) miniPlayerFragment?.view?.alpha = alpha
miniPlayerFragment?.view?.isGone = alpha == 0f // necessary to make the views below clickable
if (!isLandscape) { miniPlayerFragment?.view?.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
binding.navigationView.translationY = progress * 500
binding.navigationView.alpha = alpha
}
binding.playerFragmentContainer.alpha = (progress - 0.2F) / 0.2F
}
private fun animateNavigationBarColor(color: Int) { bottomNavigationView.translationY = progress * 500
if (VersionUtils.hasOreo()) return //bottomNavigationView.alpha = alpha
navigationBarColorAnimator?.cancel()
navigationBarColorAnimator = ValueAnimator
.ofArgb(window.navigationBarColor, color).apply {
duration = ViewUtil.RETRO_MUSIC_ANIM_TIME.toLong()
interpolator = PathInterpolator(0.4f, 0f, 1f, 1f)
addUpdateListener { animation: ValueAnimator ->
setNavigationBarColorPreOreo(
animation.animatedValue as Int
)
}
start()
}
} }
open fun onPanelCollapsed() { open fun onPanelCollapsed() {
setMiniPlayerAlphaProgress(0F)
// restore values // restore values
animateNavigationBarColor(surfaceColor()) super.setLightStatusbar(lightStatusBar)
setLightStatusBarAuto() super.setTaskDescriptionColor(taskColor)
setLightNavigationBarAuto() super.setNavigationbarColor(navigationBarColor)
setTaskDescriptionColor(taskColor) super.setLightNavigationBar(lightNavigationBar)
//playerFragment?.onHide()
playerFragment?.setMenuVisibility(false)
playerFragment?.userVisibleHint = false
playerFragment?.onHide()
} }
open fun onPanelExpanded() { open fun onPanelExpanded() {
setMiniPlayerAlphaProgress(1F) val playerFragmentColor = playerFragment!!.paletteColor
super.setTaskDescriptionColor(playerFragmentColor)
playerFragment?.setMenuVisibility(true)
playerFragment?.userVisibleHint = true
playerFragment?.onShow()
onPaletteColorChanged() onPaletteColorChanged()
//playerFragment?.onShow()
} }
private fun setupSlidingUpPanel() { private fun setupSlidingUpPanel() {
binding.slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object : slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() { override fun onGlobalLayout() {
binding.slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this) slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (nowPlayingScreen != Peek) { if (currentNowPlayingScreen != PEAK) {
binding.slidingPanel.updateLayoutParams<ViewGroup.LayoutParams> { val params = slidingPanel.layoutParams as ViewGroup.LayoutParams
height = ViewGroup.LayoutParams.MATCH_PARENT params.height = ViewGroup.LayoutParams.MATCH_PARENT
} slidingPanel.layoutParams = params
} }
when (panelState) { when (panelState) {
STATE_EXPANDED -> onPanelExpanded() BottomSheetBehavior.STATE_EXPANDED -> onPanelExpanded()
STATE_COLLAPSED -> onPanelCollapsed() BottomSheetBehavior.STATE_COLLAPSED -> onPanelCollapsed()
else -> { else -> playerFragment!!.onHide()
// playerFragment!!.onHide()
}
} }
} }
}) })
} }
val navigationView get() = binding.navigationView fun toggleBottomNavigationView(toggle: Boolean) {
bottomNavigationView.visibility = if (toggle) View.GONE else View.VISIBLE
}
val slidingPanel get() = binding.slidingPanel fun getBottomNavigationView(): BottomNavigationBarTinted {
return bottomNavigationView
}
val isBottomNavVisible get() = navigationView.isVisible && navigationView is BottomNavigationView private fun hideBottomBar(hide: Boolean) {
val heightOfBar = resources.getDimensionPixelSize(R.dimen.mini_player_height)
val heightOfBarWithTabs =
resources.getDimensionPixelSize(R.dimen.mini_player_height_expanded)
if (hide) {
bottomSheetBehavior.isHideable = true
bottomSheetBehavior.peekHeight = 0
collapsePanel()
bottomNavigationView.elevation = DensityUtil.dip2px(this, 10f).toFloat()
} else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.elevation = DensityUtil.dip2px(this, 10f).toFloat()
bottomNavigationView.elevation = DensityUtil.dip2px(this, 10f).toFloat()
bottomSheetBehavior.isHideable = false
bottomSheetBehavior.peekHeight =
if (bottomNavigationView.visibility == View.VISIBLE) heightOfBarWithTabs else heightOfBar
}
}
}
fun setBottomBarVisibility(gone: Int) {
bottomNavigationView.visibility = gone
hideBottomBar(false)
}
private fun chooseFragmentForTheme() {
currentNowPlayingScreen = PreferenceUtil.getInstance(this).nowPlayingScreen
val fragment: Fragment = when (currentNowPlayingScreen) {
BLUR -> BlurPlayerFragment()
ADAPTIVE -> AdaptiveFragment()
NORMAL -> PlayerFragment()
CARD -> CardFragment()
BLUR_CARD -> CardBlurFragment()
FIT -> FitFragment()
FLAT -> FlatPlayerFragment()
FULL -> FullPlayerFragment()
PLAIN -> PlainPlayerFragment()
SIMPLE -> SimplePlayerFragment()
MATERIAL -> MaterialFragment()
COLOR -> ColorFragment()
TINY -> TinyPlayerFragment()
PEAK -> PeakPlayerFragment()
CIRCLE -> CirclePlayerFragment()
else -> PlayerFragment()
} // must implement AbsPlayerFragment
supportFragmentManager.beginTransaction().replace(R.id.playerFragmentContainer, fragment)
.commit()
supportFragmentManager.executePendingTransactions()
playerFragment =
supportFragmentManager.findFragmentById(R.id.playerFragmentContainer) as AbsPlayerFragment
miniPlayerFragment =
supportFragmentManager.findFragmentById(R.id.miniPlayerFragment) as MiniPlayerFragment
miniPlayerFragment?.view?.setOnClickListener { expandPanel() }
}
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
hideBottomSheet(false) if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false)
}
})
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
} }
override fun onQueueChanged() { override fun onQueueChanged() {
super.onQueueChanged() super.onQueueChanged()
// Mini player should be hidden in Playing Queue hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
// it may pop up if hideBottomSheet is called
if (currentFragment(R.id.fragment_container) !is PlayingQueueFragment) {
hideBottomSheet(MusicPlayerRemote.playingQueue.isEmpty())
}
} }
private fun handleBackPress(): Boolean { override fun onBackPressed() {
if (panelState == STATE_EXPANDED) { if (!handleBackPress()) super.onBackPressed()
}
open fun handleBackPress(): Boolean {
if (bottomSheetBehavior.peekHeight != 0 && playerFragment!!.onBackPressed()) return true
if (panelState == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel() collapsePanel()
return true return true
} }
return false return false
} }
private fun onPaletteColorChanged() { override fun onPaletteColorChanged() {
if (panelState == STATE_EXPANDED) { if (panelState == BottomSheetBehavior.STATE_EXPANDED) {
navigationBarColor = surfaceColor() val paletteColor = playerFragment!!.paletteColor
setTaskDescColor(paletteColor) super.setTaskDescriptionColor(paletteColor)
val isColorLight = paletteColor.isColorLight
if (PreferenceUtil.isAdaptiveColor && (nowPlayingScreen == Normal || nowPlayingScreen == Flat || nowPlayingScreen == Material)) { val isColorLight = ColorUtil.isColorLight(paletteColor)
setLightNavigationBar(true)
setLightStatusBar(isColorLight) if (PreferenceUtil.getInstance(this).adaptiveColor && (currentNowPlayingScreen == NORMAL || currentNowPlayingScreen == FLAT)) {
} else if (nowPlayingScreen == Card || nowPlayingScreen == Blur || nowPlayingScreen == BlurCard) { super.setLightNavigationBar(true)
animateNavigationBarColor(Color.BLACK) super.setLightStatusbar(isColorLight)
navigationBarColor = Color.BLACK } else if (currentNowPlayingScreen == FULL || currentNowPlayingScreen == CARD || currentNowPlayingScreen == BLUR || currentNowPlayingScreen == BLUR_CARD) {
setLightStatusBar(false) super.setLightStatusbar(false)
setLightNavigationBar(true) super.setLightNavigationBar(true)
} else if (nowPlayingScreen == Color || nowPlayingScreen == Tiny || nowPlayingScreen == Gradient) { super.setNavigationbarColor(Color.BLACK)
animateNavigationBarColor(paletteColor) } else if (currentNowPlayingScreen == COLOR || currentNowPlayingScreen == TINY) {
navigationBarColor = paletteColor super.setNavigationbarColor(paletteColor)
setLightNavigationBar(isColorLight) super.setLightNavigationBar(isColorLight)
setLightStatusBar(isColorLight) super.setLightStatusbar(isColorLight)
} else if (nowPlayingScreen == Full) { } else if (currentNowPlayingScreen == FIT) {
animateNavigationBarColor(paletteColor) super.setLightStatusbar(false)
navigationBarColor = paletteColor } else {
setLightNavigationBar(isColorLight) super.setLightStatusbar(
setLightStatusBar(false) ColorUtil.isColorLight(
} else if (nowPlayingScreen == Classic) { ATHUtil.resolveColor(
setLightStatusBar(false) this,
} else if (nowPlayingScreen == Fit) { android.R.attr.windowBackground
setLightStatusBar(false) )
)
)
super.setLightNavigationBar(true)
} }
} }
} }
private fun setTaskDescColor(color: Int) { override fun setLightStatusbar(enabled: Boolean) {
lightStatusBar = enabled
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
super.setLightStatusbar(enabled)
}
}
override fun setLightNavigationBar(enabled: Boolean) {
lightNavigationBar = enabled
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
super.setLightNavigationBar(enabled)
}
}
override fun setNavigationbarColor(color: Int) {
navigationBarColor = color
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
if (navigationBarColorAnimator != null) navigationBarColorAnimator!!.cancel()
super.setNavigationbarColor(color)
}
}
override fun setTaskDescriptionColor(color: Int) {
taskColor = color taskColor = color
if (panelState == STATE_COLLAPSED) { if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
setTaskDescriptionColor(color) super.setTaskDescriptionColor(color)
} }
} }
fun updateTabs() { private fun updateTabs() {
binding.navigationView.menu.clear() bottomNavigationView.menu.clear()
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory val currentTabs: List<CategoryInfo> = PreferenceUtil.getInstance(this).libraryCategoryInfos
for (tab in currentTabs) { for (tab in currentTabs) {
if (tab.visible) { if (tab.visible) {
val menu = tab.category val menu = tab.category
binding.navigationView.menu.add(0, menu.id, 0, menu.stringRes) bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon)
.setIcon(menu.icon)
} }
} }
if (binding.navigationView.menu.size() == 1) { if (currentTabs.size <= 1) {
isInOneTabMode = true toggleBottomNavigationView(true)
binding.navigationView.isVisible = false
} else {
isInOneTabMode = false
} }
} }
private fun updateColor() { /*override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
libraryViewModel.paletteColor.observe(this) { color -> if (ev?.action == MotionEvent.ACTION_DOWN) {
this.paletteColor = color if (panelState == BottomSheetBehavior.STATE_EXPANDED) {
onPaletteColorChanged() val outRect = Rect()
} slidingPanel.getGlobalVisibleRect(outRect)
} if (!outRect.contains(ev.rawX.toInt(), ev.rawY.toInt())) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
fun setBottomNavVisibility(
visible: Boolean,
animate: Boolean = false,
hideBottomSheet: Boolean = MusicPlayerRemote.playingQueue.isEmpty(),
) {
if (!ViewCompat.isLaidOut(navigationView)) {
return
}
if (isInOneTabMode) {
hideBottomSheet(
hide = hideBottomSheet,
animate = animate,
isBottomNavVisible = false
)
return
}
if (visible xor navigationView.isVisible) {
val mAnimate = animate && bottomSheetBehavior.state == STATE_COLLAPSED
if (mAnimate) {
if (visible) {
binding.navigationView.bringToFront()
binding.navigationView.show()
} else {
binding.navigationView.hide()
}
} else {
binding.navigationView.isVisible = visible
if (visible && bottomSheetBehavior.state != STATE_EXPANDED) {
binding.navigationView.bringToFront()
} }
} }
} }
hideBottomSheet( return super.dispatchTouchEvent(ev)
hide = hideBottomSheet, }*/
animate = animate,
isBottomNavVisible = visible && navigationView is BottomNavigationView
)
}
fun hideBottomSheet(
hide: Boolean,
animate: Boolean = false,
isBottomNavVisible: Boolean = navigationView.isVisible && navigationView is BottomNavigationView,
) {
val heightOfBar = windowInsets.getBottomInsets() + dip(R.dimen.mini_player_height)
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
if (hide) {
bottomSheetBehavior.peekHeight = -windowInsets.getBottomInsets()
bottomSheetBehavior.state = STATE_COLLAPSED
libraryViewModel.setFabMargin(
this,
if (isBottomNavVisible) dip(R.dimen.bottom_nav_height) else 0
)
} else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
binding.slidingPanel.elevation = 0F
binding.navigationView.elevation = 5F
if (isBottomNavVisible) {
logD("List")
if (animate) {
bottomSheetBehavior.peekHeightAnimate(heightOfBarWithTabs)
} else {
bottomSheetBehavior.peekHeight = heightOfBarWithTabs
}
libraryViewModel.setFabMargin(
this,
dip(R.dimen.bottom_nav_mini_player_height)
)
} else {
logD("Details")
if (animate) {
bottomSheetBehavior.peekHeightAnimate(heightOfBar).doOnEnd {
binding.slidingPanel.bringToFront()
}
} else {
bottomSheetBehavior.peekHeight = heightOfBar
binding.slidingPanel.bringToFront()
}
libraryViewModel.setFabMargin(this, dip(R.dimen.mini_player_height))
}
}
}
}
fun setAllowDragging(allowDragging: Boolean) {
bottomSheetBehavior.isDraggable = allowDragging
hideBottomSheet(false)
}
private fun chooseFragmentForTheme() {
nowPlayingScreen = PreferenceUtil.nowPlayingScreen
val fragment: AbsPlayerFragment = when (nowPlayingScreen) {
Blur -> BlurPlayerFragment()
Adaptive -> AdaptiveFragment()
Normal -> PlayerFragment()
Card -> CardFragment()
BlurCard -> CardBlurFragment()
Fit -> FitFragment()
Flat -> FlatPlayerFragment()
Full -> FullPlayerFragment()
Plain -> PlainPlayerFragment()
Simple -> SimplePlayerFragment()
Material -> MaterialFragment()
Color -> ColorFragment()
Gradient -> GradientPlayerFragment()
Tiny -> TinyPlayerFragment()
Peek -> PeekPlayerFragment()
Circle -> CirclePlayerFragment()
Classic -> ClassicPlayerFragment()
MD3 -> MD3PlayerFragment()
else -> PlayerFragment()
} // must extend AbsPlayerFragment
supportFragmentManager.commit {
replace(R.id.playerFragmentContainer, fragment)
}
supportFragmentManager.executePendingTransactions()
playerFragment = whichFragment(R.id.playerFragmentContainer)
miniPlayerFragment = whichFragment<MiniPlayerFragment>(R.id.miniPlayerFragment)
miniPlayerFragment?.view?.setOnClickListener { expandPanel() }
}
} }

View file

@ -1,69 +1,54 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.base package code.name.monkey.retromusic.activities.base
import android.content.Context import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper
import android.view.KeyEvent import android.view.KeyEvent
import androidx.appcompat.app.AppCompatDelegate import android.view.View
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.core.os.LocaleListCompat import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.ATH
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.LanguageContextWrapper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.*
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.theme.getNightMode import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.theme.getThemeResValue import code.name.monkey.retromusic.util.theme.ThemeManager
import java.util.*
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable { abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
updateLocale()
updateTheme() updateTheme()
hideStatusBar() hideStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setEdgeToEdgeOrImmersive() setImmersiveFullscreen()
maybeSetScreenOn() registerSystemUiVisibility()
setLightNavigationBarAuto() toggleScreenOn()
setLightStatusBarAuto(surfaceColor())
if (VersionUtils.hasQ()) {
window.decorView.isForceDarkAllowed = false
}
} }
private fun updateTheme() { private fun updateTheme() {
setTheme(getThemeResValue()) setTheme(ThemeManager.getThemeResValue(this))
if (PreferenceUtil.materialYou) { setDefaultNightMode(ThemeManager.getNightMode(this))
setDefaultNightMode(getNightMode())
} }
if (PreferenceUtil.isCustomFont) { private fun toggleScreenOn() {
setTheme(R.style.FontThemeOverlay) if (PreferenceUtil.getInstance(this).isScreenOnEnabled) {
} window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} } else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
private fun updateLocale() {
val localeCode = PreferenceUtil.languageCode
if (PreferenceUtil.isLocaleAutoStorageEnabled) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(localeCode))
PreferenceUtil.isLocaleAutoStorageEnabled = true
} }
} }
@ -78,6 +63,131 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
} }
} }
fun hideStatusBar() {
hideStatusBar(PreferenceUtil.getInstance(this).fullScreenMode)
}
private fun hideStatusBar(fullscreen: Boolean) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE
}
}
private fun changeBackgroundShape() {
var background: Drawable? = if (PreferenceUtil.getInstance(this).isRoundCorners)
ContextCompat.getDrawable(this, R.drawable.round_window)
else ContextCompat.getDrawable(this, R.drawable.square_window)
background =
TintHelper.createTintedDrawable(
background,
ATHUtil.resolveColor(this, android.R.attr.windowBackground)
)
window.setBackgroundDrawable(background)
}
fun setDrawUnderStatusBar() {
RetroUtil.setAllowDrawUnderStatusBar(window)
}
fun setDrawUnderNavigationBar() {
RetroUtil.setAllowDrawUnderNavigationBar(window)
}
/**
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop. On
* Lollipop if no such view is found it will set the statusbar color using the native method.
*
* @param color the new statusbar color (will be shifted down on Lollipop and above)
*/
fun setStatusbarColor(color: Int) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
when {
VersionUtils.hasMarshmallow() -> statusBar.setBackgroundColor(color)
VersionUtils.hasLollipop() -> statusBar.setBackgroundColor(
ColorUtil.darkenColor(
color
)
)
else -> statusBar.setBackgroundColor(color)
}
} else {
when {
VersionUtils.hasMarshmallow() -> window.statusBarColor = color
else -> window.statusBarColor = ColorUtil.darkenColor(color)
}
}
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
fun setStatusbarColorAuto() {
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
setStatusbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setTaskDescriptionColor(@ColorInt color: Int) {
ATH.setTaskDescriptionColor(this, color)
}
fun setTaskDescriptionColorAuto() {
setTaskDescriptionColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setNavigationbarColor(color: Int) {
if (ThemeStore.coloredNavigationBar(this)) {
ATH.setNavigationbarColor(this, color)
} else {
ATH.setNavigationbarColor(this, Color.BLACK)
}
}
fun setNavigationbarColorAuto() {
setNavigationbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setLightStatusbar(enabled: Boolean) {
ATH.setLightStatusbar(this, enabled)
}
fun setLightStatusbarAuto(bgColor: Int) {
setLightStatusbar(ColorUtil.isColorLight(bgColor))
}
open fun setLightNavigationBar(enabled: Boolean) {
if (!ATHUtil.isWindowBackgroundDark(this) and ThemeStore.coloredNavigationBar(this)) {
ATH.setLightNavigationbar(this, enabled)
}
}
private fun registerSystemUiVisibility() {
val decorView = window.decorView
decorView.setOnSystemUiVisibilityChangeListener { visibility ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
setImmersiveFullscreen()
}
}
}
private fun unregisterSystemUiVisibility() {
val decorView = window.decorView
decorView.setOnSystemUiVisibilityChangeListener(null)
}
private fun setImmersiveFullscreen() {
val flags =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
if (PreferenceUtil.getInstance(this).fullScreenMode) {
window.decorView.systemUiVisibility = flags
}
}
private fun exitFullscreen() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
override fun run() { override fun run() {
setImmersiveFullscreen() setImmersiveFullscreen()
} }
@ -89,6 +199,7 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
public override fun onDestroy() { public override fun onDestroy() {
super.onDestroy() super.onDestroy()
unregisterSystemUiVisibility()
exitFullscreen() exitFullscreen()
} }
@ -101,6 +212,9 @@ abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
} }
override fun attachBaseContext(newBase: Context?) { override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase) val code = PreferenceUtil.getInstance(newBase).languageCode
if (code != "auto") {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code)))
} else super.attachBaseContext(newBase)
} }
} }

View file

@ -1,91 +1,331 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.bugreport package code.name.monkey.retromusic.activities.bugreport
import android.app.Activity
import android.app.Dialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
import androidx.core.content.getSystemService import android.view.inputmethod.EditorInfo
import androidx.core.net.toUri import android.widget.Toast
import androidx.annotation.StringDef
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsThemeActivity import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo
import code.name.monkey.retromusic.databinding.ActivityBugReportBinding import code.name.monkey.retromusic.activities.bugreport.model.Report
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin
import code.name.monkey.retromusic.extensions.showToast import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget
import code.name.monkey.retromusic.misc.DialogAsyncTask
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.callbacks.onCancel
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.activity_bug_report.*
import kotlinx.android.synthetic.main.bug_report_card_device_info.*
import kotlinx.android.synthetic.main.bug_report_card_report.*
import org.eclipse.egit.github.core.Issue
import org.eclipse.egit.github.core.client.GitHubClient
import org.eclipse.egit.github.core.client.RequestException
import org.eclipse.egit.github.core.service.IssueService
import java.io.IOException
private const val RESULT_SUCCESS = "RESULT_OK"
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
private const val RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN"
private const val RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED"
private const val RESULT_UNKNOWN = "RESULT_UNKNOWN"
@StringDef(
RESULT_SUCCESS,
RESULT_BAD_CREDENTIALS,
RESULT_INVALID_TOKEN,
RESULT_ISSUES_NOT_ENABLED,
RESULT_UNKNOWN
)
@Retention(AnnotationRetention.SOURCE)
private annotation class Result
open class BugReportActivity : AbsThemeActivity() { open class BugReportActivity : AbsThemeActivity() {
private lateinit var binding: ActivityBugReportBinding
private var deviceInfo: DeviceInfo? = null private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityBugReportBinding.inflate(layoutInflater) setContentView(R.layout.activity_bug_report)
setContentView(binding.root) setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
initViews() initViews()
if (title.isNullOrEmpty()) setTitle(R.string.report_an_issue) if (TextUtils.isEmpty(title)) setTitle(R.string.report_an_issue)
deviceInfo = DeviceInfo(this) deviceInfo = DeviceInfo(this)
binding.cardDeviceInfo.airTextDeviceInfo.text = deviceInfo.toString() airTextDeviceInfo.text = deviceInfo.toString()
} }
private fun initViews() { private fun initViews() {
val accentColor = accentColor() val accentColor = ThemeStore.accentColor(this)
setSupportActionBar(binding.toolbar) val primaryColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
ToolbarContentTintHelper.colorBackButton(binding.toolbar) toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
TintHelper.setTintAuto(optionUseAccount, accentColor, false)
optionUseAccount?.setOnClickListener {
inputTitle.isEnabled = true
inputDescription.isEnabled = true
inputUsername.isEnabled = true
inputPassword.isEnabled = true
binding.cardDeviceInfo.airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() } optionAnonymous.isChecked = false
sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_send_white_24dp)
sendFab.show()
}
})
}
TintHelper.setTintAuto(optionAnonymous, accentColor, false)
optionAnonymous.setOnClickListener {
inputTitle.isEnabled = false
inputDescription.isEnabled = false
inputUsername.isEnabled = false
inputPassword.isEnabled = false
TintHelper.setTintAuto(binding.sendFab, accentColor, true) optionUseAccount.isChecked = false
binding.sendFab.setOnClickListener { reportIssue() } sendFab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab.setImageResource(R.drawable.ic_open_in_browser_white_24dp)
sendFab.show()
}
})
}
inputPassword.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEND) {
reportIssue()
return@setOnEditorActionListener true
}
false
}
airTextDeviceInfo.setOnClickListener { copyDeviceInfoToClipBoard() }
TintHelper.setTintAuto(sendFab, accentColor, true)
sendFab.setOnClickListener { reportIssue() }
MaterialUtil.setTint(inputLayoutTitle, false)
MaterialUtil.setTint(inputLayoutDescription, false)
MaterialUtil.setTint(inputLayoutUsername, false)
MaterialUtil.setTint(inputLayoutPassword, false)
} }
private fun reportIssue() { private fun reportIssue() {
if (optionUseAccount.isChecked) {
if (!validateInput()) return
val username = inputUsername.text.toString()
val password = inputPassword.text.toString()
sendBugReport(GithubLogin(username, password))
} else {
copyDeviceInfoToClipBoard() copyDeviceInfoToClipBoard()
val i = Intent(Intent.ACTION_VIEW) val i = Intent(Intent.ACTION_VIEW)
i.data = ISSUE_TRACKER_LINK.toUri() i.data = Uri.parse(ISSUE_TRACKER_LINK)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i) startActivity(i)
} }
}
private fun copyDeviceInfoToClipBoard() { private fun copyDeviceInfoToClipBoard() {
val clipboard = getSystemService<ClipboardManager>() val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown()) val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo?.toMarkdown())
clipboard?.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
showToast(R.string.copied_device_info_to_clipboard) Toast.makeText(
this@BugReportActivity,
R.string.copied_device_info_to_clipboard,
Toast.LENGTH_LONG
).show()
} }
private fun validateInput(): Boolean {
var hasErrors = false
if (optionUseAccount.isChecked) {
if (TextUtils.isEmpty(inputUsername.text)) {
setError(inputLayoutUsername, R.string.bug_report_no_username)
hasErrors = true
} else {
removeError(inputLayoutUsername)
}
if (TextUtils.isEmpty(inputPassword.text)) {
setError(inputLayoutPassword, R.string.bug_report_no_password)
hasErrors = true
} else {
removeError(inputLayoutPassword)
}
}
if (TextUtils.isEmpty(inputTitle.text)) {
setError(inputLayoutTitle, R.string.bug_report_no_title)
hasErrors = true
} else {
removeError(inputLayoutTitle)
}
if (TextUtils.isEmpty(inputDescription.text)) {
setError(inputLayoutDescription, R.string.bug_report_no_description)
hasErrors = true
} else {
removeError(inputLayoutDescription)
}
return !hasErrors
}
private fun setError(editTextLayout: TextInputLayout, @StringRes errorRes: Int) {
editTextLayout.error = getString(errorRes)
}
private fun removeError(editTextLayout: TextInputLayout) {
editTextLayout.error = null
}
private fun sendBugReport(login: GithubLogin) {
if (!validateInput()) return
val bugTitle = inputTitle.text.toString()
val bugDescription = inputDescription.text.toString()
val extraInfo = ExtraInfo()
onSaveExtraInfo()
val report = Report(bugTitle, bugDescription, deviceInfo, extraInfo)
val target = GithubTarget("h4h13", "RetroMusicPlayer")
ReportIssueAsyncTask.report(this, report, target, login)
}
private fun onSaveExtraInfo() {}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed() onBackPressed()
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private class ReportIssueAsyncTask private constructor(
activity: Activity,
private val report: Report,
private val target: GithubTarget,
private val login: GithubLogin
) : DialogAsyncTask<Void, Void, String>(activity) {
override fun createDialog(context: Context): Dialog {
return AlertDialog.Builder(context).show()
}
@Result
override fun doInBackground(vararg params: Void): String {
val client: GitHubClient = if (login.shouldUseApiToken()) {
GitHubClient().setOAuth2Token(login.apiToken)
} else {
GitHubClient().setCredentials(login.username, login.password)
}
val issue = Issue().setTitle(report.title).setBody(report.description)
try {
IssueService(client).createIssue(target.username, target.repository, issue)
return RESULT_SUCCESS
} catch (e: RequestException) {
return when (e.status) {
STATUS_BAD_CREDENTIALS -> {
if (login.shouldUseApiToken()) RESULT_INVALID_TOKEN else RESULT_BAD_CREDENTIALS
}
STATUS_ISSUES_NOT_ENABLED -> RESULT_ISSUES_NOT_ENABLED
else -> {
e.printStackTrace()
RESULT_UNKNOWN
}
}
} catch (e: IOException) {
e.printStackTrace()
return RESULT_UNKNOWN
}
}
override fun onPostExecute(@Result result: String) {
super.onPostExecute(result)
val context = context ?: return
when (result) {
RESULT_SUCCESS -> tryToFinishActivity()
RESULT_BAD_CREDENTIALS -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_wrong_credentials)
positiveButton(android.R.string.ok)
}
RESULT_INVALID_TOKEN -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_invalid_token)
positiveButton(android.R.string.ok)
}
RESULT_ISSUES_NOT_ENABLED -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_issues_not_available)
positiveButton(android.R.string.ok)
}
else -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_unknown)
positiveButton(android.R.string.ok) { tryToFinishActivity() }
onCancel { tryToFinishActivity() }
}
}
}
private fun tryToFinishActivity() {
val context = context
if (context is Activity && !context.isFinishing) {
context.finish()
}
}
companion object { companion object {
private const val ISSUE_TRACKER_LINK =
"https://github.com/MuntashirAkon/Metro/issues/new" fun report(
activity: Activity,
report: Report,
target: GithubTarget,
login: GithubLogin
) {
ReportIssueAsyncTask(activity, report, target, login).execute()
}
}
}
companion object {
private const val STATUS_BAD_CREDENTIALS = 401
private const val STATUS_ISSUES_NOT_ENABLED = 410
private const val ISSUE_TRACKER_LINK = "https://github.com/h4h13/RetroMusicPlayer"
} }
} }

View file

@ -0,0 +1,130 @@
package code.name.monkey.retromusic.activities.bugreport.model;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.IntRange;
import java.util.Arrays;
import java.util.Locale;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class DeviceInfo {
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private final String[] abis = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_ABIS : new String[]{Build.CPU_ABI, Build.CPU_ABI2};
@SuppressLint("NewApi")
private final String[] abis32Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_32_BIT_ABIS : null;
@SuppressLint("NewApi")
private final String[] abis64Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_64_BIT_ABIS : null;
private final String baseTheme;
private final String brand = Build.BRAND;
private final String buildID = Build.DISPLAY;
private final String buildVersion = Build.VERSION.INCREMENTAL;
private final String device = Build.DEVICE;
private final String hardware = Build.HARDWARE;
private final boolean isAdaptive;
private final String manufacturer = Build.MANUFACTURER;
private final String model = Build.MODEL;
private final String nowPlayingTheme;
private final String product = Build.PRODUCT;
private final String releaseVersion = Build.VERSION.RELEASE;
@IntRange(from = 0)
private final int sdkVersion = Build.VERSION.SDK_INT;
private final int versionCode;
private final String versionName;
private final String selectedLang;
public DeviceInfo(Context context) {
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
packageInfo = null;
}
if (packageInfo != null) {
versionCode = packageInfo.versionCode;
versionName = packageInfo.versionName;
} else {
versionCode = -1;
versionName = null;
}
baseTheme = PreferenceUtil.getInstance(context).getBaseTheme();
nowPlayingTheme = context.getString(PreferenceUtil.getInstance(context).getNowPlayingScreen().getTitleRes());
isAdaptive = PreferenceUtil.getInstance(context).getAdaptiveColor();
selectedLang = PreferenceUtil.getInstance(context).getLanguageCode();
}
public String toMarkdown() {
return "Device info:\n"
+ "---\n"
+ "<table>\n"
+ "<tr><td>App version</td><td>" + versionName + "</td></tr>\n"
+ "<tr><td>App version code</td><td>" + versionCode + "</td></tr>\n"
+ "<tr><td>Android build version</td><td>" + buildVersion + "</td></tr>\n"
+ "<tr><td>Android release version</td><td>" + releaseVersion + "</td></tr>\n"
+ "<tr><td>Android SDK version</td><td>" + sdkVersion + "</td></tr>\n"
+ "<tr><td>Android build ID</td><td>" + buildID + "</td></tr>\n"
+ "<tr><td>Device brand</td><td>" + brand + "</td></tr>\n"
+ "<tr><td>Device manufacturer</td><td>" + manufacturer + "</td></tr>\n"
+ "<tr><td>Device name</td><td>" + device + "</td></tr>\n"
+ "<tr><td>Device model</td><td>" + model + "</td></tr>\n"
+ "<tr><td>Device product name</td><td>" + product + "</td></tr>\n"
+ "<tr><td>Device hardware name</td><td>" + hardware + "</td></tr>\n"
+ "<tr><td>ABIs</td><td>" + Arrays.toString(abis) + "</td></tr>\n"
+ "<tr><td>ABIs (32bit)</td><td>" + Arrays.toString(abis32Bits) + "</td></tr>\n"
+ "<tr><td>ABIs (64bit)</td><td>" + Arrays.toString(abis64Bits) + "</td></tr>\n"
+ "<tr><td>Language</td><td>" + selectedLang + "</td></tr>\n"
+ "</table>\n";
}
@Override
public String toString() {
return "App version: " + versionName + "\n"
+ "App version code: " + versionCode + "\n"
+ "Android build version: " + buildVersion + "\n"
+ "Android release version: " + releaseVersion + "\n"
+ "Android SDK version: " + sdkVersion + "\n"
+ "Android build ID: " + buildID + "\n"
+ "Device brand: " + brand + "\n"
+ "Device manufacturer: " + manufacturer + "\n"
+ "Device name: " + device + "\n"
+ "Device model: " + model + "\n"
+ "Device product name: " + product + "\n"
+ "Device hardware name: " + hardware + "\n"
+ "ABIs: " + Arrays.toString(abis) + "\n"
+ "ABIs (32bit): " + Arrays.toString(abis32Bits) + "\n"
+ "ABIs (64bit): " + Arrays.toString(abis64Bits) + "\n"
+ "Base theme: " + baseTheme + "\n"
+ "Now playing theme: " + nowPlayingTheme + "\n"
+ "Adaptive: " + isAdaptive + "\n"
+ "System language: " + Locale.getDefault().toLanguageTag() + "\n"
+ "In-App Language: " + selectedLang;
}
}

View file

@ -1,111 +0,0 @@
package code.name.monkey.retromusic.activities.bugreport.model
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.IntRange
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.pm.PackageInfoCompat
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.PreferenceUtil.isAdaptiveColor
import code.name.monkey.retromusic.util.PreferenceUtil.nowPlayingScreen
import java.util.*
class DeviceInfo(context: Context) {
@SuppressLint("NewApi")
private val abis = Build.SUPPORTED_ABIS
@SuppressLint("NewApi")
private val abis32Bits = Build.SUPPORTED_32_BIT_ABIS
@SuppressLint("NewApi")
private val abis64Bits = Build.SUPPORTED_64_BIT_ABIS
private val baseTheme: String
private val brand = Build.BRAND
private val buildID = Build.DISPLAY
private val buildVersion = Build.VERSION.INCREMENTAL
private val device = Build.DEVICE
private val hardware = Build.HARDWARE
private val isAdaptive: Boolean
private val manufacturer = Build.MANUFACTURER
private val model = Build.MODEL
private val nowPlayingTheme: String
private val product = Build.PRODUCT
private val releaseVersion = Build.VERSION.RELEASE
@IntRange(from = 0)
private val sdkVersion = Build.VERSION.SDK_INT
private var versionCode = 0L
private var versionName: String? = null
private val selectedLang: String
fun toMarkdown(): String {
return """
Device info:
---
<table>
<tr><td><b>App version</b></td><td>$versionName</td></tr>
<tr><td>App version code</td><td>$versionCode</td></tr>
<tr><td>Android build version</td><td>$buildVersion</td></tr>
<tr><td>Android release version</td><td>$releaseVersion</td></tr>
<tr><td>Android SDK version</td><td>$sdkVersion</td></tr>
<tr><td>Android build ID</td><td>$buildID</td></tr>
<tr><td>Device brand</td><td>$brand</td></tr>
<tr><td>Device manufacturer</td><td>$manufacturer</td></tr>
<tr><td>Device name</td><td>$device</td></tr>
<tr><td>Device model</td><td>$model</td></tr>
<tr><td>Device product name</td><td>$product</td></tr>
<tr><td>Device hardware name</td><td>$hardware</td></tr>
<tr><td>ABIs</td><td>${Arrays.toString(abis)}</td></tr>
<tr><td>ABIs (32bit)</td><td>${Arrays.toString(abis32Bits)}</td></tr>
<tr><td>ABIs (64bit)</td><td>${Arrays.toString(abis64Bits)}</td></tr>
<tr><td>Language</td><td>$selectedLang</td></tr>
</table>
""".trimIndent()
}
override fun toString(): String {
return """
App version: $versionName
App version code: $versionCode
Android build version: $buildVersion
Android release version: $releaseVersion
Android SDK version: $sdkVersion
Android build ID: $buildID
Device brand: $brand
Device manufacturer: $manufacturer
Device name: $device
Device model: $model
Device product name: $product
Device hardware name: $hardware
ABIs: ${Arrays.toString(abis)}
ABIs (32bit): ${Arrays.toString(abis32Bits)}
ABIs (64bit): ${Arrays.toString(abis64Bits)}
Base theme: $baseTheme
Now playing theme: $nowPlayingTheme
Adaptive: $isAdaptive
System language: ${Locale.getDefault().toLanguageTag()}
In-App Language: $selectedLang
""".trimIndent()
}
init {
val packageInfo = try {
context.packageManager.getPackageInfo(context.packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
null
}
if (packageInfo != null) {
versionCode = PackageInfoCompat.getLongVersionCode(packageInfo)
versionName = packageInfo.versionName
} else {
versionCode = -1
versionName = null
}
baseTheme = PreferenceUtil.baseTheme
nowPlayingTheme = context.getString(nowPlayingScreen.titleRes)
isAdaptive = isAdaptiveColor
selectedLang = AppCompatDelegate.getApplicationLocales().toLanguageTags()
}
}

View file

@ -0,0 +1,33 @@
package code.name.monkey.retromusic.activities.bugreport.model;
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo;
public class Report {
private final String description;
private final DeviceInfo deviceInfo;
private final ExtraInfo extraInfo;
private final String title;
public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) {
this.title = title;
this.description = description;
this.deviceInfo = deviceInfo;
this.extraInfo = extraInfo;
}
public String getDescription() {
return description + "\n\n"
+ "-\n\n"
+ deviceInfo.toMarkdown() + "\n\n"
+ extraInfo.toMarkdown();
}
public String getTitle() {
return title;
}
}

View file

@ -0,0 +1,62 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
import java.util.LinkedHashMap;
import java.util.Map;
public class ExtraInfo {
private final Map<String, String> extraInfo = new LinkedHashMap<>();
public void put(String key, String value) {
extraInfo.put(key, value);
}
public void put(String key, boolean value) {
extraInfo.put(key, Boolean.toString(value));
}
public void put(String key, double value) {
extraInfo.put(key, Double.toString(value));
}
public void put(String key, float value) {
extraInfo.put(key, Float.toString(value));
}
public void put(String key, long value) {
extraInfo.put(key, Long.toString(value));
}
public void put(String key, int value) {
extraInfo.put(key, Integer.toString(value));
}
public void put(String key, Object value) {
extraInfo.put(key, String.valueOf(value));
}
public void remove(String key) {
extraInfo.remove(key);
}
public String toMarkdown() {
if (extraInfo.isEmpty()) {
return "";
}
StringBuilder output = new StringBuilder();
output.append("Extra info:\n"
+ "---\n"
+ "<table>\n");
for (String key : extraInfo.keySet()) {
output.append("<tr><td>")
.append(key)
.append("</td><td>")
.append(extraInfo.get(key))
.append("</td></tr>\n");
}
output.append("</table>\n");
return output.toString();
}
}

View file

@ -0,0 +1,41 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
import android.text.TextUtils;
public class GithubLogin {
private final String apiToken;
private final String password;
private final String username;
public GithubLogin(String username, String password) {
this.username = username;
this.password = password;
this.apiToken = null;
}
public GithubLogin(String apiToken) {
this.username = null;
this.password = null;
this.apiToken = apiToken;
}
public String getApiToken() {
return apiToken;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean shouldUseApiToken() {
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
}
}

View file

@ -0,0 +1,21 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
public class GithubTarget {
private final String repository;
private final String username;
public GithubTarget(String username, String repository) {
this.username = username;
this.repository = repository;
}
public String getRepository() {
return repository;
}
public String getUsername() {
return username;
}
}

View file

@ -24,7 +24,9 @@ import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
/** Created by hemanths on 2019-07-31. */ /**
* Created by hemanths on 2019-07-31.
*/
public class SAFGuideActivity extends IntroActivity { public class SAFGuideActivity extends IntroActivity {
public static final int REQUEST_CODE_SAF_GUIDE = 98; public static final int REQUEST_CODE_SAF_GUIDE = 98;
@ -39,37 +41,31 @@ public class SAFGuideActivity extends IntroActivity {
setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT); setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT);
String title = String title = String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
addSlide( addSlide(new SimpleSlide.Builder()
new SimpleSlide.Builder()
.title(title) .title(title)
.description( .description(Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ? R.string.saf_guide_slide1_description_before_o : R.string.saf_guide_slide1_description)
? R.string.saf_guide_slide1_description_before_o
: R.string.saf_guide_slide1_description)
.image(R.drawable.saf_guide_1) .image(R.drawable.saf_guide_1)
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_300) .background(R.color.md_deep_purple_300)
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_400) .backgroundDark(R.color.md_deep_purple_400)
.layout(R.layout.fragment_simple_slide_large_image) .layout(R.layout.fragment_simple_slide_large_image)
.build()); .build());
addSlide( addSlide(new SimpleSlide.Builder()
new SimpleSlide.Builder()
.title(R.string.saf_guide_slide2_title) .title(R.string.saf_guide_slide2_title)
.description(R.string.saf_guide_slide2_description) .description(R.string.saf_guide_slide2_description)
.image(R.drawable.saf_guide_2) .image(R.drawable.saf_guide_2)
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_500) .background(R.color.md_deep_purple_500)
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_600) .backgroundDark(R.color.md_deep_purple_600)
.layout(R.layout.fragment_simple_slide_large_image) .layout(R.layout.fragment_simple_slide_large_image)
.build()); .build());
addSlide( addSlide(new SimpleSlide.Builder()
new SimpleSlide.Builder()
.title(R.string.saf_guide_slide3_title) .title(R.string.saf_guide_slide3_title)
.description(R.string.saf_guide_slide3_description) .description(R.string.saf_guide_slide3_description)
.image(R.drawable.saf_guide_3) .image(R.drawable.saf_guide_3)
.background(code.name.monkey.appthemehelper.R.color.md_deep_purple_700) .background(R.color.md_deep_purple_700)
.backgroundDark(code.name.monkey.appthemehelper.R.color.md_deep_purple_800) .backgroundDark(R.color.md_deep_purple_800)
.layout(R.layout.fragment_simple_slide_large_image) .layout(R.layout.fragment_simple_slide_large_image)
.build()); .build());
} }

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2021 Bartlomiej Uliasz.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities.saf
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
import code.name.monkey.retromusic.util.SAFUtil
/** Created by buliasz on 2021-02-07. */
class SAFRequestActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, code.name.monkey.retromusic.activities.saf.SAFGuideActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_SAF_GUIDE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
when (requestCode) {
REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE -> {
if (resultCode == RESULT_OK) {
SAFUtil.saveTreeUri(this, intent)
}
finish()
}
}
}
}

View file

@ -1,105 +1,75 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.tageditor package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity import android.app.Activity
import android.app.SearchManager import android.app.SearchManager
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.widget.ImageView import code.name.monkey.appthemehelper.ThemeStore
import androidx.activity.result.ActivityResultLauncher import code.name.monkey.appthemehelper.util.ATHUtil
import androidx.activity.result.IntentSenderRequest import code.name.monkey.appthemehelper.util.ColorUtil
import androidx.activity.result.PickVisualMediaRequest import code.name.monkey.appthemehelper.util.MaterialValueHelper
import androidx.activity.result.contract.ActivityResultContracts import code.name.monkey.appthemehelper.util.TintHelper
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.drawable
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.extensions.hideSoftKeyboard import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.extensions.setTaskDescriptionColorAuto import code.name.monkey.retromusic.util.SAFUtil
import code.name.monkey.retromusic.model.ArtworkInfo import com.afollestad.materialdialogs.LayoutMode
import code.name.monkey.retromusic.model.AudioTagInfo import com.afollestad.materialdialogs.MaterialDialog
import code.name.monkey.retromusic.repository.Repository import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import code.name.monkey.retromusic.util.logD import com.afollestad.materialdialogs.list.listItems
import code.name.monkey.retromusic.util.logE
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import java.util.*
abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() { abstract class AbsTagEditorActivity : AbsBaseActivity() {
abstract val editorImage: ImageView
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton protected var id: Int = 0
protected var id: Long = 0
private set private set
private var paletteColorPrimary: Int = 0 private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null private var songPaths: List<String>? = null
lateinit var saveFab: MaterialButton
private var savedSongPaths: List<String>? = null private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null private var savedTags: Map<FieldKey, String>? = null
private var savedArtworkInfo: ArtworkInfo? = null private var savedArtworkInfo: ArtworkInfo? = null
private var _binding: VB? = null
protected val binding: VB get() = _binding!!
private var cacheFiles = listOf<File>()
abstract val bindingInflater: (LayoutInflater) -> VB protected val show: MaterialDialog
get() = MaterialDialog(this, BottomSheet(LayoutMode.WRAP_CONTENT)).show {
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest> cornerRadius(PreferenceUtil.getInstance(this@AbsTagEditorActivity).dialogCorner)
title(R.string.update_image)
protected abstract fun loadImageFromFile(selectedFile: Uri?) listItems(items = items) { _, position, _ ->
protected val show: AlertDialog
get() =
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_image)
.setItems(items.toTypedArray()) { _, position ->
when (position) { when (position) {
0 -> startImagePicker() 0 -> startImagePicker()
1 -> searchImageOnWeb() 1 -> searchImageOnWeb()
2 -> deleteImage() 2 -> deleteImage()
} }
} }
.setNegativeButton(R.string.action_cancel, null) }
.show() protected abstract val contentViewLayout: Int
.colorButtons()
internal val albumArtist: String? internal val albumArtist: String?
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -108,8 +78,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -117,8 +86,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -127,8 +95,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -137,8 +104,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -147,8 +113,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM_ARTIST)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -157,8 +122,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -167,8 +131,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -177,18 +140,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null
}
}
protected val discNumber: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.DISC_NO)
} catch (e: Exception) {
logE(e)
null null
} }
} }
@ -197,8 +149,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
get() { get() {
return try { return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS) getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
null null
} }
} }
@ -216,58 +167,62 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
) )
} }
return null return null
} catch (e: Exception) { } catch (ignored: Exception) {
logE(e)
return null return null
} }
} }
private val pickArtworkImage =
registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
loadImageFromFile(uri)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater) setContentView(contentViewLayout)
setContentView(binding.root)
setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
getIntentExtras() getIntentExtras()
songPaths = getSongPaths() songPaths = getSongPaths()
logD(songPaths?.size)
if (songPaths!!.isEmpty()) { if (songPaths!!.isEmpty()) {
finish() finish()
return
} }
setUpViews() setUpViews()
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) { setStatusbarColorAuto()
writeToFiles(getSongUris(), cacheFiles) setNavigationbarColorAuto()
} setTaskDescriptionColorAuto()
}
} }
private fun setUpViews() { private fun setUpViews() {
setUpScrollView()
setUpFab() setUpFab()
setUpImageView() setUpImageView()
} }
private fun setUpScrollView() {
//observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
}
private lateinit var items: List<String> private lateinit var items: List<String>
private fun setUpImageView() { private fun setUpImageView() {
loadCurrentImage() loadCurrentImage()
items = listOf( items = listOf(
getString(R.string.pick_from_local_storage), getString(code.name.monkey.retromusic.R.string.pick_from_local_storage),
getString(R.string.web_search), getString(code.name.monkey.retromusic.R.string.web_search),
getString(R.string.remove_cover) getString(code.name.monkey.retromusic.R.string.remove_cover)
) )
editorImage.setOnClickListener { show } editorImage?.setOnClickListener { show }
} }
private fun startImagePicker() { private fun startImagePicker() {
pickArtworkImage.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(
Intent.createChooser(
intent,
getString(code.name.monkey.retromusic.R.string.pick_from_local_storage)
), REQUEST_CODE_SELECT_IMAGE
)
} }
protected abstract fun loadCurrentImage() protected abstract fun loadCurrentImage()
@ -277,12 +232,26 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
protected abstract fun deleteImage() protected abstract fun deleteImage()
private fun setUpFab() { private fun setUpFab() {
saveFab.accentColor() saveFab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(
ThemeStore.accentColor(
this
)
)
)
).apply {
saveFab.setTextColor(this)
saveFab.iconTint = this
}
saveFab.apply { saveFab.apply {
scaleX = 0f scaleX = 0f
scaleY = 0f scaleY = 0f
isEnabled = false isEnabled = false
setOnClickListener { save() } setOnClickListener { save() }
TintHelper.setTintAuto(this, ThemeStore.accentColor(this@AbsTagEditorActivity), true)
} }
} }
@ -291,14 +260,12 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
private fun getIntentExtras() { private fun getIntentExtras() {
val intentExtras = intent.extras val intentExtras = intent.extras
if (intentExtras != null) { if (intentExtras != null) {
id = intentExtras.getLong(EXTRA_ID) id = intentExtras.getInt(EXTRA_ID)
} }
} }
protected abstract fun getSongPaths(): List<String> protected abstract fun getSongPaths(): List<String>
protected abstract fun getSongUris(): List<Uri>
protected fun searchWebFor(vararg keys: String) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
for (key in keys) { for (key in keys) {
@ -315,13 +282,27 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressedDispatcher.onBackPressed() super.onBackPressed()
return true return true
} }
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
protected fun setNoImageMode() {
isInNoImageMode = true
imageContainer?.visibility = View.GONE
editorImage?.visibility = View.GONE
editorImage?.isEnabled = false
setColors(
intent.getIntExtra(
EXTRA_PALETTE,
ATHUtil.resolveColor(this, R.attr.colorPrimary)
)
)
}
protected fun dataChanged() { protected fun dataChanged() {
showFab() showFab()
} }
@ -340,7 +321,7 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) { protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) { if (bitmap == null) {
editorImage.setImageResource(R.drawable.default_audio_art) editorImage.setImageResource(drawable.default_audio_art)
} else { } else {
editorImage.setImageBitmap(bitmap) editorImage.setImageBitmap(bitmap)
} }
@ -352,106 +333,86 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
} }
protected fun writeValuesToFiles( protected fun writeValuesToFiles(
fieldKeyValueMap: Map<FieldKey, String>, fieldKeyValueMap: Map<FieldKey, String>, artworkInfo: ArtworkInfo?
artworkInfo: ArtworkInfo?
) { ) {
hideSoftKeyboard() RetroUtil.hideSoftKeyboard(this)
hideFab() hideFab()
logD(fieldKeyValueMap)
GlobalScope.launch {
if (VersionUtils.hasR()) {
cacheFiles = TagWriter.writeTagsToFilesR(
this@AbsTagEditorActivity, AudioTagInfo(
songPaths,
fieldKeyValueMap,
artworkInfo
)
)
if (cacheFiles.isNotEmpty()) { savedSongPaths = getSongPaths()
val pendingIntent = savedTags = fieldKeyValueMap
MediaStore.createWriteRequest(contentResolver, getSongUris()) savedArtworkInfo = artworkInfo
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} if (!SAFUtil.isSAFRequired(savedSongPaths)) {
writeTags(savedSongPaths)
} else { } else {
TagWriter.writeTagsToFiles( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this@AbsTagEditorActivity, AudioTagInfo( if (SAFUtil.isSDCardAccessGranted(this)) {
songPaths, writeTags(savedSongPaths)
fieldKeyValueMap, } else {
artworkInfo startActivityForResult(
) Intent(this, SAFGuideActivity::class.java),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
) )
} }
} }
} }
}
private fun writeTags(paths: List<String>?) { private fun writeTags(paths: List<String>?) {
GlobalScope.launch { WriteTagsAsyncTask(this).execute(
if (VersionUtils.hasR()) { WriteTagsAsyncTask.LoadingInfo(
cacheFiles = TagWriter.writeTagsToFilesR(
this@AbsTagEditorActivity, AudioTagInfo(
paths,
savedTags,
savedArtworkInfo
)
)
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} else {
TagWriter.writeTagsToFiles(
this@AbsTagEditorActivity, AudioTagInfo(
paths, paths,
savedTags, savedTags,
savedArtworkInfo savedArtworkInfo
) )
) )
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
intent?.data?.let {
loadImageFromFile(it)
}
}
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE -> {
if (resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(this, intent)
writeTags(savedSongPaths)
}
}
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (resultCode == Activity.RESULT_OK) {
writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent!!.dataString))
}
}
} }
} }
private lateinit var audioFile: AudioFile protected abstract fun loadImageFromFile(selectedFile: Uri?)
private fun getAudioFile(path: String): AudioFile { private fun getAudioFile(path: String): AudioFile {
return try { return try {
if (!this::audioFile.isInitialized) { AudioFileIO.read(File(path))
audioFile = AudioFileIO.read(File(path))
}
audioFile
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Could not read audio file $path", e) Log.e(TAG, "Could not read audio file $path", e)
AudioFile() AudioFile()
} }
} }
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) { class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
if (cacheFiles.size == songUris.size) {
for (i in cacheFiles.indices) {
contentResolver.openOutputStream(songUris[i])?.use { output ->
cacheFiles[i].inputStream().use { input ->
input.copyTo(output)
}
}
}
}
lifecycleScope.launch {
TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
}
}
override fun onDestroy() {
super.onDestroy()
// Delete Cache Files
cacheFiles.forEach { file ->
file.delete()
}
}
companion object { companion object {
const val EXTRA_ID = "extra_id" const val EXTRA_ID = "extra_id"
const val EXTRA_PALETTE = "extra_palette" const val EXTRA_PALETTE = "extra_palette"
private val TAG = AbsTagEditorActivity::class.java.simpleName private val TAG = AbsTagEditorActivity::class.java.simpleName
private const val REQUEST_CODE_SELECT_IMAGE = 1000 private const val REQUEST_CODE_SELECT_IMAGE = 1000
} }
} }

View file

@ -1,17 +1,3 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.tageditor package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity import android.app.Activity
@ -22,36 +8,31 @@ import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.transition.Slide import android.transition.Slide
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.core.widget.doAfterTextChanged import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivityAlbumTagEditorBinding import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.loaders.AlbumLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import code.name.monkey.retromusic.util.logD
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.ImageViewTarget import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.target.SimpleTarget
import com.google.android.material.shape.MaterialShapeDrawable import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import java.util.* import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBinding>() { class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int
override val bindingInflater: (LayoutInflater) -> ActivityAlbumTagEditorBinding = get() = R.layout.activity_album_tag_editor
ActivityAlbumTagEditorBinding::inflate
private fun windowEnterTransition() { private fun windowEnterTransition() {
val slide = Slide() val slide = Slide()
@ -63,19 +44,54 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
window.enterTransition = slide window.enterTransition = slide
} }
override fun loadImageFromFile(selectedFileUri: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFileUri).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() {
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
) {
getColor(resource?.palette, Color.TRANSPARENT)
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource?.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
)
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG)
.show()
}
})
}
private var albumArtBitmap: Bitmap? = null private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false private var deleteAlbumArt: Boolean = false
private fun setupToolbar() { private fun setupToolbar() {
setSupportActionBar(binding.toolbar) toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
binding.appBarLayout?.statusBarForeground = setSupportActionBar(toolbar)
MaterialShapeDrawable.createWithElevationOverlay(this)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true window.sharedElementsUseOverlay = true
binding.imageContainer.transitionName = getString(R.string.transition_album_art) imageContainer?.transitionName = "${getString(R.string.transition_album_art)}_$id"
windowEnterTransition() windowEnterTransition()
setUpViews() setUpViews()
setupToolbar() setupToolbar()
@ -84,23 +100,22 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
binding.yearContainer.setTint(false) MaterialUtil.setTint(yearContainer, false)
binding.genreContainer.setTint(false) MaterialUtil.setTint(genreContainer, false)
binding.albumTitleContainer.setTint(false) MaterialUtil.setTint(albumTitleContainer, false)
binding.albumArtistContainer.setTint(false) MaterialUtil.setTint(albumArtistContainer, false)
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() } albumText.appHandleColor().addTextChangedListener(this)
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() } albumArtistText.appHandleColor().addTextChangedListener(this)
binding.genreTitle.appHandleColor().doAfterTextChanged { dataChanged() } genreTitle.appHandleColor().addTextChangedListener(this)
binding.yearTitle.appHandleColor().doAfterTextChanged { dataChanged() } yearTitle.appHandleColor().addTextChangedListener(this)
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
binding.albumText.setText(albumTitle) albumText.setText(albumTitle)
binding.albumArtistText.setText(albumArtistName) albumArtistText.setText(albumArtistName)
binding.genreTitle.setText(genreName) genreTitle.setText(genreName)
binding.yearTitle.setText(songYear) yearTitle.setText(songYear)
logD(albumTitle + albumArtistName)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {
@ -109,108 +124,72 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
bitmap, bitmap,
getColor( getColor(
generatePalette(bitmap), generatePalette(bitmap),
defaultFooterColor() ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
) )
) )
deleteAlbumArt = false deleteAlbumArt = false
} }
private fun toastLoadingFailed() { private fun toastLoadingFailed() {
showToast(R.string.could_not_download_album_cover) Toast.makeText(
this@AlbumTagEditorActivity,
R.string.could_not_download_album_cover,
Toast.LENGTH_SHORT
).show()
} }
override fun searchImageOnWeb() { override fun searchImageOnWeb() {
searchWebFor(binding.albumText.text.toString(), binding.albumArtistText.text.toString()) searchWebFor(albumText.text.toString(), albumArtistText.text.toString())
} }
override fun deleteImage() { override fun deleteImage() {
setImageBitmap( setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art), BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
defaultFooterColor() ATHUtil.resolveColor(this, R.attr.defaultFooterColor)
) )
deleteAlbumArt = true deleteAlbumArt = true
dataChanged() dataChanged()
} }
override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity)
.asBitmapPalette()
.load(selectedFile)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
}
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
//android seems not to recognize album_artist field so we additionally write the normal artist field //android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap[FieldKey.ARTIST] = binding.albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = binding.genreTitle.text.toString() fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = binding.yearTitle.text.toString() fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString()
writeValuesToFiles( writeValuesToFiles(
fieldKeyValueMap, fieldKeyValueMap,
when { if (deleteAlbumArt) ArtworkInfo(id, null)
deleteAlbumArt -> ArtworkInfo(id, null) else if (albumArtBitmap == null) null else ArtworkInfo(id, albumArtBitmap!!)
albumArtBitmap == null -> null
else -> ArtworkInfo(id, albumArtBitmap!!)
}
) )
} }
override fun getSongPaths(): List<String> { override fun getSongPaths(): List<String> {
return repository.albumById(id).songs val songs = AlbumLoader.getAlbum(this, id).songs
.map(Song::data) val paths = ArrayList<String>(songs!!.size)
for (song in songs) {
paths.add(song.data)
}
return paths
} }
override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
MusicUtil.getSongFileUri(it.id) }
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
dataChanged()
} }
override fun setColors(color: Int) { override fun setColors(color: Int) {
super.setColors(color) super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color) saveFab.backgroundTintList = ColorStateList.valueOf(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
} }
}
override val editorImage: ImageView
get() = binding.editorImage
companion object { companion object {

View file

@ -1,214 +1,112 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.activities.tageditor package code.name.monkey.retromusic.activities.tageditor
import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.text.Editable
import android.widget.ImageView import android.text.TextWatcher
import android.widget.Toast import code.name.monkey.appthemehelper.util.ATHUtil
import androidx.core.widget.doAfterTextChanged import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.* import code.name.monkey.retromusic.loaders.SongLoader
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette import kotlinx.android.synthetic.main.activity_song_tag_editor.*
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.logD
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.shape.MaterialShapeDrawable
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.util.* import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>() { class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val bindingInflater: (LayoutInflater) -> ActivitySongTagEditorBinding = override val contentViewLayout: Int
ActivitySongTagEditorBinding::inflate get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>()
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews() setUpViews()
setSupportActionBar(binding.toolbar) toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
binding.appBarLayout?.statusBarForeground = setSupportActionBar(toolbar)
MaterialShapeDrawable.createWithElevationOverlay(this)
} }
@SuppressLint("ClickableViewAccessibility")
private fun setUpViews() { private fun setUpViews() {
fillViewsWithFileTags() fillViewsWithFileTags()
binding.songTextContainer.setTint(false) MaterialUtil.setTint(songTextContainer, false)
binding.composerContainer.setTint(false) MaterialUtil.setTint(composerContainer, false)
binding.albumTextContainer.setTint(false) MaterialUtil.setTint(albumTextContainer, false)
binding.artistContainer.setTint(false) MaterialUtil.setTint(artistContainer, false)
binding.albumArtistContainer.setTint(false) MaterialUtil.setTint(albumArtistContainer, false)
binding.yearContainer.setTint(false) MaterialUtil.setTint(yearContainer, false)
binding.genreContainer.setTint(false) MaterialUtil.setTint(genreContainer, false)
binding.trackNumberContainer.setTint(false) MaterialUtil.setTint(trackNumberContainer, false)
binding.discNumberContainer.setTint(false) MaterialUtil.setTint(lyricsContainer, false)
binding.lyricsContainer.setTint(false)
binding.songText.appHandleColor().doAfterTextChanged { dataChanged() } songText.appHandleColor().addTextChangedListener(this)
binding.albumText.appHandleColor().doAfterTextChanged { dataChanged() } albumText.appHandleColor().addTextChangedListener(this)
binding.albumArtistText.appHandleColor().doAfterTextChanged { dataChanged() } albumArtistText.appHandleColor().addTextChangedListener(this)
binding.artistText.appHandleColor().doAfterTextChanged { dataChanged() } artistText.appHandleColor().addTextChangedListener(this)
binding.genreText.appHandleColor().doAfterTextChanged { dataChanged() } genreText.appHandleColor().addTextChangedListener(this)
binding.yearText.appHandleColor().doAfterTextChanged { dataChanged() } yearText.appHandleColor().addTextChangedListener(this)
binding.trackNumberText.appHandleColor().doAfterTextChanged { dataChanged() } trackNumberText.appHandleColor().addTextChangedListener(this)
binding.discNumberText.appHandleColor().doAfterTextChanged { dataChanged() } lyricsText.appHandleColor().addTextChangedListener(this)
binding.lyricsText.appHandleColor().doAfterTextChanged { dataChanged() } songComposerText.appHandleColor().addTextChangedListener(this)
binding.songComposerText.appHandleColor().doAfterTextChanged { dataChanged() }
} }
private fun fillViewsWithFileTags() { private fun fillViewsWithFileTags() {
binding.songText.setText(songTitle) songText.setText(songTitle)
binding.albumArtistText.setText(albumArtist) albumArtistText.setText(albumArtist)
binding.albumText.setText(albumTitle) albumText.setText(albumTitle)
binding.artistText.setText(artistName) artistText.setText(artistName)
binding.genreText.setText(genreName) genreText.setText(genreName)
binding.yearText.setText(songYear) yearText.setText(songYear)
binding.trackNumberText.setText(trackNumber) trackNumberText.setText(trackNumber)
binding.discNumberText.setText(discNumber) lyricsText.setText(lyrics)
binding.lyricsText.setText(lyrics) songComposerText.setText(composer)
binding.songComposerText.setText(composer)
logD(songTitle + songYear)
} }
override fun loadCurrentImage() { override fun loadCurrentImage() {
val bitmap = albumArt
setImageBitmap(
bitmap,
RetroColorUtil.getColor(
RetroColorUtil.generatePalette(bitmap),
defaultFooterColor()
)
)
deleteAlbumArt = false
} }
override fun searchImageOnWeb() { override fun searchImageOnWeb() {
searchWebFor(binding.songText.text.toString(), binding.artistText.text.toString())
} }
override fun deleteImage() { override fun deleteImage() {
setImageBitmap(
BitmapFactory.decodeResource(resources, R.drawable.default_audio_art),
defaultFooterColor()
)
deleteAlbumArt = true
dataChanged()
}
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
color.isColorLight
)
).also {
saveFab.iconTint = it
saveFab.setTextColor(it)
}
} }
override fun save() { override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.TITLE] = binding.songText.text.toString() fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = binding.albumText.text.toString() fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = binding.artistText.text.toString() fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = binding.genreText.text.toString() fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = binding.yearText.text.toString() fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = binding.trackNumberText.text.toString() fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.DISC_NO] = binding.discNumberText.text.toString() fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = binding.lyricsText.text.toString() fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = binding.albumArtistText.text.toString() fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = binding.songComposerText.text.toString() writeValuesToFiles(fieldKeyValueMap, null)
writeValuesToFiles(
fieldKeyValueMap, when {
deleteAlbumArt -> ArtworkInfo(id, null)
albumArtBitmap == null -> null
else -> ArtworkInfo(id, albumArtBitmap!!)
}
)
} }
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data) override fun getSongPaths(): List<String> {
val paths = ArrayList<String>(1)
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id)) paths.add(SongLoader.getSong(this, id).data)
return paths
}
override fun loadImageFromFile(selectedFile: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@SongTagEditorActivity) }
.asBitmapPalette()
.load(selectedFile) override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
.diskCacheStrategy(DiskCacheStrategy.NONE) }
.skipMemoryCache(true)
.into(object : ImageViewTarget<BitmapPaletteWrapper>(binding.editorImage) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
override fun onResourceReady( }
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>? override fun afterTextChanged(s: Editable) {
) {
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = resource.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
RetroColorUtil.getColor(
resource.palette,
defaultFooterColor()
)
)
deleteAlbumArt = false
dataChanged() dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
showToast(R.string.error_load_failed, Toast.LENGTH_LONG)
}
override fun setResource(resource: BitmapPaletteWrapper?) {}
})
} }
companion object { companion object {
val TAG: String = SongTagEditorActivity::class.java.simpleName val TAG: String = SongTagEditorActivity::class.java.simpleName
} }
override val editorImage: ImageView
get() = binding.editorImage
} }

View file

@ -1,204 +0,0 @@
package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.showToast
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
import code.name.monkey.retromusic.model.AudioTagInfo
import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile
import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt
import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.CannotWriteException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.FieldDataInvalidException
import org.jaudiotagger.tag.TagException
import org.jaudiotagger.tag.images.AndroidArtwork
import org.jaudiotagger.tag.images.Artwork
import java.io.File
import java.io.IOException
class TagWriter {
companion object {
suspend fun scan(context: Context, toBeScanned: List<String?>?) {
if (toBeScanned.isNullOrEmpty()) {
Log.i("scan", "scan: Empty")
context.showToast("Scan file from folder")
return
}
MediaScannerConnection.scanFile(
context,
toBeScanned.toTypedArray(),
null,
withContext(Dispatchers.Main) {
if (context is Activity) UpdateToastMediaScannerCompletionListener(
context, toBeScanned
) else null
}
)
}
suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) {
withContext(Dispatchers.IO) {
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.JPEG,
100,
albumArtFile.outputStream()
)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val audioFile = AudioFileIO.read(File(filePath))
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: FieldDataInvalidException) {
withContext(Dispatchers.Main) {
context.showToast(R.string.could_not_write_tags_to_file)
}
return@withContext listOf<File>()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
scan(context, info.filePaths)
}
}
@RequiresApi(Build.VERSION_CODES.R)
suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo): List<File> =
withContext(Dispatchers.IO) {
val cacheFiles = mutableListOf<File>()
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.JPEG,
100,
albumArtFile.outputStream()
)
artwork = AndroidArtwork.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val originFile = File(filePath)
val cacheFile = File(context.cacheDir, originFile.name)
cacheFiles.add(cacheFile)
originFile.inputStream().use { input ->
cacheFile.outputStream().use { output ->
input.copyTo(output)
}
}
val audioFile = AudioFileIO.read(cacheFile)
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: FieldDataInvalidException) {
withContext(Dispatchers.Main) {
context.showToast(R.string.could_not_write_tags_to_file)
}
return@withContext listOf<File>()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
cacheFiles
}
}
}

View file

@ -0,0 +1,193 @@
package code.name.monkey.retromusic.activities.tageditor;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.SAFUtil;
public class WriteTagsAsyncTask extends
DialogAsyncTask<WriteTagsAsyncTask.LoadingInfo, Integer, String[]> {
private WeakReference<Activity> activity;
public WriteTagsAsyncTask(@NonNull Activity activity) {
super(activity);
this.activity = new WeakReference<>(activity);
}
@NonNull
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context)
.setTitle(R.string.saving_changes)
.setCancelable(false)
.setView(R.layout.loading)
.create();
}
@Override
protected String[] doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
Artwork artwork = null;
File albumArtFile = null;
if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) {
try {
albumArtFile = MusicUtil.createAlbumArtFile().getCanonicalFile();
info.artworkInfo.getArtwork()
.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
} catch (IOException e) {
e.printStackTrace();
}
}
int counter = 0;
boolean wroteArtwork = false;
boolean deletedArtwork = false;
for (String filePath : info.filePaths) {
publishProgress(++counter, info.filePaths.size());
try {
Uri safUri = null;
if (filePath.contains(SAFUtil.SEPARATOR)) {
String[] fragments = filePath.split(SAFUtil.SEPARATOR);
filePath = fragments[0];
safUri = Uri.parse(fragments[1]);
}
AudioFile audioFile = AudioFileIO.read(new File(filePath));
Tag tag = audioFile.getTagOrCreateAndSetDefault();
if (info.fieldKeyValueMap != null) {
for (Map.Entry<FieldKey, String> entry : info.fieldKeyValueMap.entrySet()) {
try {
tag.setField(entry.getKey(), entry.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.getArtwork() == null) {
tag.deleteArtworkField();
deletedArtwork = true;
} else if (artwork != null) {
tag.deleteArtworkField();
tag.setField(artwork);
wroteArtwork = true;
}
}
Activity activity = this.activity.get();
SAFUtil.write(activity, audioFile, safUri);
} catch (@NonNull Exception e) {
e.printStackTrace();
}
}
Context context = getContext();
if (context != null) {
if (wroteArtwork) {
MusicUtil.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) {
MusicUtil.deleteAlbumArt(context, info.artworkInfo.getAlbumId());
}
}
Collection<String> paths = info.filePaths;
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
paths = new ArrayList<>(info.filePaths.size());
for (String path : info.filePaths) {
if (path.contains(SAFUtil.SEPARATOR)) {
path = path.split(SAFUtil.SEPARATOR)[0];
}
paths.add(path);
}
}
return paths.toArray(new String[paths.size()]);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onCancelled(String[] toBeScanned) {
super.onCancelled(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onPostExecute(String[] toBeScanned) {
super.onPostExecute(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
//((MaterialDialog) dialog).setMaxProgress(values[1]);
//((MaterialDialog) dialog).setProgress(values[0]);
}
private void scan(String[] toBeScanned) {
Activity activity = this.activity.get();
if (activity != null) {
MediaScannerConnection.scanFile(activity, toBeScanned, null,
new UpdateToastMediaScannerCompletionListener(activity, toBeScanned));
}
}
public static class LoadingInfo {
@Nullable
final Map<FieldKey, String> fieldKeyValueMap;
final Collection<String> filePaths;
@Nullable
private AbsTagEditorActivity.ArtworkInfo artworkInfo;
public LoadingInfo(Collection<String> filePaths,
@Nullable Map<FieldKey, String> fieldKeyValueMap,
@Nullable AbsTagEditorActivity.ArtworkInfo artworkInfo) {
this.filePaths = filePaths;
this.fieldKeyValueMap = fieldKeyValueMap;
this.artworkInfo = artworkInfo;
}
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.adapter;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.CategoryInfo;
import code.name.monkey.retromusic.util.SwipeAndDragHelper;
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>
implements SwipeAndDragHelper.ActionCompletionContract {
private List<CategoryInfo> categoryInfos;
private ItemTouchHelper touchHelper;
public CategoryInfoAdapter(@NonNull List<CategoryInfo> categoryInfos) {
this.categoryInfos = categoryInfos;
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
touchHelper = new ItemTouchHelper(swipeAndDragHelper);
}
public void attachToRecyclerView(RecyclerView recyclerView) {
touchHelper.attachToRecyclerView(recyclerView);
}
@NonNull
public List<CategoryInfo> getCategoryInfos() {
return categoryInfos;
}
public void setCategoryInfos(@NonNull List<CategoryInfo> categoryInfos) {
this.categoryInfos = categoryInfos;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return categoryInfos.size();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
CategoryInfo categoryInfo = categoryInfos.get(position);
holder.checkBox.setChecked(categoryInfo.visible);
holder.title.setText(holder.title.getResources().getString(categoryInfo.category.stringRes));
holder.itemView.setOnClickListener(v -> {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible;
holder.checkBox.setChecked(categoryInfo.visible);
} else {
Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category,
Toast.LENGTH_SHORT).show();
}
});
holder.dragView.setOnTouchListener((view, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder);
}
return false;
}
);
}
@Override
@NonNull
public CategoryInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.preference_dialog_library_categories_listitem, parent, false);
return new ViewHolder(view);
}
@Override
public void onViewMoved(int oldPosition, int newPosition) {
CategoryInfo categoryInfo = categoryInfos.get(oldPosition);
categoryInfos.remove(oldPosition);
categoryInfos.add(newPosition, categoryInfo);
notifyItemMoved(oldPosition, newPosition);
}
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
if (categoryInfo.visible) {
for (CategoryInfo c : categoryInfos) {
if (c != categoryInfo && c.visible) {
return false;
}
}
}
return true;
}
static class ViewHolder extends RecyclerView.ViewHolder {
MaterialCheckBox checkBox;
View dragView;
TextView title;
ViewHolder(View view) {
super(view);
checkBox = view.findViewById(R.id.checkbox);
checkBox.setButtonTintList(
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
title = view.findViewById(R.id.title);
dragView = view.findViewById(R.id.drag_view);
}
}
}

View file

@ -1,116 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.adapter
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.PreferenceDialogLibraryCategoriesListitemBinding
import code.name.monkey.retromusic.extensions.showToast
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.SwipeAndDragHelper
import code.name.monkey.retromusic.util.SwipeAndDragHelper.ActionCompletionContract
class CategoryInfoAdapter : RecyclerView.Adapter<code.name.monkey.retromusic.adapter.CategoryInfoAdapter.ViewHolder>(),
ActionCompletionContract {
var categoryInfos: MutableList<CategoryInfo> =
PreferenceUtil.libraryCategory.toMutableList()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
notifyDataSetChanged()
}
private val touchHelper: ItemTouchHelper
fun attachToRecyclerView(recyclerView: RecyclerView?) {
touchHelper.attachToRecyclerView(recyclerView)
}
override fun getItemCount(): Int {
return categoryInfos.size
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val categoryInfo = categoryInfos[position]
holder.binding.checkbox.isChecked = categoryInfo.visible
holder.binding.title.text =
holder.binding.title.resources.getString(categoryInfo.category.stringRes)
holder.itemView.setOnClickListener {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible
holder.binding.checkbox.isChecked = categoryInfo.visible
} else {
holder.itemView.context.showToast(R.string.you_have_to_select_at_least_one_category)
}
}
holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder)
}
false
}
}
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): ViewHolder {
return ViewHolder(
PreferenceDialogLibraryCategoriesListitemBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
}
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
val categoryInfo = categoryInfos[oldPosition]
categoryInfos.removeAt(oldPosition)
categoryInfos.add(newPosition, categoryInfo)
notifyItemMoved(oldPosition, newPosition)
}
private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
if (categoryInfo.visible) {
for (c in categoryInfos) {
if (c !== categoryInfo && c.visible) {
return false
}
}
}
return true
}
class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.checkbox.buttonTintList =
ColorStateList.valueOf(accentColor(binding.checkbox.context))
}
}
init {
val swipeAndDragHelper = SwipeAndDragHelper(this)
touchHelper = ItemTouchHelper(swipeAndDragHelper)
}
}

View file

@ -0,0 +1,78 @@
package code.name.monkey.retromusic.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.RetroUtil.openUrl
import code.name.monkey.retromusic.views.RetroShapeableImageView
import com.bumptech.glide.Glide
class ContributorAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<ContributorAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == HEADER) {
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor_header,
parent,
false
)
)
} else ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
companion object {
const val HEADER: Int = 0
const val ITEM: Int = 1
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
HEADER
} else {
ITEM
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
Glide.with(image.context)
.load(contributor.profileImage)
.error(R.drawable.ic_account_white_24dp)
.placeholder(R.drawable.ic_account_white_24dp)
.dontAnimate()
.into(image)
}
}
}

View file

@ -1,37 +1,14 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter package code.name.monkey.retromusic.adapter
import android.annotation.SuppressLint import android.app.Activity
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 androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ItemGenreBinding import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.interfaces.IGenreClickListener
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import java.util.* import java.util.*
/** /**
@ -39,74 +16,41 @@ import java.util.*
*/ */
class GenreAdapter( class GenreAdapter(
private val activity: FragmentActivity, private val activity: Activity,
var dataSet: List<Genre>, var dataSet: List<Genre>,
private val listener: IGenreClickListener private val mItemLayoutRes: Int
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() { ) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
init {
this.setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return dataSet[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false)) return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position] val genre = dataSet[position]
holder.binding.title.text = genre.name holder.title?.text = genre.name
holder.binding.text.text = String.format( holder.text?.text = String.format(
Locale.getDefault(), Locale.getDefault(),
"%d %s", "%d %s",
genre.songCount, genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song) if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
) )
loadGenreImage(genre, holder)
}
private fun loadGenreImage(genre: Genre, holder: GenreAdapter.ViewHolder) {
val genreSong = MusicUtil.songByGenre(genre.id)
Glide.with(activity)
.asBitmapPalette()
.songCoverOptions(genreSong)
.load(RetroGlideExtension.getSongModel(genreSong))
.into(object : RetroMusicColoredTarget(holder.binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(holder, colors)
}
})
// Just for a bit of shadow around image
holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
}
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
holder.binding.title.setTextColor(color.primaryTextColor)
holder.binding.text.setTextColor(color.secondaryTextColor)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet.size
} }
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(list: List<Genre>) { fun swapDataSet(list: List<Genre>) {
dataSet = list dataSet = list
notifyDataSetChanged() notifyDataSetChanged()
} }
inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root), inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
listener.onClickGenre(dataSet[layoutPosition], itemView) super.onClick(v)
} val genre = dataSet[layoutPosition]
NavigationUtil.goToGenre(activity, genre)
init {
itemView.setOnClickListener(this)
} }
} }
} }

View file

@ -1,47 +1,31 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter package code.name.monkey.retromusic.adapter
import android.annotation.SuppressLint import android.util.DisplayMetrics
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 androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.core.os.bundleOf
import androidx.fragment.app.findFragment
import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumAdapter import code.name.monkey.retromusic.adapter.album.AlbumFullWidthAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.fragments.home.HomeFragment import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Home import code.name.monkey.retromusic.model.Home
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
class HomeAdapter(private val activity: AppCompatActivity) : class HomeAdapter(
RecyclerView.Adapter<RecyclerView.ViewHolder>(), IArtistClickListener, IAlbumClickListener { private val activity: AppCompatActivity,
private val displayMetrics: DisplayMetrics
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var list = listOf<Home>() private var list = listOf<Home>()
@ -50,75 +34,44 @@ class HomeAdapter(private val activity: AppCompatActivity) :
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = val layout = LayoutInflater.from(activity)
LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false) .inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) { return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
FAVOURITES -> PlaylistViewHolder(layout) PLAYLISTS -> PlaylistViewHolder(layout)
TOP_ALBUMS, RECENT_ALBUMS -> AlbumViewHolder(layout)
else -> { else -> {
ArtistViewHolder(layout) AlbumViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.metal_section_recycler_view,
parent,
false
)
)
} }
} }
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val home = list[position]
when (getItemViewType(position)) { when (getItemViewType(position)) {
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.recent_albums)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to RECENT_ALBUMS)
)
}
} }
TOP_ALBUMS -> { TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home) viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.top_albums)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to TOP_ALBUMS)
)
}
} }
RECENT_ARTISTS -> { RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(list[position].arrayList.toArtists(), R.string.recent_artists)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to RECENT_ARTISTS)
)
}
} }
TOP_ARTISTS -> { TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home) viewHolder.bindView(list[position].arrayList.toArtists(), R.string.top_artists)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to TOP_ARTISTS)
)
} }
} PLAYLISTS -> {
FAVOURITES -> {
val viewHolder = holder as PlaylistViewHolder val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(home) viewHolder.bindView(list[position].arrayList.toPlaylist(), R.string.favorites)
viewHolder.clickableArea.setOnClickListener {
it.findFragment<HomeFragment>().setSharedAxisXTransitions()
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to FAVOURITES)
)
}
} }
} }
} }
@ -127,87 +80,102 @@ class HomeAdapter(private val activity: AppCompatActivity) :
return list.size return list.size
} }
@SuppressLint("NotifyDataSetChanged")
fun swapData(sections: List<Home>) { fun swapData(sections: List<Home>) {
list = sections list = sections
notifyDataSetChanged() notifyDataSetChanged()
} }
@Suppress("UNCHECKED_CAST") companion object {
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS)
@Retention(AnnotationRetention.SOURCE)
annotation class HomeSection
const val RECENT_ALBUMS = 3
const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 0
const val PLAYLISTS = 4
}
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) { private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(home: Home) { fun bindView(list: ArrayList<Album>, titleRes: Int) {
title.setText(home.titleRes) if (list.isNotEmpty()) {
recyclerView.apply { recyclerView.apply {
adapter = albumAdapter(home.arrayList as List<Album>) show()
layoutManager = gridLayoutManager() adapter = AlbumFullWidthAdapter(activity, list, displayMetrics)
}
title.text = activity.getString(titleRes)
} }
} }
} }
@Suppress("UNCHECKED_CAST") inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) { fun bindView(list: ArrayList<Artist>, titleRes: Int) {
fun bindView(home: Home) { if (list.isNotEmpty()) {
title.setText(home.titleRes)
recyclerView.apply { recyclerView.apply {
layoutManager = linearLayoutManager() show()
adapter = artistsAdapter(home.arrayList as List<Artist>) layoutManager =
} LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
} val artistAdapter = ArtistAdapter(
}
@Suppress("UNCHECKED_CAST")
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(home: Home) {
title.setText(home.titleRes)
recyclerView.apply {
val songAdapter = SongAdapter(
activity, activity,
home.arrayList as MutableList<Song>, list,
R.layout.item_favourite_card PreferenceUtil.getInstance(activity).getHomeGridStyle(activity),
null
) )
layoutManager = linearLayoutManager() adapter = artistAdapter
}
title.text = activity.getString(titleRes)
}
}
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(arrayList: ArrayList<Playlist>, titleRes: Int) {
if (arrayList.isNotEmpty()) {
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0])
if (songs.isNotEmpty()) {
recyclerView.apply {
show()
val songAdapter =
SongAdapter(activity, songs, R.layout.item_album_card, null)
layoutManager =
GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
adapter = songAdapter adapter = songAdapter
} }
title.text = activity.getString(titleRes)
}
}
} }
} }
open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) { open inner class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView) val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
val title: AppCompatTextView = itemView.findViewById(R.id.title) val title: AppCompatTextView = itemView.findViewById(R.id.title)
val clickableArea: ViewGroup = itemView.findViewById(R.id.clickable_area)
}
private fun artistsAdapter(artists: List<Artist>) =
ArtistAdapter(activity, artists, PreferenceUtil.homeArtistGridStyle, this)
private fun albumAdapter(albums: List<Album>) =
AlbumAdapter(activity, albums, PreferenceUtil.homeAlbumGridStyle, this)
private fun gridLayoutManager() =
GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
private fun linearLayoutManager() =
LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
override fun onArtist(artistId: Long, view: View) {
activity.findNavController(R.id.fragment_container).navigate(
R.id.artistDetailsFragment,
bundleOf(EXTRA_ARTIST_ID to artistId),
null,
FragmentNavigatorExtras(
view to artistId.toString()
)
)
}
override fun onAlbumClick(albumId: Long, view: View) {
activity.findNavController(R.id.fragment_container).navigate(
R.id.albumDetailsFragment,
bundleOf(EXTRA_ALBUM_ID to albumId),
null,
FragmentNavigatorExtras(
view to albumId.toString()
)
)
} }
} }
private fun <E> ArrayList<E>.toAlbums(): ArrayList<Album> {
val arrayList = ArrayList<Album>()
for (x in this) {
arrayList.add(x as Album)
}
return arrayList
}
private fun <E> ArrayList<E>.toArtists(): ArrayList<Artist> {
val arrayList = ArrayList<Artist>()
for (x in this) {
arrayList.add(x as Artist)
}
return arrayList
}
private fun <E> ArrayList<E>.toPlaylist(): ArrayList<Playlist> {
val arrayList = ArrayList<Playlist>()
for (x in this) {
arrayList.add(x as Playlist)
}
return arrayList
}

View file

@ -1,176 +1,122 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter package code.name.monkey.retromusic.adapter
import android.annotation.SuppressLint import android.app.ActivityOptions
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 androidx.core.os.bundleOf import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.util.* import android.util.Pair as UtilPair
class SearchAdapter( class SearchAdapter(
private val activity: FragmentActivity, private val activity: AppCompatActivity,
private var dataSet: List<Any> private var dataSet: List<Any>?
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() { ) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
@SuppressLint("NotifyDataSetChanged") fun swapDataSet(dataSet: MutableList<Any>) {
fun swapDataSet(dataSet: List<Any>) {
this.dataSet = dataSet this.dataSet = dataSet
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
if (dataSet[position] is Album) return ALBUM if (dataSet!![position] is Album) return ALBUM
if (dataSet[position] is Artist) return if ((dataSet[position] as Artist).isAlbumArtist) ALBUM_ARTIST else ARTIST if (dataSet!![position] is Artist) return ARTIST
if (dataSet[position] is Genre) return GENRE if (dataSet!![position] is Genre) return GENRE
if (dataSet[position] is PlaylistWithSongs) return PLAYLIST if (dataSet!![position] is Playlist) return PLAYLIST
return if (dataSet[position] is Song) SONG else HEADER return if (dataSet!![position] is Song) SONG else HEADER
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) { return if (viewType == HEADER) ViewHolder(
HEADER -> ViewHolder(
LayoutInflater.from(activity).inflate( LayoutInflater.from(activity).inflate(
R.layout.sub_header, R.layout.sub_header,
parent, parent,
false false
), viewType ), viewType
) )
else
ALBUM, ARTIST, ALBUM_ARTIST -> ViewHolder( ViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_list_big,
parent,
false
), viewType
)
else -> ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
viewType viewType
) )
} }
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (getItemViewType(position)) { when (getItemViewType(position)) {
ALBUM -> { ALBUM -> {
holder.imageTextContainer?.isVisible = true val album = dataSet?.get(position) as Album
val album = dataSet[position] as Album
holder.title?.text = album.title holder.title?.text = album.title
holder.text?.text = album.artistName holder.text?.text = album.artistName
Glide.with(activity).asDrawable().albumCoverOptions(album.safeGetFirstSong()) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong())) .checkIgnoreMediaStore(activity).build().into(holder.image)
.into(holder.image!!)
} }
ARTIST -> { ARTIST -> {
holder.imageTextContainer?.isVisible = true val artist = dataSet?.get(position) as Artist
val artist = dataSet[position] as Artist
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist) holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
Glide.with(activity).asDrawable().artistImageOptions(artist).load( ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build()
RetroGlideExtension.getArtistModel(artist) .into(holder.image)
).into(holder.image!!)
} }
SONG -> { SONG -> {
holder.imageTextContainer?.isVisible = true val song = dataSet?.get(position) as Song
val song = dataSet[position] as Song
holder.title?.text = song.title holder.title?.text = song.title
holder.text?.text = song.albumName holder.text?.text = song.albumName
Glide.with(activity).asDrawable().songCoverOptions(song)
.load(RetroGlideExtension.getSongModel(song)).into(holder.image!!)
} }
GENRE -> { GENRE -> {
val genre = dataSet[position] as Genre val genre = dataSet?.get(position) as Genre
holder.title?.text = genre.name holder.title?.text = genre.name
holder.text?.text = String.format(
Locale.getDefault(),
"%d %s",
genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(
R.string.song
)
)
} }
PLAYLIST -> { PLAYLIST -> {
val playlist = dataSet[position] as PlaylistWithSongs val playlist = dataSet?.get(position) as Playlist
holder.title?.text = playlist.playlistEntity.playlistName holder.title?.text = playlist.name
//holder.text?.text = MusicUtil.playlistInfoString(activity, playlist.songs) holder.text?.text = MusicUtil.getPlaylistInfoString(activity, getSongs(playlist))
} }
ALBUM_ARTIST -> {
holder.imageTextContainer?.isVisible = true
val artist = dataSet[position] as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
Glide.with(activity).asDrawable().artistImageOptions(artist).load(
RetroGlideExtension.getArtistModel(artist)
).into(holder.image!!)
}
else -> { else -> {
holder.title?.text = dataSet[position].toString() holder.title?.text = dataSet?.get(position).toString()
holder.title?.setTextColor(ThemeStore.accentColor(activity)) holder.title?.setTextColor(ThemeStore.accentColor(activity))
} }
} }
} }
private fun getSongs(playlist: Playlist): java.util.ArrayList<Song> {
val songs = java.util.ArrayList<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity))
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
return songs
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return dataSet.size return dataSet!!.size
} }
inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) {
init { init {
itemView.setOnLongClickListener(null) itemView.setOnLongClickListener(null)
imageTextContainer?.isInvisible = true
if (itemViewType == SONG) { if (itemViewType == SONG) {
imageTextContainer?.isGone = true menu?.visibility = View.VISIBLE
menu?.isVisible = true
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) { menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
override val song: Song override val song: Song
get() = dataSet[layoutPosition] as Song get() = dataSet!![layoutPosition] as Song
}) })
} else { } else {
menu?.isVisible = false menu?.visibility = View.GONE
} }
when (itemViewType) { when (itemViewType) {
@ -178,52 +124,38 @@ class SearchAdapter(
ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image)) ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
else -> { else -> {
val container = itemView.findViewById<View>(R.id.imageContainer) val container = itemView.findViewById<View>(R.id.imageContainer)
container?.isVisible = false container?.visibility = View.GONE
} }
} }
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
val item = dataSet[layoutPosition] val item = dataSet!![layoutPosition]
when (itemViewType) { when (itemViewType) {
ALBUM -> { ALBUM -> {
activity.findNavController(R.id.fragment_container).navigate( val options = ActivityOptions.makeSceneTransitionAnimation(
R.id.albumDetailsFragment, activity,
bundleOf(EXTRA_ALBUM_ID to (item as Album).id) UtilPair.create(image, activity.getString(R.string.transition_album_art))
) )
NavigationUtil.goToAlbumOptions(activity, (item as Album).id, options)
} }
ARTIST -> { ARTIST -> {
activity.findNavController(R.id.fragment_container).navigate( val options = ActivityOptions.makeSceneTransitionAnimation(
R.id.artistDetailsFragment, activity,
bundleOf(EXTRA_ARTIST_ID to (item as Artist).id) UtilPair.create(image, activity.getString(R.string.transition_artist_image))
) )
NavigationUtil.goToArtistOptions(activity, (item as Artist).id, options)
} }
ALBUM_ARTIST -> {
activity.findNavController(R.id.fragment_container).navigate(
R.id.albumArtistDetailsFragment,
bundleOf(EXTRA_ARTIST_NAME to (item as Artist).name)
)
}
GENRE -> { GENRE -> {
activity.findNavController(R.id.fragment_container).navigate( NavigationUtil.goToGenre(activity, item as Genre)
R.id.genreDetailsFragment,
bundleOf(EXTRA_GENRE to (item as Genre))
)
} }
PLAYLIST -> { PLAYLIST -> {
activity.findNavController(R.id.fragment_container).navigate( NavigationUtil.goToPlaylistNew(activity, item as Playlist)
R.id.playlistDetailsFragment,
bundleOf(EXTRA_PLAYLIST_ID to (item as PlaylistWithSongs).playlistEntity.playListId)
)
} }
SONG -> { SONG -> {
MusicPlayerRemote.playNext(item as Song) val playList = ArrayList<Song>()
MusicPlayerRemote.playNextSong() playList.add(item as Song)
MusicPlayerRemote.openQueue(playList, 0, true)
} }
} }
} }
@ -236,6 +168,5 @@ class SearchAdapter(
private const val SONG = 3 private const val SONG = 3
private const val GENRE = 4 private const val GENRE = 4
private const val PLAYLIST = 5 private const val PLAYLIST = 5
private const val ALBUM_ARTIST = 6
} }
} }

View file

@ -1,17 +1,3 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter package code.name.monkey.retromusic.adapter
import android.graphics.PorterDuff import android.graphics.PorterDuff
@ -20,31 +6,32 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.extensions.getTintedDrawable import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.ICallbacks import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File import java.io.File
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
class SongFileAdapter( class SongFileAdapter(
override val activity: AppCompatActivity, private val activity: AppCompatActivity,
private var dataSet: List<File>, private var dataSet: List<File>,
private val itemLayoutRes: Int, private val itemLayoutRes: Int,
private val iCallbacks: ICallbacks? private val callbacks: Callbacks?,
cabHolder: CabHolder?
) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>( ) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>(
activity, R.menu.menu_media_selection activity, cabHolder, R.menu.menu_media_selection
), PopupTextProvider { ), PopupTextProvider {
init { init {
@ -76,7 +63,7 @@ class SongFileAdapter(
if (holder.itemViewType == FILE) { if (holder.itemViewType == FILE) {
holder.text?.text = getFileText(file) holder.text?.text = getFileText(file)
} else { } else {
holder.text?.isVisible = false holder.text?.visibility = View.GONE
} }
} }
@ -94,28 +81,30 @@ class SongFileAdapter(
} }
private fun loadFileImage(file: File, holder: ViewHolder) { private fun loadFileImage(file: File, holder: ViewHolder) {
val iconColor = ATHUtil.resolveColor(activity, androidx.appcompat.R.attr.colorControlNormal) val iconColor = ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
if (file.isDirectory) { if (file.isDirectory) {
holder.image?.let { holder.image?.let {
it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN) it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
it.setImageResource(R.drawable.ic_folder) it.setImageResource(R.drawable.ic_folder_white_24dp)
} }
holder.imageTextContainer?.setCardBackgroundColor( holder.imageTextContainer?.setCardBackgroundColor(
ATHUtil.resolveColor( ATHUtil.resolveColor(
activity, activity,
com.google.android.material.R.attr.colorSurface R.attr.colorSurface
) )
) )
} else { } else {
val error = activity.getTintedDrawable(R.drawable.ic_audio_file, iconColor) val error = RetroUtil.getTintedVectorDrawable(
activity, R.drawable.ic_file_music_white_24dp, iconColor
)
Glide.with(activity) Glide.with(activity)
.load(AudioFileCover(file.path)) .load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error) .error(error)
.placeholder(error) .placeholder(error)
.transition(RetroGlideExtension.getDefaultTransition()) .animate(android.R.anim.fade_in)
.signature(MediaStoreSignature("", file.lastModified(), 0)) .signature(MediaStoreSignature("", file.lastModified(), 0))
.into(holder.image!!) .into(holder.image)
} }
} }
@ -123,35 +112,43 @@ class SongFileAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): File { override fun getIdentifier(position: Int): File? {
return dataSet[position] return dataSet[position]
} }
override fun getName(model: File): String { override fun getName(`object`: File): String {
return getFileTitle(model) return getFileTitle(`object`)
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<File>) {
if (iCallbacks == null) return if (callbacks == null) return
iCallbacks.onMultipleItemAction(menuItem, selection as ArrayList<File>) callbacks.onMultipleItemAction(menuItem, selection)
} }
override fun getPopupText(position: Int): String { override fun getPopupText(position: Int): String {
return if (position >= dataSet.lastIndex) "" else getSectionName(position) return getSectionName(position)
} }
private fun getSectionName(position: Int): String { private fun getSectionName(position: Int): String {
return MusicUtil.getSectionName(dataSet[position].name) return MusicUtil.getSectionName(dataSet[position].name)
} }
inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) { interface Callbacks {
fun onFileSelected(file: File)
fun onFileMenuClicked(file: File, view: View)
fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
if (menu != null && iCallbacks != null) { if (menu != null && callbacks != null) {
menu?.setOnClickListener { v -> menu?.setOnClickListener { v ->
val position = layoutPosition val position = layoutPosition
if (isPositionInRange(position)) { if (isPositionInRange(position)) {
iCallbacks.onFileMenuClicked(dataSet[position], v) callbacks.onFileMenuClicked(dataSet[position], v)
} }
} }
} }
@ -166,7 +163,7 @@ class SongFileAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(position) toggleChecked(position)
} else { } else {
iCallbacks?.onFileSelected(dataSet[position]) callbacks?.onFileSelected(dataSet[position])
} }
} }
} }

View file

@ -1,55 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import java.io.File
class StorageAdapter(
val storageList: List<Storage>,
val storageClickListener: StorageClickListener
) :
RecyclerView.Adapter<StorageAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_storage,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindData(storageList[position])
}
override fun getItemCount(): Int {
return storageList.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
fun bindData(storage: Storage) {
title.text = storage.title
}
init {
itemView.setOnClickListener { storageClickListener.onStorageClicked(storageList[bindingAdapterPosition]) }
}
}
}
interface StorageClickListener {
fun onStorageClicked(storage: Storage)
}
class Storage {
lateinit var title: String
lateinit var file: File
}

View file

@ -0,0 +1,52 @@
package code.name.monkey.retromusic.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.views.RetroShapeableImageView
class TranslatorsAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<TranslatorsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
RetroUtil.openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
image.hide()
}
}
}

View file

@ -1,51 +1,40 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter.album package code.name.monkey.retromusic.adapter.album
import android.app.ActivityOptions
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter( open class AlbumAdapter(
override val activity: FragmentActivity, protected val activity: AppCompatActivity,
var dataSet: List<Album>, var dataSet: List<Album>,
var itemLayoutRes: Int, protected var itemLayoutRes: Int,
val listener: IAlbumClickListener? cabHolder: CabHolder?
) : AbsMultiSelectAdapter<AlbumAdapter.ViewHolder, Album>( ) : AbsMultiSelectAdapter<AlbumAdapter.ViewHolder, Album>(
activity, activity,
cabHolder,
R.menu.menu_media_selection R.menu.menu_media_selection
), PopupTextProvider { ), PopupTextProvider {
@ -67,18 +56,12 @@ open class AlbumAdapter(
return ViewHolder(view) return ViewHolder(view)
} }
private fun getAlbumTitle(album: Album): String { private fun getAlbumTitle(album: Album): String? {
return album.title return album.title
} }
protected open fun getAlbumText(album: Album): String? { protected open fun getAlbumText(album: Album): String? {
return album.albumArtist.let { return album.artistName
if (it.isNullOrEmpty()) {
album.artistName
} else {
it
}
}
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -87,39 +70,54 @@ open class AlbumAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album) holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album) holder.text?.text = getAlbumText(album)
// Check if imageContainer exists so we can have a smooth transition without holder.playSongs?.setOnClickListener {
// CardView clipping, if it doesn't exist in current layout set transition name to image instead. album.songs?.let { songs ->
if (holder.imageContainer != null) { MusicPlayerRemote.openQueue(
holder.imageContainer?.transitionName = album.id.toString() songs,
} else { 0,
holder.image?.transitionName = album.id.toString() true
)
}
} }
loadAlbumCover(album, holder) loadAlbumCover(album, holder)
} }
protected open fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { protected open fun setColors(color: Int, holder: ViewHolder) {
if (holder.paletteColorContainer != null) { if (holder.paletteColorContainer != null) {
holder.title?.setTextColor(color.primaryTextColor) holder.title?.setTextColor(
holder.text?.setTextColor(color.secondaryTextColor) MaterialValueHelper.getPrimaryTextColor(
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor) activity,
ColorUtil.isColorLight(color)
)
)
holder.text?.setTextColor(
MaterialValueHelper.getSecondaryTextColor(
activity,
ColorUtil.isColorLight(color)
)
)
holder.paletteColorContainer?.setBackgroundColor(color)
} }
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor) holder.mask?.backgroundTintList = ColorStateList.valueOf(color)
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
} }
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) { protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) { if (holder.image == null) {
return return
} }
val song = album.safeGetFirstSong()
Glide.with(activity) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.asBitmapPalette() .checkIgnoreMediaStore(activity)
.albumCoverOptions(song) .generatePalette(activity)
//.checkIgnoreMediaStore() .build()
.load(RetroGlideExtension.getSongModel(song))
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onLoadCleared(placeholder: Drawable?) {
setColors(colors, holder) super.onLoadCleared(placeholder)
//setColors(defaultFooterColor, holder)
}
override fun onColorReady(color: Int) {
setColors(color, holder)
} }
}) })
} }
@ -129,28 +127,27 @@ open class AlbumAdapter(
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return dataSet[position].id return dataSet[position].id.toLong()
} }
override fun getIdentifier(position: Int): Album? { override fun getIdentifier(position: Int): Album? {
return dataSet[position] return dataSet[position]
} }
override fun getName(model: Album): String { override fun getName(album: Album): String {
return model.title return album.title!!
} }
override fun onMultipleItemAction( override fun onMultipleItemAction(
menuItem: MenuItem, menuItem: MenuItem, selection: ArrayList<Album>
selection: List<Album>
) { ) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
} }
private fun getSongList(albums: List<Album>): List<Song> { private fun getSongList(albums: List<Album>): ArrayList<Song> {
val songs = ArrayList<Song>() val songs = ArrayList<Song>()
for (album in albums) { for (album in albums) {
songs.addAll(album.songs) songs.addAll(album.songs!!)
} }
return songs return songs
} }
@ -161,22 +158,23 @@ open class AlbumAdapter(
private fun getSectionName(position: Int): String { private fun getSectionName(position: Int): String {
var sectionName: String? = null var sectionName: String? = null
when (PreferenceUtil.albumSortOrder) { when (PreferenceUtil.getInstance(activity).albumSortOrder) {
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName = SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
dataSet[position].title dataSet[position].title
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].albumArtist
SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString( SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
dataSet[position].year dataSet[position].year
) )
} }
return MusicUtil.getSectionName(sectionName) return MusicUtil.getSectionName(sectionName)
} }
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
menu?.isVisible = false setImageTransitionName(activity.getString(R.string.transition_album_art))
menu?.visibility = View.GONE
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
@ -184,14 +182,22 @@ open class AlbumAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
image?.let { val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
listener?.onAlbumClick(dataSet[layoutPosition].id, imageContainer ?: it) activity,
} imageContainerCard ?: image,
"${activity.getString(R.string.transition_album_art)}_${dataSet[layoutPosition].id}"
)
NavigationUtil.goToAlbumOptions(
activity,
dataSet[layoutPosition].id,
activityOptions
)
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
return toggleChecked(layoutPosition) toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }

View file

@ -1,17 +1,3 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter.album package code.name.monkey.retromusic.adapter.album
import android.os.Bundle import android.os.Bundle
@ -19,31 +5,17 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.core.os.BundleCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.goToLyrics
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroGlideExtension.songCoverOptions
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AlbumCoverPagerAdapter( class AlbumCoverPagerAdapter(
fragmentManager: FragmentManager, fragmentManager: FragmentManager,
@ -88,17 +60,31 @@ class AlbumCoverPagerAdapter(
class AlbumCoverFragment : Fragment() { class AlbumCoverFragment : Fragment() {
lateinit var albumCover: ImageView
private var isColorReady: Boolean = false private var isColorReady: Boolean = false
private lateinit var color: MediaNotificationProcessor private var color: Int = 0
private lateinit var song: Song private lateinit var song: Song
private var colorReceiver: ColorReceiver? = null private var colorReceiver: ColorReceiver? = null
private var request: Int = 0 private var request: Int = 0
private val mainActivity get() = activity as MainActivity
private val layout: Int
get() {
return when (PreferenceUtil.getInstance(requireContext()).albumCoverStyle) {
AlbumCoverStyle.NORMAL -> R.layout.fragment_album_cover
AlbumCoverStyle.FLAT -> R.layout.fragment_album_flat_cover
AlbumCoverStyle.CIRCLE -> R.layout.fragment_album_circle_cover
AlbumCoverStyle.CARD -> R.layout.fragment_album_card_cover
AlbumCoverStyle.MATERIAL -> R.layout.fragment_album_material_cover
AlbumCoverStyle.FULL -> R.layout.fragment_album_full_cover
AlbumCoverStyle.FULL_CARD -> R.layout.fragment_album_full_card_cover
else -> R.layout.fragment_album_cover
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (arguments != null) { if (arguments != null) {
song = BundleCompat.getParcelable(requireArguments(), SONG_ARG, Song::class.java)!! song = requireArguments().getParcelable(SONG_ARG)!!
} }
} }
@ -107,58 +93,22 @@ class AlbumCoverPagerAdapter(
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val finalLayout = when {
view.setOnClickListener { PreferenceUtil.getInstance(requireContext())
if (mainActivity.getBottomSheetBehavior().state == STATE_EXPANDED) { .carouselEffect() -> R.layout.fragment_album_carousel_cover
showLyricsDialog() else -> layout
} }
val view = inflater.inflate(finalLayout, container, false)
albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener {
NavigationUtil.goToLyrics(requireActivity())
} }
return view return view
} }
private fun showLyricsDialog() {
lifecycleScope.launch(Dispatchers.IO) {
val data: String? = MusicUtil.getLyrics(song)
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(
requireContext(),
com.google.android.material.R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
).apply {
setTitle(song.title)
setMessage(if (data.isNullOrEmpty()) "No lyrics found" else data)
setNegativeButton(R.string.synced_lyrics) { _, _ ->
goToLyrics(requireActivity())
}
show()
}
}
}
}
private fun getLayoutWithPlayerTheme(): Int {
return when (PreferenceUtil.nowPlayingScreen) {
Card, Fit, Tiny, Classic, Gradient, Full -> R.layout.fragment_album_full_cover
Peek -> R.layout.fragment_peek_album_cover
else -> {
if (PreferenceUtil.isCarouselEffect) {
R.layout.fragment_album_carousel_cover
} else {
when (PreferenceUtil.albumCoverStyle) {
AlbumCoverStyle.Normal -> R.layout.fragment_album_cover
AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
loadAlbumCover(albumCover = view.findViewById(R.id.player_image)) loadAlbumCover()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -166,21 +116,18 @@ class AlbumCoverPagerAdapter(
colorReceiver = null colorReceiver = null
} }
private fun loadAlbumCover(albumCover: ImageView) { private fun loadAlbumCover() {
Glide.with(this) SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
.asBitmapPalette() .checkIgnoreMediaStore(requireContext())
.songCoverOptions(song) .generatePalette(requireContext()).build()
//.checkIgnoreMediaStore()
.load(RetroGlideExtension.getSongModel(song))
.dontAnimate()
.into(object : RetroMusicColoredTarget(albumCover) { .into(object : RetroMusicColoredTarget(albumCover) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onColorReady(color: Int) {
setColor(colors) setColor(color)
} }
}) })
} }
private fun setColor(color: MediaNotificationProcessor) { private fun setColor(color: Int) {
this.color = color this.color = color
isColorReady = true isColorReady = true
if (colorReceiver != null) { if (colorReceiver != null) {
@ -199,7 +146,7 @@ class AlbumCoverPagerAdapter(
} }
interface ColorReceiver { interface ColorReceiver {
fun onColorReady(color: MediaNotificationProcessor, request: Int) fun onColorReady(color: Int, request: Int)
} }
companion object { companion object {
@ -208,7 +155,9 @@ class AlbumCoverPagerAdapter(
fun newInstance(song: Song): AlbumCoverFragment { fun newInstance(song: Song): AlbumCoverFragment {
val frag = AlbumCoverFragment() val frag = AlbumCoverFragment()
frag.arguments = bundleOf(SONG_ARG to song) val args = Bundle()
args.putParcelable(SONG_ARG, song)
frag.arguments = args
return frag return frag
} }
} }
@ -218,3 +167,4 @@ class AlbumCoverPagerAdapter(
val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName
} }
} }

View file

@ -0,0 +1,105 @@
/*
* Copyright (C) 2017. Alexander Bilchuk <a.bilchuk@sandrlab.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package code.name.monkey.retromusic.adapter.album
import android.app.Activity
import android.app.ActivityOptions
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.views.MetalRecyclerViewPager
import com.bumptech.glide.Glide
class AlbumFullWidthAdapter(
private val activity: Activity,
private val dataSet: List<Album>,
metrics: DisplayMetrics
) : MetalRecyclerViewPager.MetalAdapter<AlbumFullWidthAdapter.FullMetalViewHolder>(metrics) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FullMetalViewHolder {
return FullMetalViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.pager_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: FullMetalViewHolder, position: Int) {
// don't forget about calling supper.onBindViewHolder!
super.onBindViewHolder(holder, position)
val album = dataSet[position]
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
holder.playSongs?.setOnClickListener {
album.songs?.let { songs ->
MusicPlayerRemote.openQueue(
songs,
0,
true
)
}
}
loadAlbumCover(album, holder)
}
private fun getAlbumTitle(album: Album): String? {
return album.title
}
private fun getAlbumText(album: Album): String? {
return album.artistName
}
private fun loadAlbumCover(album: Album, holder: FullMetalViewHolder) {
if (holder.image == null) {
return
}
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity)
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(color: Int) {
}
})
}
override fun getItemCount(): Int {
return dataSet.size
}
inner class FullMetalViewHolder(itemView: View) :
MetalRecyclerViewPager.MetalViewHolder(itemView) {
override fun onClick(v: View?) {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
"${activity.getString(R.string.transition_album_art)}_${dataSet[layoutPosition].id}"
)
NavigationUtil.goToAlbumOptions(activity, dataSet[layoutPosition].id, activityOptions)
}
}
}

View file

@ -1,39 +1,24 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter.album package code.name.monkey.retromusic.adapter.album
import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.glide.RetroGlideExtension.albumCoverOptions import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.HorizontalAdapterHelper import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
import code.name.monkey.retromusic.interfaces.IAlbumClickListener import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
class HorizontalAlbumAdapter( class HorizontalAlbumAdapter(
activity: FragmentActivity, activity: AppCompatActivity,
dataSet: List<Album>, dataSet: List<Album>,
albumClickListener: IAlbumClickListener cabHolder: CabHolder?
) : AlbumAdapter( ) : AlbumAdapter(
activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, albumClickListener activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, cabHolder
) { ) {
override fun createViewHolder(view: View, viewType: Int): ViewHolder { override fun createViewHolder(view: View, viewType: Int): ViewHolder {
@ -42,30 +27,35 @@ class HorizontalAlbumAdapter(
return ViewHolder(view) return ViewHolder(view)
} }
override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) { override fun setColors(color: Int, holder: ViewHolder) {
// holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary)) holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
// holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary)) holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
} }
override fun loadAlbumCover(album: Album, holder: ViewHolder) { override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return if (holder.image == null) return
Glide.with(activity) AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.asBitmapPalette() .checkIgnoreMediaStore(activity)
.albumCoverOptions(album.safeGetFirstSong()) .generatePalette(activity)
.load(RetroGlideExtension.getSongModel(album.safeGetFirstSong())) .build()
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onLoadCleared(placeholder: Drawable?) {
setColors(colors, holder) super.onLoadCleared(placeholder)
setColors(albumArtistFooterColor, holder)
}
override fun onColorReady(color: Int) {
setColors(color, holder)
} }
}) })
} }
override fun getAlbumText(album: Album): String { override fun getAlbumText(album: Album): String? {
return MusicUtil.getYearString(album.year) return MusicUtil.getYearString(album.year)
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return HorizontalAdapterHelper.getItemViewType(position, itemCount) return HorizontalAdapterHelper.getItemViewtype(position, itemCount)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {

View file

@ -1,79 +1,55 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter.artist package code.name.monkey.retromusic.adapter.artist
import android.annotation.SuppressLint import android.app.ActivityOptions
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources import android.graphics.drawable.Drawable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroGlideExtension.artistImageOptions
import code.name.monkey.retromusic.glide.RetroGlideExtension.asBitmapPalette
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.IAlbumArtistClickListener import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.interfaces.IArtistClickListener
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
class ArtistAdapter( class ArtistAdapter(
override val activity: FragmentActivity, val activity: AppCompatActivity,
var dataSet: List<Artist>, var dataSet: List<Artist>,
var itemLayoutRes: Int, var itemLayoutRes: Int,
val IArtistClickListener: IArtistClickListener, cabHolder: CabHolder?
val IAlbumArtistClickListener: IAlbumArtistClickListener? = null ) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(activity, R.menu.menu_media_selection), activity, cabHolder, R.menu.menu_media_selection
PopupTextProvider { ), PopupTextProvider {
var albumArtistsOnly = false
init { init {
this.setHasStableIds(true) this.setHasStableIds(true)
} }
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(dataSet: List<Artist>) { fun swapDataSet(dataSet: List<Artist>) {
this.dataSet = dataSet this.dataSet = dataSet
notifyDataSetChanged() notifyDataSetChanged()
albumArtistsOnly = PreferenceUtil.albumArtistsOnly
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return dataSet[position].id return dataSet[position].id.toLong()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
try {
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
} catch (e: Resources.NotFoundException) {
LayoutInflater.from(activity).inflate(R.layout.item_grid_circle, parent, false)
}
return createViewHolder(view) return createViewHolder(view)
} }
@ -87,37 +63,39 @@ class ArtistAdapter(
holder.itemView.isActivated = isChecked holder.itemView.isActivated = isChecked
holder.title?.text = artist.name holder.title?.text = artist.name
holder.text?.hide() holder.text?.hide()
val transitionName =
if (albumArtistsOnly) artist.name else artist.id.toString()
if (holder.imageContainer != null) {
holder.imageContainer?.transitionName = transitionName
} else {
holder.image?.transitionName = transitionName
}
loadArtistImage(artist, holder) loadArtistImage(artist, holder)
} }
private fun setColors(processor: MediaNotificationProcessor, holder: ViewHolder) { fun setColors(color: Int, holder: ViewHolder) {
holder.mask?.backgroundTintList = ColorStateList.valueOf(processor.primaryTextColor)
if (holder.paletteColorContainer != null) { if (holder.paletteColorContainer != null) {
holder.paletteColorContainer?.setBackgroundColor(processor.backgroundColor) holder.paletteColorContainer?.setBackgroundColor(color)
holder.title?.setTextColor(processor.primaryTextColor) holder.title?.setTextColor(
MaterialValueHelper.getPrimaryTextColor(
activity, ColorUtil.isColorLight(
color
)
)
)
} }
holder.imageContainerCard?.setCardBackgroundColor(processor.backgroundColor) holder.imageContainerCard?.setCardBackgroundColor(color)
holder.mask?.backgroundTintList = ColorStateList.valueOf(color)
} }
private fun loadArtistImage(artist: Artist, holder: ViewHolder) { private fun loadArtistImage(artist: Artist, holder: ViewHolder) {
if (holder.image == null) { if (holder.image == null) {
return return
} }
Glide.with(activity) ArtistGlideRequest.Builder.from(Glide.with(activity), artist)
.asBitmapPalette() .generatePalette(activity)
.artistImageOptions(artist) .build()
.load(RetroGlideExtension.getArtistModel(artist))
.transition(RetroGlideExtension.getDefaultTransition())
.into(object : RetroMusicColoredTarget(holder.image!!) { .into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) { override fun onLoadCleared(placeholder: Drawable?) {
setColors(colors, holder) super.onLoadCleared(placeholder)
setColors(defaultFooterColor, holder)
}
override fun onColorReady(color: Int) {
setColors(color, holder)
} }
}) })
} }
@ -126,22 +104,21 @@ class ArtistAdapter(
return dataSet.size return dataSet.size
} }
override fun getIdentifier(position: Int): Artist { override fun getIdentifier(position: Int): Artist? {
return dataSet[position] return dataSet[position]
} }
override fun getName(model: Artist): String { override fun getName(artist: Artist): String {
return model.name return artist.name
} }
override fun onMultipleItemAction( override fun onMultipleItemAction(
menuItem: MenuItem, menuItem: MenuItem, selection: ArrayList<Artist>
selection: List<Artist>
) { ) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
} }
private fun getSongList(artists: List<Artist>): List<Song> { private fun getSongList(artists: List<Artist>): ArrayList<Song> {
val songs = ArrayList<Song>() val songs = ArrayList<Song>()
for (artist in artists) { for (artist in artists) {
songs.addAll(artist.songs) // maybe async in future? songs.addAll(artist.songs) // maybe async in future?
@ -157,10 +134,11 @@ class ArtistAdapter(
return MusicUtil.getSectionName(dataSet[position].name) return MusicUtil.getSectionName(dataSet[position].name)
} }
inner class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init { init {
menu?.isVisible = false setImageTransitionName(activity.getString(R.string.transition_artist_image))
menu?.visibility = View.GONE
} }
override fun onClick(v: View?) { override fun onClick(v: View?) {
@ -168,19 +146,20 @@ class ArtistAdapter(
if (isInQuickSelectMode) { if (isInQuickSelectMode) {
toggleChecked(layoutPosition) toggleChecked(layoutPosition)
} else { } else {
val artist = dataSet[layoutPosition] val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
image?.let { activity,
if (albumArtistsOnly && IAlbumArtistClickListener != null) { imageContainerCard ?: image,
IAlbumArtistClickListener.onAlbumArtist(artist.name, imageContainer ?: it) "${activity.getString(R.string.transition_artist_image)}_${dataSet[layoutPosition].id}"
} else { )
IArtistClickListener.onArtist(artist.id, imageContainer ?: it) NavigationUtil.goToArtistOptions(
} activity, dataSet[layoutPosition].id, activityOptions
} )
} }
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
return toggleChecked(layoutPosition) toggleChecked(layoutPosition)
return super.onLongClick(v)
} }
} }
} }

View file

@ -1,65 +0,0 @@
package code.name.monkey.retromusic.adapter.backup
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ItemListBackupBinding
import java.io.File
class BackupAdapter(
val activity: FragmentActivity,
var dataSet: MutableList<File>,
val backupClickedListener: BackupClickedListener
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.title.text = dataSet[position].nameWithoutExtension
}
override fun getItemCount(): Int = dataSet.size
@SuppressLint("NotifyDataSetChanged")
fun swapDataset(dataSet: List<File>) {
this.dataSet = ArrayList(dataSet)
notifyDataSetChanged()
}
inner class ViewHolder(val binding: ItemListBackupBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.menu.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_backup)
popupMenu.setOnMenuItemClickListener { menuItem ->
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
dataSet[bindingAdapterPosition],
menuItem
)
}
popupMenu.show()
}
itemView.setOnClickListener {
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
}
}
}
interface BackupClickedListener {
fun onBackupClicked(file: File)
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
}
}

View file

@ -0,0 +1,133 @@
package code.name.monkey.retromusic.adapter.base;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.CabHolder;
public abstract class AbsMultiSelectAdapter<VH extends RecyclerView.ViewHolder, I> extends RecyclerView.Adapter<VH>
implements MaterialCab.Callback {
@Nullable
private final CabHolder cabHolder;
private final Context context;
private MaterialCab cab;
private ArrayList<I> checked;
private int menuRes;
public AbsMultiSelectAdapter(@NonNull Context context, @Nullable CabHolder cabHolder, @MenuRes int menuRes) {
this.cabHolder = cabHolder;
checked = new ArrayList<>();
this.menuRes = menuRes;
this.context = context;
}
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
return true;
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
clearChecked();
return true;
}
@Override
public boolean onCabItemClicked(MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) {
checkAll();
} else {
onMultipleItemAction(menuItem, new ArrayList<>(checked));
cab.finish();
clearChecked();
}
return true;
}
protected void checkAll() {
if (cabHolder != null) {
checked.clear();
for (int i = 0; i < getItemCount(); i++) {
I identifier = getIdentifier(i);
if (identifier != null) {
checked.add(identifier);
}
}
notifyDataSetChanged();
updateCab();
}
}
@Nullable
protected abstract I getIdentifier(int position);
protected String getName(I object) {
return object.toString();
}
protected boolean isChecked(I identifier) {
return checked.contains(identifier);
}
protected boolean isInQuickSelectMode() {
return cab != null && cab.isActive();
}
protected abstract void onMultipleItemAction(MenuItem menuItem, ArrayList<I> selection);
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = menuRes;
}
protected boolean toggleChecked(final int position) {
if (cabHolder != null) {
I identifier = getIdentifier(position);
if (identifier == null) {
return false;
}
if (!checked.remove(identifier)) {
checked.add(identifier);
}
notifyItemChanged(position);
updateCab();
return true;
}
return false;
}
private void clearChecked() {
checked.clear();
notifyDataSetChanged();
}
private void updateCab() {
if (cabHolder != null) {
if (cab == null || !cab.isActive()) {
cab = cabHolder.openCab(menuRes, this);
}
final int size = checked.size();
if (size <= 0) {
cab.finish();
} else if (size == 1) {
cab.setTitle(getName(checked.get(0)));
} else {
cab.setTitle(context.getString(R.string.x_selected, size));
}
}
}
}

View file

@ -1,131 +0,0 @@
package code.name.monkey.retromusic.adapter.base
import android.graphics.Color
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import androidx.activity.OnBackPressedCallback
import androidx.annotation.MenuRes
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.NumberRollViewBinding
import code.name.monkey.retromusic.views.NumberRollView
abstract class AbsMultiSelectAdapter<V : RecyclerView.ViewHolder?, I>(
open val activity: FragmentActivity, @MenuRes menuRes: Int,
) : RecyclerView.Adapter<V>(), ActionMode.Callback {
var actionMode: ActionMode? = null
private val checked: MutableList<I>
private var menuRes: Int
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val inflater = mode?.menuInflater
inflater?.inflate(menuRes, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
if (item?.itemId == R.id.action_multi_select_adapter_check_all) {
checkAll()
} else {
onMultipleItemAction(item!!, ArrayList(checked))
actionMode?.finish()
clearChecked()
}
return true
}
override fun onDestroyActionMode(mode: ActionMode?) {
clearChecked()
activity.window.statusBarColor = when {
VersionUtils.hasMarshmallow() -> Color.TRANSPARENT
else -> Color.BLACK
}
actionMode = null
onBackPressedCallback.remove()
}
private fun checkAll() {
if (actionMode != null) {
checked.clear()
for (i in 0 until itemCount) {
val identifier = getIdentifier(i)
if (identifier != null) {
checked.add(identifier)
}
}
notifyDataSetChanged()
updateCab()
}
}
protected abstract fun getIdentifier(position: Int): I?
protected abstract fun getName(model: I): String?
protected fun isChecked(identifier: I): Boolean {
return checked.contains(identifier)
}
protected val isInQuickSelectMode: Boolean
get() = actionMode != null
protected abstract fun onMultipleItemAction(menuItem: MenuItem, selection: List<I>)
protected fun setMultiSelectMenuRes(@MenuRes menuRes: Int) {
this.menuRes = menuRes
}
protected fun toggleChecked(position: Int): Boolean {
val identifier = getIdentifier(position) ?: return false
if (!checked.remove(identifier)) {
checked.add(identifier)
}
notifyItemChanged(position)
updateCab()
return true
}
private fun clearChecked() {
checked.clear()
notifyDataSetChanged()
}
private fun updateCab() {
if (actionMode == null) {
actionMode = activity.startActionMode(this)?.apply {
customView = NumberRollViewBinding.inflate(activity.layoutInflater).root
}
activity.onBackPressedDispatcher.addCallback(onBackPressedCallback)
}
val size = checked.size
when {
size <= 0 -> {
actionMode?.finish()
}
else -> {
actionMode?.customView?.findViewById<NumberRollView>(R.id.selection_mode_number)
?.setNumber(size, true)
}
}
}
init {
checked = ArrayList()
this.menuRes = menuRes
}
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (actionMode != null) {
actionMode?.finish()
remove()
}
}
}
}

View file

@ -16,13 +16,14 @@ package code.name.monkey.retromusic.adapter.base;
import android.graphics.Color; import android.graphics.Color;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
@ -42,10 +43,13 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
public ImageView image; public ImageView image;
@Nullable @Nullable
public MaterialCardView imageContainerCard; public ImageView playerImage;
@Nullable @Nullable
public FrameLayout imageContainer; public ViewGroup imageContainer;
@Nullable
public MaterialCardView imageContainerCard;
@Nullable @Nullable
public TextView imageText; public TextView imageText;
@ -57,16 +61,19 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
public View mask; public View mask;
@Nullable @Nullable
public AppCompatImageView menu; public View menu;
@Nullable @Nullable
public View paletteColorContainer; public View paletteColorContainer;
@Nullable @Nullable
public TextView text; public ImageButton playSongs;
@Nullable @Nullable
public TextView text2; public RecyclerView recyclerView;
@Nullable
public TextView text;
@Nullable @Nullable
public TextView time; public TextView time;
@ -78,20 +85,22 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
super(itemView); super(itemView);
title = itemView.findViewById(R.id.title); title = itemView.findViewById(R.id.title);
text = itemView.findViewById(R.id.text); text = itemView.findViewById(R.id.text);
text2 = itemView.findViewById(R.id.text2);
image = itemView.findViewById(R.id.image); image = itemView.findViewById(R.id.image);
playerImage = itemView.findViewById(R.id.player_image);
time = itemView.findViewById(R.id.time); time = itemView.findViewById(R.id.time);
imageText = itemView.findViewById(R.id.imageText); imageText = itemView.findViewById(R.id.imageText);
imageContainer = itemView.findViewById(R.id.imageContainer);
imageTextContainer = itemView.findViewById(R.id.imageTextContainer); imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
imageContainerCard = itemView.findViewById(R.id.imageContainerCard); imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
imageContainer = itemView.findViewById(R.id.imageContainer);
menu = itemView.findViewById(R.id.menu); menu = itemView.findViewById(R.id.menu);
dragView = itemView.findViewById(R.id.drag_view); dragView = itemView.findViewById(R.id.drag_view);
paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer); paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
recyclerView = itemView.findViewById(R.id.recycler_view);
mask = itemView.findViewById(R.id.mask); mask = itemView.findViewById(R.id.mask);
playSongs = itemView.findViewById(R.id.playSongs);
dummyContainer = itemView.findViewById(R.id.dummy_view); dummyContainer = itemView.findViewById(R.id.dummy_view);
if (imageContainerCard != null) { if (imageContainerCard != null) {
@ -101,7 +110,6 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
itemView.setOnLongClickListener(this); itemView.setOnLongClickListener(this);
} }
@Nullable
@Override @Override
public View getSwipeableContainerView() { public View getSwipeableContainerView() {
return null; return null;
@ -109,6 +117,7 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
@Override @Override
public void onClick(View v) { public void onClick(View v) {
} }
@Override @Override
@ -117,12 +126,11 @@ public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHold
} }
public void setImageTransitionName(@NonNull String transitionName) { public void setImageTransitionName(@NonNull String transitionName) {
itemView.setTransitionName(transitionName); if (imageContainerCard != null) {
/* if (imageContainerCard != null) {
imageContainerCard.setTransitionName(transitionName); imageContainerCard.setTransitionName(transitionName);
} }
if (image != null) { if (image != null) {
image.setTransitionName(transitionName); image.setTransitionName(transitionName);
}*/ }
} }
} }

View file

@ -1,65 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.adapter.playlist
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.MusicUtil
class LegacyPlaylistAdapter(
private val activity: FragmentActivity,
private var list: List<Playlist>,
private val layoutRes: Int,
private val playlistClickListener: PlaylistClickListener
) :
RecyclerView.Adapter<LegacyPlaylistAdapter.ViewHolder>() {
fun swapData(list: List<Playlist>) {
this.list = list
notifyDataSetChanged()
}
class ViewHolder(itemView: View) : code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder(itemView)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist: Playlist = list[position]
holder.title?.text = playlist.name
holder.text?.text = MusicUtil.getPlaylistInfoString(activity, playlist.getSongs())
holder.itemView.setOnClickListener {
playlistClickListener.onPlaylistClick(playlist)
}
}
override fun getItemCount(): Int {
return list.size
}
interface PlaylistClickListener {
fun onPlaylistClick(playlist: Playlist)
}
}

Some files were not shown because too many files have changed in this diff Show more