Initial commit

This commit is contained in:
bernd32 2019-12-16 00:14:48 +05:00
commit a511d42ecd
106 changed files with 6149 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
JLyrics

164
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,164 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

454
.idea/dbnavigator.xml generated Normal file
View File

@ -0,0 +1,454 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="true" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DataExportManager">
<export-instructions>
<create-header value="true" />
<quote-values-containing-separator value="true" />
<quote-all-values value="false" />
<value-separator value="" />
<file-name value="" />
<file-location value="" />
<scope value="GLOBAL" />
<destination value="FILE" />
<format value="EXCEL" />
<charset value="windows-31j" />
</export-instructions>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseFileManager">
<open-files />
</component>
<component name="DBNavigator.Project.EditorStateManager">
<last-used-providers />
</component>
<component name="DBNavigator.Project.MethodExecutionManager">
<method-browser />
<execution-history>
<group-entries value="true" />
<execution-inputs />
</execution-history>
<argument-values-cache />
</component>
<component name="DBNavigator.Project.ObjectDependencyManager">
<last-used-dependency-type value="INCOMING" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
<recently-used-interfaces />
</component>
<component name="DBNavigator.Project.Settings">
<connections />
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="NESTED TABLE" enabled="false" />
<object-type name="COLUMN" enabled="false" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE ATTRIBUTE" enabled="false" />
<object-type name="ARGUMENT" enabled="false" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="true" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
<enable-column-tooltip value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<tracking-columns>
<columnNames value="" />
<visible value="true" />
<editable value="false" />
</tracking-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-actions-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-actions-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="Properties" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="Java" enabled="true" />
<content-type name="Groovy" enabled="true" />
<content-type name="AIDL" enabled="true" />
<content-type name="YAML" enabled="true" />
<content-type name="Manifest" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
<enable-spellchecking value="true" />
<enable-reference-spellchecking value="false" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
<debugger>
<debugger-type value="ASK" />
<use-generic-runners value="true" />
</debugger>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="SYSTEM_DEFAULT" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
<component name="DBNavigator.Project.StatementExecutionManager">
<execution-variables />
</component>
</project>

16
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="resolveModulePerSourceSet" value="false" />
<option name="testRunner" value="PLATFORM" />
</GradleProjectSettings>
</option>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

12
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

BIN
JLyrics.zip Normal file

Binary file not shown.

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# jlyrics-android
An unoffical android client of j-lyrics.net

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

85
app/build.gradle Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright 2019 bernd32
*
* 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.
*/
apply plugin: 'com.android.application'
android {
android {
// Kuromoji
packagingOptions {
exclude 'META-INF/CONTRIBUTORS.md'
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/MANIFEST.MF'
exclude 'META-INF/NOTICE.md'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.bernd32.jlyrics"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//Jsoup
implementation 'org.jsoup:jsoup:1.12.1'
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
// Glide
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:glide:4.10.0'
// Material
implementation 'com.google.android.material:material:1.2.0-alpha02'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'androidx.paging:paging-runtime:2.1.0'
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
// Junit
implementation 'androidx.test.ext:junit:1.1.2-alpha02'
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
// Romaji-henkan
implementation 'io.github.bernd32:romaji-henkan:0.0.1'
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

62
app/roadmap/todo.txt Normal file
View File

@ -0,0 +1,62 @@
1. Implement detailed search
✓1.1 Prepare UI
✓1.1.1 In MainActivity add spinner (song name, artist, words from lyrics)
✓1.1.2 Add "Detailed search button"
✓1.1.3 Show hidden UI elements: song name, artist and lyrics EditText and spinner (full, partial )
✓1.2 If user entered only artist, show artist search result
✓1.2.1 Modify SearchActivity.documentHandle()
✓1.3 Implement artist page
2. UI changes
✓2.1 Add Magnifying-glass icon on the search button
✓2.2 Add FAB button which scrolls the page all the way up, and which appears after user scrolled 2nd page
✓2.3 Change app's color theme
2.4 Add an additional search button (at the bottom of detailed search form)
✓3. Handle a no results situation
4. Add options menu
✓5.1 Enable dark theme
✓5.2 Quit app
✓5.3 Contact developer
✓5.4 Add/Remove to favorites
✓5.5 Copy lyrics
✓5.6 Change font size (dialog)
✓5.7 Open favorites
✓5.7.1 Create favorites activity
5. Fix lyrics page
✓4.1 Add toolbar
✓4.2 Make page scrollable
✓4.3 Scale font size of textview with pinch zoom (https://stackoverflow.com/questions/40203241/android-custom-shape-for-zoom-buttons)
✓4.4 Add option in toolbar to translate lyrics to romaji
✓4.5 Show ProgressBar when loading lyrics
✓4.6 Hide toolbar when scrolled
✓4.7 Finish making share button
✓6. Create "Add lyrics to favorites" feature (https://codelabs.developers.google.com/codelabs/android-room-with-a-view/)
✓6.1 Delete lyric by swiping
✓6.2 Delete lyric by long tap
✓6.3 In LyricsView, detect that if current lyrics is in the database
7. Catch all possible exceptions
8. Miscellaneous
✓8.1 Fix the progress bar bug: from artist-list to song
✓8.2 Add auto-increment ID field into the sql table
✓8.3 Add app icon
✓8.4 Change default picture in CardView
✓8.5 Optimize the code
✓8.5.1 Get rid of ButterKnife dependence
✓8.6 Create random user-agent generator
✓8.7 Adapt app's color theme in WebView
9. Convert lyrics to romaji
✓9.1 Install kuromoji dependence
✓9.2 Adapt jakaroma library
✓9.3 Execute romanize() in AsyncTask
✓9.4 Switch to original
✓9.5 Hide layout and show progressBar and cancel fab
✓ - completed

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bernd32.jlyrics">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.FuriganaActivity" />
<activity android:name=".ui.ArtistSongsActivity" />
<activity
android:name=".ui.FavoritesActivity"
android:parentActivityName=".ui.LauncherActivity" />
<activity android:name=".ui.ShowLyricsActivity" />
<activity
android:name=".ui.SearchActivity"
android:parentActivityName=".ui.LauncherActivity" />
<activity android:name=".ui.LauncherActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,43 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public abstract class BaseViewHolder extends RecyclerView.ViewHolder {
private int mCurrentPosition;
protected BaseViewHolder(View itemView) {
super(itemView);
}
protected abstract void clear();
public void onBind(int position) {
mCurrentPosition = position;
clear();
}
public int getCurrentPosition() {
return mCurrentPosition;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics;
public class Lyric {
private String cardTitle;
private String cardDescription;
private String imgUrl;
private String songUrl;
private String artistUrl;
private String url;
public String getArtistUrl() {
return artistUrl;
}
public void setArtistUrl(String artistUrl) {
this.artistUrl = artistUrl;
}
public String getCardTitle() {
return cardTitle;
}
public void setCardTitle(String cardTitle) {
this.cardTitle = cardTitle;
}
public String getCardDescription() {
return cardDescription;
}
public void setCardDescription(String cardDescription) {
this.cardDescription = cardDescription;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getSongUrl() {
return songUrl;
}
public void setSongUrl(String songUrl) {
this.songUrl = songUrl;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public abstract class PaginationListener extends RecyclerView.OnScrollListener {
public static final int PAGE_START = 1;
@NonNull
private LinearLayoutManager layoutManager;
private static final int PAGE_SIZE = 20;
public PaginationListener(@NonNull LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
if (!isLoading() && !isLastPage()) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0
&& totalItemCount >= PAGE_SIZE) {
loadMoreItems();
}
}
if (dy > 0 && firstVisibleItemPosition > 5) {
showFAB();
}
if(dy < 0 && firstVisibleItemPosition < 5)
hideFAB();
}
protected abstract void showFAB();
protected abstract void hideFAB();
protected abstract void loadMoreItems();
protected abstract boolean isLastPage();
protected abstract boolean isLoading();
}

View File

@ -0,0 +1,133 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.adapters;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.jlyrics.BaseViewHolder;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.database.FavLyrics;
import com.bernd32.jlyrics.ui.SearchActivity;
import com.bernd32.jlyrics.ui.ShowLyricsActivity;
import com.bumptech.glide.Glide;
import com.google.android.material.card.MaterialCardView;
import java.util.List;
public class FavLyricsAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private static final String TAG = "PostRecyclerAdapter";
private List<FavLyrics> mPostItems;
private Context mContext;
public FavLyricsAdapter(Context context, List<FavLyrics> postItems) {
Log.d(TAG, "PostRecyclerAdapter: constructor activated");
this.mPostItems = postItems;
this.mContext = context;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem, parent, false));
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
holder.onBind(position);
}
@Override
public int getItemCount() {
return mPostItems == null ? 0 : mPostItems.size();
}
public void addItems(List<FavLyrics> postItems) {
mPostItems.addAll(postItems);
mPostItems = postItems;
notifyDataSetChanged();
}
public void clear() {
mPostItems.clear();
notifyDataSetChanged();
Glide.get(mContext).clearMemory();
}
public FavLyrics getItem(int position) {
return mPostItems.get(position);
}
public class ViewHolder extends BaseViewHolder {
TextView cardTitle;
TextView cardDescription;
ImageView image;
MaterialCardView parentLayout;
ViewHolder(View itemView) {
super(itemView);
cardTitle = itemView.findViewById(R.id.card_title);
cardDescription = itemView.findViewById(R.id.card_description);
image = itemView.findViewById(R.id.image);
parentLayout = itemView.findViewById(R.id.parent_layout);
}
protected void clear() {
}
public void onBind(int position) {
super.onBind(position);
FavLyrics item = mPostItems.get(position);
cardTitle.setText(item.getTitle());
cardDescription.setText(item.getDescription());
// If no picture found, set default picture
if (item.getImgUrl().equals(SearchActivity.NO_IMG)) {
image.setVisibility(View.GONE);
} else {
image.setVisibility(View.VISIBLE);
Glide.with(mContext)
.asBitmap()
.load(item.getImgUrl())
.into(image);
}
// click listener
parentLayout.setOnClickListener(view -> {
Intent intent = new Intent(mContext, ShowLyricsActivity.class);
intent.putExtra("song_url", item.getUrl());
intent.putExtra("title", item.getTitle());
intent.putExtra("description", item.getDescription());
intent.putExtra("img_url", item.getImgUrl());
mContext.startActivity(intent);
});
}
}
}

View File

@ -0,0 +1,199 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.adapters;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.jlyrics.BaseViewHolder;
import com.bernd32.jlyrics.Lyric;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.ui.ArtistSongsActivity;
import com.bernd32.jlyrics.ui.SearchActivity;
import com.bernd32.jlyrics.ui.ShowLyricsActivity;
import com.bumptech.glide.Glide;
import com.google.android.material.card.MaterialCardView;
import java.util.List;
public class PostRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private static final String TAG = "PostRecyclerAdapter";
private static final int VIEW_TYPE_LOADING = 0;
private static final int VIEW_TYPE_NORMAL = 1;
public static final int SONG_PAGE = 0;
public static final int ARTIST_LYRICS_PAGE = 1;
public static final int ARTISTS_PAGE = 2;
public static final int LYRICS_PAGE = 3;
private boolean isLoaderVisible = false;
private List<Lyric> mPostItems;
private Context mContext;
private int pageType;
public PostRecyclerAdapter(Context context, List<Lyric> postItems) {
Log.d(TAG, "PostRecyclerAdapter: constructor activated");
this.mPostItems = postItems;
this.mContext = context;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_NORMAL:
return new ViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_listitem, parent, false));
case VIEW_TYPE_LOADING:
return new ProgressHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false));
default:
return null;
}
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
holder.onBind(position);
}
@Override
public int getItemViewType(int position) {
if (isLoaderVisible) {
return position == mPostItems.size() - 1 ? VIEW_TYPE_LOADING : VIEW_TYPE_NORMAL;
} else {
return VIEW_TYPE_NORMAL;
}
}
@Override
public int getItemCount() {
return mPostItems == null ? 0 : mPostItems.size();
}
public void addItems(List<Lyric> postItems) {
mPostItems.addAll(postItems);
notifyDataSetChanged();
}
public void addLoading() {
isLoaderVisible = true;
mPostItems.add(new Lyric());
notifyItemInserted(mPostItems.size() - 1);
}
public void removeLoading() {
isLoaderVisible = false;
int position = mPostItems.size() - 1;
Lyric item = getItem(position);
if (item != null) {
mPostItems.remove(position);
notifyItemRemoved(position);
}
}
public void clear() {
Log.d(TAG, "clear: started");
// use it when performing new search
mPostItems.clear();
notifyDataSetChanged();
Glide.get(mContext).clearMemory();
}
private Lyric getItem(int position) {
return mPostItems.get(position);
}
public void setPageType(int pageType) {
this.pageType = pageType;
}
public class ViewHolder extends BaseViewHolder {
TextView cardTitle;
TextView cardDescription;
ImageView image;
MaterialCardView parentLayout;
ViewHolder(View itemView) {
super(itemView);
cardTitle = itemView.findViewById(R.id.card_title);
cardDescription = itemView.findViewById(R.id.card_description);
image = itemView.findViewById(R.id.image);
parentLayout = itemView.findViewById(R.id.parent_layout);
}
protected void clear() {
}
public void onBind(int position) {
super.onBind(position);
Lyric item = mPostItems.get(position);
cardTitle.setText(item.getCardTitle());
cardDescription.setText(item.getCardDescription());
Log.d(TAG, "onBind: item#"+position+" img url="+ item.getImgUrl());
// If no picture found, set default picture
if (item.getImgUrl().equals(SearchActivity.NO_IMG)) {
image.setVisibility(View.GONE);
} else {
image.setVisibility(View.VISIBLE);
Glide.with(mContext)
.asBitmap()
.load(item.getImgUrl())
.into(image);
}
// click listener
parentLayout.setOnClickListener(view -> {
Log.d(TAG, "onBind: " + pageType);
if (pageType == SONG_PAGE || pageType == ARTIST_LYRICS_PAGE) {
Intent intent = new Intent(mContext, ShowLyricsActivity.class);
intent.putExtra("song_url", item.getSongUrl());
intent.putExtra("title", item.getCardTitle());
intent.putExtra("description", item.getCardDescription());
intent.putExtra("img_url", item.getImgUrl());
mContext.startActivity(intent);
} else {
Intent intent = new Intent(mContext, ArtistSongsActivity.class);
intent.putExtra("url", item.getArtistUrl());
intent.putExtra("title", item.getCardTitle());
intent.putExtra("description", item.getCardDescription());
intent.putExtra("img_url", item.getImgUrl());
mContext.startActivity(intent);
}
});
}
}
protected class ProgressHolder extends BaseViewHolder {
ProgressHolder(View itemView) {
super(itemView);
}
@Override
protected void clear() {
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.async;
public interface AsyncTaskListener<T> {
void onPreTask();
void onPostTask(T object);
void onFailure(Exception e, int statusCode);
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.async;
import android.os.AsyncTask;
import android.util.Log;
import com.bernd32.jlyrics.utils.HelperClass;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class GetDataAsyncTask extends AsyncTask<String, Void, Document> { // <[Input_Parameter Type], [Progress_Report Type], [Result Type]>
private static final String TAG = "GetDataAsyncTask";
private Exception exception;
private int httpStatusCode;
private AsyncTaskListener<Document> asyncTaskListener;
public GetDataAsyncTask(AsyncTaskListener<Document> asyncTaskListener) {
this.asyncTaskListener = asyncTaskListener;
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPreExecute() {
asyncTaskListener.onPreTask();
super.onPreExecute();
}
@Override
protected Document doInBackground(String... params) {
String url = params[0];
Log.d(TAG, "doInBackground: loading url: " + url);
Document doc;
String userAgent = HelperClass.getUserAgent();
try {
doc = Jsoup
.connect(url)
.userAgent(userAgent)
.referrer("https://www.google.co.jp/")
.get();
} catch (Exception e) {
if (e instanceof HttpStatusException) {
httpStatusCode = ((HttpStatusException) e).getStatusCode();
}
exception = e;
Log.d(TAG, "doInBackground: " + e.getMessage());
return null;
}
return doc.outputSettings(new Document.OutputSettings().prettyPrint(false));
}
protected void onPostExecute(Document s) {
super.onPostExecute(s);
if (asyncTaskListener != null) {
if (exception == null) {
asyncTaskListener.onPostTask(s);
} else {
asyncTaskListener.onFailure(exception, httpStatusCode);
}
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.async;
import android.os.AsyncTask;
import android.util.Log;
import com.bernd32.jlyrics.utils.HelperClass;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
/**
* Used for showing furigana
*/
public class PostDataAsyncTask extends AsyncTask<String, Void, Document> { // <[Input_Parameter Type], [Progress_Report Type], [Result Type]>
private static final String TAG = "PostDataAsyncTask";
private Exception exception;
private int httpStatusCode;
private AsyncTaskListener<String> listener;
public PostDataAsyncTask(AsyncTaskListener<String> listener) {
this.listener = listener;
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPreExecute() {
super.onPreExecute();
listener.onPreTask();
}
@Override
protected Document doInBackground(String... params) {
// params[0] is a lyric text
String url;
url = "https://www.jcinfo.net/ja/tools/kana";
Log.d(TAG, "doInBackground: params="+params[0]);
Document doc;
String userAgent = HelperClass.getUserAgent();
try {
Connection.Response response = Jsoup
.connect(url)
.method(Connection.Method.POST)
.userAgent(userAgent)
.referrer("https://www.google.co.jp/")
.data("txt", params[0])
.execute();
doc = response.parse();
} catch (Exception e) {
if (e instanceof HttpStatusException) {
httpStatusCode = ((HttpStatusException) e).getStatusCode();
}
exception = e;
Log.d(TAG, "doInBackground: " + e.getMessage());
return null;
}
return doc;
}
protected void onPostExecute(Document s) {
String htmlString = s.select("#main-content > div.dsp2.radius_5").html();
Log.d(TAG, "onPostExecute: \n\n" + htmlString);
super.onPostExecute(s);
if (listener != null) {
if (exception == null) {
listener.onPostTask(htmlString);
} else {
listener.onFailure(exception, httpStatusCode);
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.async;
import android.os.AsyncTask;
import com.bernd32.romajihenkan.RomajiHenkan;
/**
* Converting a Japanese text to romaji.
* Since this operation is time-consuming, it should be performed in a non-UI thread
*/
public class RomanizeAsyncTask extends AsyncTask<String, Void, String> {
private AsyncTaskListener<String> listener;
public RomanizeAsyncTask(AsyncTaskListener<String> listener) {
this.listener = listener;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
listener.onPreTask();
}
@Override
protected void onPostExecute(String string) {
if (listener != null) {
listener.onPostTask(string);
}
super.onPostExecute(string);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected String doInBackground(String... strings) {
RomajiHenkan henkan = new RomajiHenkan();
return henkan.convert(strings[0]);
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.database;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "lyrics")
public class FavLyrics {
@PrimaryKey(autoGenerate = true)
private Integer id;
@NonNull
@ColumnInfo(name = "title")
private String mTitle;
@NonNull
@ColumnInfo(name = "description")
private String mDescription;
@NonNull
@ColumnInfo(name = "url")
private String mUrl;
@NonNull
@ColumnInfo(name = "img_url")
private String mImgUrl;
@ColumnInfo(name = "time_stamp")
private long mTimeStamp;
public FavLyrics(@NonNull String title, @NonNull String description,
@NonNull String url, @NonNull String imgUrl, long timeStamp) {
this.mTitle = title;
this.mDescription = description;
this.mUrl = url;
this.mImgUrl = imgUrl;
this.mTimeStamp = timeStamp;
}
@NonNull
public String getTitle() {
return this.mTitle;
}
@NonNull
public String getDescription() {
return this.mDescription;
}
@NonNull
public String getUrl() {
return this.mUrl;
}
@NonNull
public String getImgUrl() {
return this.mImgUrl;
}
public long getTimeStamp() {return this.mTimeStamp; }
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.database;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
@Dao
public interface LyricsDao {
// Notifies its active observers when the data has changed.
@Query("SELECT * from lyrics ORDER BY id DESC")
LiveData<List<FavLyrics>> getFavLyricsById();
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(FavLyrics lyrics);
@Query("DELETE FROM lyrics")
void deleteAll();
@Delete
void deleteLyrics(FavLyrics lyrics);
@Query("SELECT count(*) FROM lyrics WHERE url = :url")
int isLyricsExists(String url);
@Query("DELETE FROM lyrics WHERE url = :url")
void deleteLyricsByUrl(String url);
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.database;
import android.app.Application;
import android.os.AsyncTask;
import androidx.lifecycle.LiveData;
import java.util.List;
import java.util.concurrent.ExecutionException;
class LyricsRepository {
private LyricsDao mLyricsDao;
private LiveData<List<FavLyrics>> mAllFavLyrics;
public LyricsRepository(Application application) {
LyricsRoomDatabase db = LyricsRoomDatabase.getDatabase(application);
mLyricsDao = db.lyricsDao();
mAllFavLyrics = mLyricsDao.getFavLyricsById();
}
// Room executes all queries on a separate thread.
// Observed LiveData will notify the observer when the data has changed.
public LiveData<List<FavLyrics>> getAllLyrics() {
return mAllFavLyrics;
}
// Must call this on a non-UI thread
public void insert(FavLyrics lyrics) {
LyricsRoomDatabase.databaseWriteExecutor.execute(() -> {
mLyricsDao.insert(lyrics);
});
}
public void deleteAll() {
new DeleteAllLyricsAsyncTask(mLyricsDao).execute();
}
// Need to run off main thread
public void deleteLyrics(FavLyrics lyrics) {
new DeleteLyricsAsyncTask(mLyricsDao).execute(lyrics);
}
public boolean isLyricsExists(String url) throws ExecutionException, InterruptedException {
new SearchLyricsAsyncTask(mLyricsDao).execute(url);
SearchLyricsAsyncTask searchLyricsAsyncTask = new SearchLyricsAsyncTask(mLyricsDao);
return searchLyricsAsyncTask.execute(url).get();
}
public void deleteLyricsByUrl(String url) {
new DeleteLyricsByURLAsyncTask(mLyricsDao).execute(url);
}
// Delete all favorites from the database (does not delete the table)
private static class DeleteAllLyricsAsyncTask extends AsyncTask<Void, Void, Void> {
private LyricsDao mAsyncTaskDao;
DeleteAllLyricsAsyncTask(LyricsDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(Void... voids) {
mAsyncTaskDao.deleteAll();
return null;
}
}
// Delete a single favorite from the database.
private static class DeleteLyricsAsyncTask extends AsyncTask<FavLyrics, Void, Void> {
private LyricsDao mAsyncTaskDao;
DeleteLyricsAsyncTask(LyricsDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final FavLyrics... params) {
mAsyncTaskDao.deleteLyrics(params[0]);
return null;
}
}
private static class SearchLyricsAsyncTask extends AsyncTask<String, Void, Boolean> {
private LyricsDao mAsyncTaskDao;
SearchLyricsAsyncTask(LyricsDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Boolean doInBackground(final String... params) {
int count = mAsyncTaskDao.isLyricsExists(params[0]);
return count != 0;
}
}
private static class DeleteLyricsByURLAsyncTask extends AsyncTask<String, Void, Void> {
private LyricsDao mAsyncTaskDao;
DeleteLyricsByURLAsyncTask(LyricsDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final String... params) {
mAsyncTaskDao.deleteLyricsByUrl(params[0]);
return null;
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = {FavLyrics.class}, version = 3, exportSchema = false)
public abstract class LyricsRoomDatabase extends RoomDatabase {
abstract LyricsDao lyricsDao();
// marking the instance as volatile to ensure atomic access to the variable
private static volatile LyricsRoomDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
static LyricsRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (LyricsRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
LyricsRoomDatabase.class, "lyrics_database")
.fallbackToDestructiveMigration()
.addCallback(sRoomDatabaseCallback)
.build();
}
}
}
return INSTANCE;
}
/**
* Override the onOpen method to populate the database.
* For this sample, we clear the database every time it is created or opened.
*
* If you want to populate the database only when the database is created for the 1st time,
* override RoomDatabase.Callback()#onCreate
*/
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
};
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.database;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;
import java.util.concurrent.ExecutionException;
/**
* View Model to keep a reference to the lyrics repository and
* an up-to-date list of all words.
*/
public class LyricsViewModel extends AndroidViewModel {
private LyricsRepository mRepository;
// Using LiveData and caching what getFavLyricsById returns has several benefits:
// - We can put an observer on the data (instead of polling for changes) and only update the
// the UI when the data actually changes.
// - Repository is completely separated from the UI through the ViewModel.
private LiveData<List<FavLyrics>> mAllLyrics;
public LyricsViewModel(Application application) {
super(application);
mRepository = new LyricsRepository(application);
mAllLyrics = mRepository.getAllLyrics();
}
public LiveData<List<FavLyrics>> getAllLyrics() {
return mAllLyrics;
}
public void insert(FavLyrics lyrics) {
mRepository.insert(lyrics);
}
public void deleteAll() {
mRepository.deleteAll();
}
public void deleteLyrics(FavLyrics lyrics) {
mRepository.deleteLyrics(lyrics);
}
public boolean isLyricsExists(String url) throws ExecutionException, InterruptedException {
return mRepository.isLyricsExists(url);
}
public void deleteLyricsByUrl(String url) {
mRepository.deleteLyricsByUrl(url);
}
}

View File

@ -0,0 +1,17 @@
package com.bernd32.jlyrics.search;
import com.bernd32.jlyrics.Lyric;
import java.util.ArrayList;
public interface CallbackInterface {
void showAlertDialog();
void setActivityTitle(int numberOfSongs);
void setPageType(int pageType);
void getSearchResults(ArrayList<Lyric> items);
void taskStarted();
void taskFinished();
void taskFailed(Exception e, int statusCode);
}

View File

@ -0,0 +1,230 @@
package com.bernd32.jlyrics.search;
import android.app.Application;
import android.net.Uri;
import android.util.Log;
import com.bernd32.jlyrics.Lyric;
import com.bernd32.jlyrics.adapters.PostRecyclerAdapter;
import com.bernd32.jlyrics.async.AsyncTaskListener;
import com.bernd32.jlyrics.async.GetDataAsyncTask;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import static com.bernd32.jlyrics.adapters.PostRecyclerAdapter.ARTISTS_PAGE;
import static com.bernd32.jlyrics.adapters.PostRecyclerAdapter.SONG_PAGE;
import static com.bernd32.jlyrics.ui.SearchActivity.NO_IMG;
class SearchLyricsRepository {
private static final String TAG = "SearchLyricsRepository";
private CallbackInterface listener;
private int pageType;
private int maxPage;
SearchLyricsRepository(Application application) {
}
void newSearchRequest(String url) {
getDocumentAsyncTask(url);
}
private ArrayList<Lyric> artistSearch(Document doc) {
// Handles artist search result
final ArrayList<Lyric> items = new ArrayList<>();
Elements imgElements = doc.select("#mnb > div.bdy");
Elements artists = doc.select("#mnb > div > p.mid > a");
Elements urls = doc.select("#mnb > div > p.mid > a");
Elements lyricsNumber = doc.select("#mnb > div > p.sml:contains(収録数:)");
int maxPages = 1;
// Show alert dialog if nothing found
if (artists.size() == 0) {
listener.showAlertDialog();
} else {
for (int i = 0; i < artists.size(); i++) {
Lyric postItem = new Lyric();
String getArtist = artists.get(i).text();
String getNumberOfSongs = lyricsNumber.get(i).text()
.replace("収録数:", "Lyrics: ")
.replace("", "");
String imgSrc = imgElements.get(i).select("a > img").attr("src");
if (!imgSrc.isEmpty()) {
postItem.setImgUrl(imgSrc);
} else {
postItem.setImgUrl(NO_IMG);
}
String artistUrl = urls.get(i).attr("href");
postItem.setCardTitle(getArtist);
postItem.setCardDescription(getNumberOfSongs);
postItem.setArtistUrl(artistUrl);
items.add(postItem);
}
listener.setActivityTitle(artists.size());
setMaxPage(maxPage);
}
listener.getSearchResults(items);
return items;
}
private ArrayList<Lyric> songSearch(Document doc) {
Log.d(TAG, "songSearch: started");
final ArrayList<Lyric> items = new ArrayList<>();
maxPage = 1;
Elements imgElements = doc.select("#mnb > div.bdy");
Elements urls = doc.select("#mnb > div > p.mid > a");
Elements songNames = doc.select("#mnb > div.bdy > p.mid");
Elements artists = doc.select("#mnb > div.bdy > p.sml:contains(歌:)");
Element pageUrlElement = doc.select("#pager > a").first();
// Show alert dialog if nothing found
if (artists.size() == 0) {
listener.showAlertDialog();
} else {
for (int i = 0; i < artists.size(); i++) {
Lyric lyric = new Lyric();
String imgSrc = imgElements.get(i).select("a > img").attr("src");
if (!imgSrc.isEmpty()) {
lyric.setImgUrl(imgSrc);
} else {
lyric.setImgUrl(NO_IMG);
}
String url = urls.get(i).attr("href");
lyric.setSongUrl(url);
lyric.setCardTitle(songNames.get(i).text());
lyric.setCardDescription(artists.get(i).text().replace("歌:", ""));
items.add(lyric);
maxPage = numberOfPages(getNumberOfSongsFound(pageUrlElement));
setMaxPage(maxPage);
}
listener.setActivityTitle(getNumberOfSongsFound(pageUrlElement));
}
listener.getSearchResults(items);
return items;
}
private int getPageType(Document doc) {
String description = doc.select("meta[name=description]").first().attr("content");
Elements lyricsNumber = doc.select("#mnb > div > p.sml:contains(収録数:)");
// keywords for detecting a page type
final String search_result_keyword = "検索結果ページ";
final String lyrics_page_keyword = "歌詞ページ";
final String artist_list_keyword = "歌詞一覧ページ";
final String artist_search_result_keyword = "歌手名に";
if (description.contains(search_result_keyword) && lyricsNumber.isEmpty()) {
// Song search result (has pages)
Log.d(TAG, "getPageType: Search result page detected");
pageType = PostRecyclerAdapter.SONG_PAGE;
//adapter.setPageType(pageType);
listener.setPageType(pageType);
return pageType;
} else if (description.contains(lyrics_page_keyword)) {
Log.d(TAG, "getPageType: Lyrics page detected");
pageType = PostRecyclerAdapter.LYRICS_PAGE;
//adapter.setPageType(pageType);
return pageType;
} else if (description.contains(artist_list_keyword)) {
Log.d(TAG, "getPageType: Artist list page detected");
pageType = PostRecyclerAdapter.ARTIST_LYRICS_PAGE;
listener.setPageType(pageType);
return pageType;
} else if(description.contains(artist_search_result_keyword) && !lyricsNumber.isEmpty()) {
Log.d(TAG, "getPageType: Artist search result page detected");
pageType = PostRecyclerAdapter.ARTISTS_PAGE;
listener.setPageType(pageType);
return pageType;
} else {
Log.d(TAG, "getPageType: cannot detect page type");
return PostRecyclerAdapter.SONG_PAGE;
}
}
private void getDocumentAsyncTask(String url) {
Log.d(TAG, "startAsyncTask: " + url);
GetDataAsyncTask task = new GetDataAsyncTask(new AsyncTaskListener<Document>() {
@Override
public void onPreTask() {
listener.taskStarted();
}
@Override
public void onPostTask(Document doc) {
pageType = getPageType(doc);
// Get lyrics from a song search type
if (pageType == SONG_PAGE) {
songSearch(doc);
} else if (pageType == ARTISTS_PAGE) {
artistSearch(doc);
} else {
artistSongList(doc);
}
listener.taskFinished();
}
@Override
public void onFailure(Exception e, int statusCode) {
listener.taskFailed(e, statusCode);
}
});
task.execute(url);
}
private ArrayList<Lyric> artistSongList(Document doc) {
// Handles artist's list of lyrics
final ArrayList<Lyric> items = new ArrayList<>();
Elements imgElements = doc.select("#mnb > div.cnt > div[id^=ly]");
Elements titles = doc.select("#mnb > div.cnt > div[id^=ly] > p.ttl > a");
String description = doc.selectFirst("#mnb > div.cnt > div.cap > h2").text();
for (int i = 0; i < titles.size(); i++) {
Lyric postItem = new Lyric();
String imgSrc = imgElements.get(i).select("a > img.i5r").attr("src");
if (!imgSrc.isEmpty()) {
Log.d(TAG, i + " = " + imgSrc);
postItem.setImgUrl(imgSrc);
} else {
Log.d(TAG, "artistDocumentHandle: no image found");
postItem.setImgUrl(NO_IMG);
}
String title = titles.get(i).text();
String url = titles.get(i).attr("href");
postItem.setCardTitle(title);
postItem.setCardDescription(description.replace("の歌詞リスト", "")); // leave blank for now
postItem.setSongUrl("http://j-lyric.net" + url);
Log.d(TAG, "documentHandler: url = http://j-lyric.net" + url);
items.add(postItem);
}
listener.setActivityTitle(titles.size());
listener.getSearchResults(items);
return items;
}
private int getNumberOfSongsFound(Element pageUrlElement) {
String url = pageUrlElement.attr("href");
Uri uri = Uri.parse(url);
String c = uri.getQueryParameter("c");
assert c != null;
return Integer.valueOf(c);
}
private int numberOfPages(int songs) {
double p = Math.ceil((double) songs/20);
return (int) p;
}
public void addListener(CallbackInterface listener) {
this.listener = listener;
}
public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
public int getMaxPage() {
return maxPage;
}
}

View File

@ -0,0 +1,31 @@
package com.bernd32.jlyrics.search;
import android.app.Application;
import android.util.Log;
import androidx.lifecycle.AndroidViewModel;
public class SearchLyricsViewModel extends AndroidViewModel {
private SearchLyricsRepository mRepository;
private static final String TAG = "SearchLyricsViewModel";
public SearchLyricsViewModel(Application application) {
super(application);
mRepository = new SearchLyricsRepository(application);
}
public void newSearchRequest(String url) {
Log.d(TAG, "newSearchRequest: " + url);
mRepository.newSearchRequest(url);
}
public void addListener(CallbackInterface callback) {
mRepository.addListener(callback);
}
public int getMaxPage() {
Log.d(TAG, "getMaxPage: " + mRepository.getMaxPage());
return mRepository.getMaxPage();
}
}

View File

@ -0,0 +1,174 @@
package com.bernd32.jlyrics.ui;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.jlyrics.Lyric;
import com.bernd32.jlyrics.PaginationListener;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.adapters.PostRecyclerAdapter;
import com.bernd32.jlyrics.async.GetDataAsyncTask;
import com.bernd32.jlyrics.search.CallbackInterface;
import com.bernd32.jlyrics.search.SearchLyricsViewModel;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
public class ArtistSongsActivity extends AppCompatActivity {
private static final String TAG = "SearchActivity";
private static final String NO_IMG = "no_img";
@SuppressWarnings("WeakerAccess")
public RecyclerView mRecyclerView;
private ProgressBar progressBar;
private PostRecyclerAdapter adapter;
private String url;
private FloatingActionButton floatingActionButton;
private ExtendedFloatingActionButton cancelFAB;
private GetDataAsyncTask task;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: started");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
cancelFAB = findViewById(R.id.load_cancel);
progressBar = findViewById(R.id.progress_bar);
mRecyclerView = findViewById(R.id.recyclerv_view);
floatingActionButton = findViewById(R.id.floating_action_button);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
Bundle extras = getIntent().getExtras();
if (extras != null) {
loadIntents();
}
mRecyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
adapter = new PostRecyclerAdapter(this, new ArrayList<>());
mRecyclerView.setAdapter(adapter);
SearchLyricsViewModel viewModel = new ViewModelProvider(this).get(SearchLyricsViewModel.class);
// show progressbar only in initial loading
CallbackInterface listener = new CallbackInterface() {
@Override
public void showAlertDialog() {
}
@Override
public void setActivityTitle(int numberOfSongs) {
ArtistSongsActivity.this.setTitle(getString(R.string.title_found) +
" " + numberOfSongs);
}
@Override
public void setPageType(int pageType) {
}
@Override
public void getSearchResults(ArrayList<Lyric> items) {
adapter.addItems(items);
}
@Override
public void taskStarted() {
Log.d(TAG, "onPreTask: started");
// show progressbar only in initial loading
progressBar.setVisibility(View.VISIBLE);
cancelFAB.setVisibility(View.VISIBLE);
}
@Override
public void taskFinished() {
Log.d(TAG, "onPostTask: started");
progressBar.setVisibility(View.GONE);
cancelFAB.setVisibility(View.GONE);
}
@Override
public void taskFailed(Exception e, int statusCode) {
Toast.makeText(ArtistSongsActivity.this,
getString(R.string.connection_failed),
Toast.LENGTH_LONG).show();
finish();
Log.d(TAG, "onFailure: " + e.toString());
}
};
viewModel.addListener(listener);
viewModel.newSearchRequest(url);
floatingActionButton.setOnClickListener(view -> {
mRecyclerView.smoothScrollToPosition(0);
});
mRecyclerView.addOnScrollListener(new PaginationListener(layoutManager) {
@Override
protected void loadMoreItems() {
floatingActionButton.setVisibility(View.VISIBLE);
}
@Override
public boolean isLastPage() {
return true;
}
@Override
public boolean isLoading() {
return false;
}
@Override
protected void showFAB() {
if(floatingActionButton.getVisibility() != View.VISIBLE) {
floatingActionButton.show();
}
}
@Override
protected void hideFAB() {
if(floatingActionButton.getVisibility() == View.VISIBLE) {
floatingActionButton.hide();
}
}
});
}
@Override
public boolean onSupportNavigateUp(){
finish();
return true;
}
public void onLoadCancel(View view) {
if (task != null)
task.cancel(true);
finish();
}
private void loadIntents() {
Intent intent = getIntent();
String title = intent.getStringExtra("title");
String description = intent.getStringExtra("description");
url = intent.getStringExtra("url");
String imgUrl = intent.getStringExtra("img_url");
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.bernd32.jlyrics.R;
import java.util.Objects;
class ChangeFontDialogFragment extends DialogFragment {
private static final String TAG = "ChangeFontDialogFragmen";
private OnDialogButtonClick listener;
public void setOnDialogButtonClick(OnDialogButtonClick buttonClick) {
this.listener = buttonClick;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// Instantiate the NoticeDialogListener so we can send events to the host
listener = (OnDialogButtonClick) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: started");
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: started");
return inflater.inflate(R.layout.dialog_change_fontsize, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Log.d(TAG, "onViewCreated: started");
super.onViewCreated(view, savedInstanceState);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Log.d(TAG, "onCreateDialog: started");
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getActivity()));
// Get the layout inflater
LayoutInflater inflater = requireActivity().getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
@SuppressLint("InflateParams") View content = inflater.inflate(R.layout.dialog_change_fontsize, null);
builder.setView(content);
final TextView fontSizeTV = content.findViewById(R.id.font_size_textview);
final SeekBar seekBar = content.findViewById(R.id.seek_bar);
TextView lyricsTV = ((ShowLyricsActivity) getActivity()).lyricsTV;
// Convert received font size from px to sp metrics
float px = lyricsTV.getTextSize();
float sp = px / getResources().getDisplayMetrics().scaledDensity;
seekBar.setProgress((int) sp);
fontSizeTV.setText(String.valueOf((int) sp));
builder.setMessage(R.string.change_font)
.setPositiveButton(R.string.ok, (dialog, id) -> {
listener.onOkClicked(ChangeFontDialogFragment.this);
// Send data (seekBar.getProgress()) through the interface
// (OnDialogButtonClick.changeFontSize()) back to the activity (ShowLyricsActivty)
listener.changeFontSize(seekBar.getProgress());
})
.setNegativeButton(R.string.cancel, (dialog, id) -> {
// Send the negative button event back to the host activity
listener.onCancelClicked(ChangeFontDialogFragment.this);
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
@SuppressLint("SetTextI18n")
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
fontSizeTV.setText(" " + progress);
}
});
// Create the AlertDialog object and return it
return builder.create();
}
public interface OnDialogButtonClick {
void onOkClicked(DialogFragment dialog);
void onCancelClicked(DialogFragment dialog);
void changeFontSize(int i);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.adapters.FavLyricsAdapter;
import com.bernd32.jlyrics.database.FavLyrics;
import com.bernd32.jlyrics.database.LyricsViewModel;
import java.util.ArrayList;
import java.util.List;
public class FavoritesActivity extends AppCompatActivity {
private static final String TAG = "FavoritesActivity";
private LyricsViewModel lyricsViewModel;
@SuppressWarnings("WeakerAccess")
public RecyclerView recyclerView; // must not be private or static
private FavLyricsAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: has been called");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_favorites);
recyclerView = findViewById(R.id.recycler_view_favs);
TextView emptyMsg = findViewById(R.id.empty_message);
// Find the toolbar view inside the activity layout
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new FavLyricsAdapter(this, new ArrayList<>());
recyclerView.setAdapter(adapter);
lyricsViewModel = new ViewModelProvider(this).get(LyricsViewModel.class);
// Get all the lyrics from the database
// and associate them with the adapter
lyricsViewModel.getAllLyrics().observe(this, (List<FavLyrics> lyrics) -> {
// Update the cached copy of the lyrics in the adapter.
adapter.addItems(lyrics);
// Show a message if the list is empty
emptyMsg.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
});
deleteFavoriteBySwipe();
FavoritesActivity.this.setTitle(getString(R.string.favs));
}
private void deleteFavoriteBySwipe() {
ItemTouchHelper helper = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
// Delete an item from the database.
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
FavLyrics myLyrics = adapter.getItem(position);
Toast.makeText(FavoritesActivity.this,
getString(R.string.delete_lyrics_preamble), Toast.LENGTH_LONG).show();
lyricsViewModel.deleteLyrics(myLyrics);
}
});
// Attach the item touch helper to the recycler view
helper.attachToRecyclerView(recyclerView);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_favorites, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.delete_all) {
lyricsViewModel.deleteAll();
adapter.clear();
Toast.makeText(this, "Cleared", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.async.AsyncTaskListener;
import com.bernd32.jlyrics.async.PostDataAsyncTask;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
public class FuriganaActivity extends AppCompatActivity {
private static final String TAG = "FuriganaActivity";
private ProgressBar progressBar;
private ExtendedFloatingActionButton cancelFAB;
private PostDataAsyncTask task;
private String savedHtmlString = "";
private boolean darkTheme;
private float fontSize;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_furigana);
progressBar = findViewById(R.id.progress_bar);
cancelFAB = findViewById(R.id.load_cancel);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
Intent intent = getIntent();
String lyrics = intent.getStringExtra("lyrics_text");
darkTheme = intent.getBooleanExtra("theme", false);
fontSize = intent.getFloatExtra("font_size", 20);
// Load lyrics from savedInstanceState, so we don't have to call startAsyncTask
// if activity is stopped or destroyed
if (savedInstanceState == null) {
startAsyncTask(lyrics);
} else {
savedHtmlString = savedInstanceState.getString("saved_lyrics");
startWebView(savedHtmlString);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Log.d(TAG, "onSaveInstanceState: ");
if (!savedHtmlString.isEmpty()) {
outState.putString("saved_lyrics", savedHtmlString);
}
super.onSaveInstanceState(outState);
}
private void startAsyncTask(String textToConvert) {
Log.d(TAG, "startAsyncTask: started");
task = new PostDataAsyncTask(new AsyncTaskListener<String>() {
@Override
public void onPreTask() {
Log.d(TAG, "onPreTask: started");
progressBar.setVisibility(View.VISIBLE);
cancelFAB.setVisibility(View.VISIBLE);
}
@Override
public void onPostTask(String htmlString) {
Log.d(TAG, "onPostTask: started");
// Save a string from AsyncTask to the local variable
savedHtmlString = htmlString;
progressBar.setVisibility(View.GONE);
cancelFAB.setVisibility(View.GONE);
startWebView(htmlString);
}
@Override
public void onFailure(Exception e, int statusCode) {
Toast.makeText(FuriganaActivity.this,
getString(R.string.connection_failed),
Toast.LENGTH_LONG).show();
finish();
// TODO: 24.11.2019 log Exception e and int statusCode
Log.d(TAG, "onFailure: " + e.toString());
}
});
task.execute(textToConvert);
}
public void onLoadCancel(View view) {
if (task != null)
task.cancel(true);
finish();
}
private void startWebView(String htmlString) {
WebView webView = findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setTextZoom(120);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(false);
String html = setUpHtmlString(htmlString, (int)fontSize, darkTheme);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
webView.loadData(html,"text/html", "UTF-8");
}
private String setUpHtmlString(String htmlString, int fontSize, boolean darkTheme) {
String bgColor = darkTheme ? "black":"white";
String fontColor = darkTheme ? "white":"black";
String html = String.format("<html><head><style>"
+ "span {color:%s}"
+ "</style></head><body style=\"background-color:%s\">"
+ "%s"
+ "</body></html>",fontColor, bgColor, htmlString);
Log.d(TAG, "setUpHtmlString: "+html);
return html;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d(TAG, "onCreateOptionsMenu: started");
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onSupportNavigateUp(){
if (task != null)
task.cancel(true);
finish();
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.Group;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.utils.PreferencesManager;
public class LauncherActivity extends AppCompatActivity {
private static final String TAG = "LauncherActivity";
public static final String SEARCH_QUERY_SONG = "com.bernd32.jlyrics.SEARCH_QUERY_SONG";
public static final String SPINNER_LIST = "com.bernd32.jlyrics.SPINNER_LIST";
public static final String ARTIST_INPUT = "com.bernd32.jlyrics.ARTIST_INPUT";
public static final String SONG_INPUT = "com.bernd32.jlyrics.SONG_INPUT";
public static final String LYRICS_INPUT = "com.bernd32.jlyrics.LYRICS_INPUT";
public static final String ARTIST_SPINNER = "com.bernd32.jlyrics.ARTIST_SPINNER";
public static final String SONG_SPINNER = "com.bernd32.jlyrics.SONG_SPINNER";
public static final String LYRICS_SPINNER = "com.bernd32.jlyrics.LYRICS_SPINNER";
public static final String IGNORE_MAIN_SEARCH_INPUT = "com.bernd32.jlyrics.IGNORE_MAIN_SEARCH_INPUT";
private EditText mainSearchInput;
private EditText songInput;
private EditText artistInput;
private EditText lyricsInput;
private Spinner songSpinner;
private Spinner artistSpinner;
private Spinner lyricsSpinner;
private Spinner spinner;
private Button detailedSearchButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainSearchInput = findViewById(R.id.search_input);
spinner = findViewById(R.id.spinner);
songSpinner = findViewById(R.id.song_spinner);
artistSpinner = findViewById(R.id.artist_spinner);
lyricsSpinner = findViewById(R.id.lyrics_spinner);
songInput = findViewById(R.id.song_input);
artistInput = findViewById(R.id.artist_input);
lyricsInput = findViewById(R.id.lyrics_input);
detailedSearchButton = findViewById(R.id.detailed_search_button);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.spinner_items, android.R.layout.simple_spinner_item);
ArrayAdapter<CharSequence> adapter_d = ArrayAdapter.createFromResource(this,
R.array.spinner_search_options, android.R.layout.simple_spinner_item);
ArrayAdapter<CharSequence> adapter_l = ArrayAdapter.createFromResource(this,
R.array.lyrics_spinner_options, android.R.layout.simple_spinner_item);
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter_d.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter_l.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the adapter to the spinner
spinner.setAdapter(adapter);
songSpinner.setAdapter(adapter_d);
artistSpinner.setAdapter(adapter_d);
lyricsSpinner.setAdapter(adapter_l);
// Set default values to the spinners
songSpinner.setSelection(2);
artistSpinner.setSelection(2);
lyricsSpinner.setSelection(1);
// Load theme settings from PrefManager
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_NO);
PreferencesManager.initializeInstance(this);
PreferencesManager pm = PreferencesManager.getInstance();
if (pm.getDarkThemeSelected()) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
// Get saved data and set value to the checkable menu item
MenuItem darkThemeItem = menu.findItem(R.id.dark_theme);
PreferencesManager.initializeInstance(this);
PreferencesManager pm = PreferencesManager.getInstance();
darkThemeItem.setChecked(pm.getDarkThemeSelected());
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.toolbar_show_favs:
Intent intent = new Intent(this, FavoritesActivity.class);
startActivity(intent);
return true;
case R.id.quit:
this.finishAffinity();
return true;
case R.id.contact:
sendMail(getString(R.string.email_address), getString(R.string.email_subject_question));
case R.id.dark_theme:
item.setChecked(!item.isChecked());
// Save to value
PreferencesManager.initializeInstance(this);
PreferencesManager pm = PreferencesManager.getInstance();
pm.setDarkThemeSelected(item.isChecked());
if (item.isChecked()) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void sendMail(String email, String subject) {
String[] email_address = new String[] {email};
Intent mailto = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", email, null));
mailto.putExtra(Intent.EXTRA_EMAIL, email_address);
mailto.putExtra(Intent.EXTRA_SUBJECT, subject);
startActivity(Intent.createChooser(mailto, "Send E-mail"));
}
public void onLoadLyrics(View view) {
Intent intent = new Intent(this, SearchActivity.class);
boolean ignoreMainSearchInput = true;
String mainSearchText = mainSearchInput.getText().toString().trim();
int spinnerPos = spinner.getSelectedItemPosition();
String artistText = artistInput.getText().toString().trim();
String songText = songInput.getText().toString().trim();
String lyricsText = lyricsInput.getText().toString().trim();
int artistSpinnerPos = artistSpinner.getSelectedItemPosition();
int songSpinnerPos = songSpinner.getSelectedItemPosition();
int lyricsSpinnerPos = lyricsSpinner.getSelectedItemPosition();
if (artistText.isEmpty() && songText.isEmpty() && lyricsText.isEmpty()) {
ignoreMainSearchInput = false;
}
if (!mainSearchText.isEmpty() && !ignoreMainSearchInput) {
Log.d(TAG, "onLoadLyrics: user didn't use detailed search");
intent.putExtra(SEARCH_QUERY_SONG, mainSearchText);
intent.putExtra(SPINNER_LIST, spinnerPos);
}
if (ignoreMainSearchInput){
Log.d(TAG, "onLoadLyrics: user used detailed search");
intent.putExtra(ARTIST_INPUT, artistText);
intent.putExtra(SONG_INPUT, songText);
intent.putExtra(LYRICS_INPUT, lyricsText);
intent.putExtra(ARTIST_SPINNER, artistSpinnerPos);
intent.putExtra(SONG_SPINNER, songSpinnerPos);
intent.putExtra(LYRICS_SPINNER, lyricsSpinnerPos);
}
if (intent.resolveActivity(getPackageManager()) != null &&
(!mainSearchText.isEmpty() || ignoreMainSearchInput)) {
intent.putExtra(IGNORE_MAIN_SEARCH_INPUT, ignoreMainSearchInput);
startActivity(intent);
}
}
public void onDetailedSearch(View view) {
Group group = findViewById(R.id.group);
if (group.getVisibility() == View.GONE) {
// Hide keyboard
view = this.getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
assert imm != null;
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
group.setVisibility(View.VISIBLE);
detailedSearchButton.setText(getString(R.string.detailed_search_hide));
}
else {
group.setVisibility(View.GONE);
detailedSearchButton.setText(getString(R.string.detailed_search_show));
}
}
}

View File

@ -0,0 +1,322 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.jlyrics.PaginationListener;
import com.bernd32.jlyrics.Lyric;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.adapters.PostRecyclerAdapter;
import com.bernd32.jlyrics.async.GetDataAsyncTask;
import com.bernd32.jlyrics.search.CallbackInterface;
import com.bernd32.jlyrics.search.SearchLyricsViewModel;
import com.bernd32.jlyrics.utils.PreferencesManager;
import com.bernd32.romajihenkan.RomajiHenkan;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import static com.bernd32.jlyrics.PaginationListener.PAGE_START;
import static com.bernd32.jlyrics.utils.HelperClass.urlBuilder;
public class SearchActivity extends AppCompatActivity {
private static final String TAG = "SearchActivity";
public static final String NO_IMG = "no_img";
@SuppressWarnings("WeakerAccess")
public RecyclerView mRecyclerView; // must not be private or static
private ProgressBar progressBar;
private PostRecyclerAdapter adapter;
private int currentPage = PAGE_START;
private boolean isLastPage = false;
private boolean isLoading = false;
private String searchInput;
private int spinnerPos;
private String artistText;
private String songText;
private String lyricsText;
private int artistSpinnerPos;
private int songSpinnerPos;
private int lyricsSpinnerPos;
private boolean ignoreMainSearch;
private FloatingActionButton floatingActionButton;
private ExtendedFloatingActionButton cancelFAB;
private GetDataAsyncTask task;
private SearchLyricsViewModel viewModel;
private int maxPage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get intents
Bundle extras = getIntent().getExtras();
if (extras != null) {
intentLoader();
}
// UI
setContentView(R.layout.activity_search);
progressBar = findViewById(R.id.progress_bar);
mRecyclerView = findViewById(R.id.recyclerv_view);
floatingActionButton = findViewById(R.id.floating_action_button);
cancelFAB = findViewById(R.id.load_cancel);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
mRecyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
adapter = new PostRecyclerAdapter(this, new ArrayList<>());
mRecyclerView.setAdapter(adapter);
// initial load
String url = ignoreMainSearch ?
urlBuilder(PAGE_START, artistText, artistSpinnerPos, songText,
songSpinnerPos, lyricsText, lyricsSpinnerPos) :
urlBuilder(PAGE_START, spinnerPos, searchInput);
viewModel = new ViewModelProvider(this).get(SearchLyricsViewModel.class);
CallbackInterface listener = new CallbackInterface() {
// Show alert dialog in case when no search results were found
@Override
public void showAlertDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(SearchActivity.this);
builder.setTitle(getString(R.string.not_found_title));
builder.setMessage(getString(R.string.not_found_message));
builder.setPositiveButton(R.string.ok, (dialog, id) -> {
dialog.dismiss();
finish();
});
AlertDialog dialog = builder.create();
dialog.show();
}
// Show number of search result found in the action bar
@Override
public void setActivityTitle(int numberOfSongs) {
SearchActivity.this.setTitle(getString(R.string.title_found) +
" " + numberOfSongs);
}
// Tell our adapter the search result type we got, so adapter's click listener
// will know what activity show be opened when we click on a search result
@Override
public void setPageType(int pageType) {
adapter.setPageType(pageType);
}
// This is basically the main callback method that takes the search
// result items from the view model and loads them into the UI via our recycler adapter
// This callback method also handles the pagination future, and loads new search
// result pages into our adapter
@Override
public void getSearchResults(ArrayList<Lyric> items) {
maxPage = viewModel.getMaxPage();
newPageLoader(maxPage, items);
/*
* add scroll listener while user reach in bottom load more will call
*/
mRecyclerView.addOnScrollListener(new PaginationListener(layoutManager) {
@Override
protected void loadMoreItems() {
isLoading = true;
currentPage++;
String url = ignoreMainSearch ?
urlBuilder(currentPage, artistText, artistSpinnerPos, songText,
songSpinnerPos, lyricsText, lyricsSpinnerPos) :
urlBuilder(currentPage, spinnerPos, searchInput);
floatingActionButton.setVisibility(View.VISIBLE);
// Load search results from a new page
viewModel.newSearchRequest(url);
}
@Override
public boolean isLastPage() {
return isLastPage;
}
@Override
public boolean isLoading() {
return isLoading;
}
@Override
protected void showFAB() {
if (floatingActionButton.getVisibility() != View.VISIBLE) {
floatingActionButton.show();
}
}
@Override
protected void hideFAB() {
if (floatingActionButton.getVisibility() == View.VISIBLE) {
floatingActionButton.hide();
}
}
});
}
// Showing loading animation when user started searching
@Override
public void taskStarted() {
if (currentPage == 1) {
progressBar.setVisibility(View.VISIBLE);
mRecyclerView.setVisibility(View.GONE);
cancelFAB.setVisibility(View.VISIBLE);
}
}
// Hide loading animation and show search results
@Override
public void taskFinished() {
Log.d(TAG, "onPostTask: started");
if (currentPage == 1) {
progressBar.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
cancelFAB.setVisibility(View.GONE);
}
}
// Shows a toast when we got an exception and returns back to main activity
@Override
public void taskFailed(Exception e, int statusCode) {
Toast.makeText(SearchActivity.this,
getString(R.string.connection_failed),
Toast.LENGTH_LONG).show();
finish();
// TODO: 24.11.2019 log Exception e and int statusCode
Log.e(TAG, "onFailure: " + e.toString());
}
};
floatingActionButton.setOnClickListener(view -> {
mRecyclerView.smoothScrollToPosition(0);
});
viewModel.addListener(listener);
// Send a request to parse a search results from an URL and get results back
// to activity via callback method getSearchResults()
viewModel.newSearchRequest(url);
}
private void newPageLoader(int maxPage, ArrayList<Lyric> items) {
if (currentPage != PAGE_START) {
mRecyclerView.post(() -> adapter.removeLoading());
}
mRecyclerView.post(() -> adapter.addItems(items));
// check whether is last page or not
if (currentPage < maxPage) {
mRecyclerView.post(() -> adapter.addLoading());
} else {
isLastPage = true;
}
isLoading = false;
}
@Override
public boolean onSupportNavigateUp(){
finish();
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: activated");
adapter.clear();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
// Get saved data and set value to the checkable menu item
MenuItem darkThemeItem = menu.findItem(R.id.dark_theme);
PreferencesManager.initializeInstance(this);
PreferencesManager pm = PreferencesManager.getInstance();
darkThemeItem.setChecked(pm.getDarkThemeSelected());
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.toolbar_show_favs:
// Open favorites activity
Intent intent = new Intent(this, FavoritesActivity.class);
startActivity(intent);
return true;
case R.id.quit:
this.finishAffinity();
return true;
case R.id.dark_theme:
// Switch color theme of the app (light/dark)
item.setChecked(!item.isChecked());
// Save to value
PreferencesManager.initializeInstance(this);
PreferencesManager pm = PreferencesManager.getInstance();
pm.setDarkThemeSelected(item.isChecked());
if (item.isChecked()) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void intentLoader() {
Intent intent = getIntent();
searchInput = intent.getStringExtra(LauncherActivity.SEARCH_QUERY_SONG);
spinnerPos = intent.getIntExtra(LauncherActivity.SPINNER_LIST, 0);
artistText = intent.getStringExtra(LauncherActivity.ARTIST_INPUT);
songText = intent.getStringExtra(LauncherActivity.SONG_INPUT);
lyricsText = intent.getStringExtra(LauncherActivity.LYRICS_INPUT);
artistSpinnerPos = intent.getIntExtra(LauncherActivity.ARTIST_SPINNER, 0);
songSpinnerPos = intent.getIntExtra(LauncherActivity.SONG_SPINNER, 0);
lyricsSpinnerPos = intent.getIntExtra(LauncherActivity.LYRICS_SPINNER, 0);
ignoreMainSearch = intent.getBooleanExtra(LauncherActivity.IGNORE_MAIN_SEARCH_INPUT, false);
}
public void onLoadCancel(View view) {
if (task != null) {
task.cancel(true);
task = null;
}
finish();
}
}

View File

@ -0,0 +1,383 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.ui;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.ShareActionProvider;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.bernd32.jlyrics.R;
import com.bernd32.jlyrics.async.AsyncTaskListener;
import com.bernd32.jlyrics.async.GetDataAsyncTask;
import com.bernd32.jlyrics.async.RomanizeAsyncTask;
import com.bernd32.jlyrics.database.FavLyrics;
import com.bernd32.jlyrics.database.LyricsViewModel;
import com.bernd32.jlyrics.utils.PreferencesManager;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import org.jsoup.nodes.Document;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import static com.bernd32.jlyrics.utils.HelperClass.addNewLines;
public class ShowLyricsActivity extends AppCompatActivity implements ChangeFontDialogFragment.OnDialogButtonClick {
private static final String TAG = "ShowLyricsActivity";
private ProgressBar progressBar;
public TextView lyricsTV;
private String lyrics = "";
private String romanizedLyrics = "";
private String title;
private String songUrl;
private String cardDesc;
private String cardTitle;
private String imgUrl;
private LyricsViewModel lyricsViewModel;
private boolean hasLyrics;
private ExtendedFloatingActionButton cancelFAB;
private GetDataAsyncTask task;
private boolean romanized = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: activated");
super.onCreate(savedInstanceState);
PreferencesManager.initializeInstance(this);
setContentView(R.layout.activity_show_lyrics);
lyricsTV = findViewById(R.id.lyrics);
progressBar = findViewById(R.id.progress_bar);
cancelFAB = findViewById(R.id.load_cancel);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setDisplayHomeAsUpEnabled(true);
// For saving lyrics to favs
lyricsViewModel = new ViewModelProvider(this).get(LyricsViewModel.class);
loadIntents();
try {
isLyricsInDB(songUrl);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
// Load lyrics from savedInstanceState, so we don't have to call startAsyncTask
// if activity is stopped or destroyed
loadLyrics(savedInstanceState);
}
private void loadLyrics(Bundle savedInstanceState) {
if (savedInstanceState == null) {
startAsyncTask(songUrl);
} else {
romanized = savedInstanceState.getBoolean("romanized");
lyrics = savedInstanceState.getString("saved_lyrics");
romanizedLyrics = savedInstanceState.getString("saved_romaji_lyrics");
if (!romanized) {
lyricsTV.setText(lyrics);
} else {
lyricsTV.setText(romanizedLyrics);
}
}
}
@Override
protected void onStop() {
Log.d(TAG, "onStop: started");
super.onStop();
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
Log.d(TAG, "onSaveInstanceState: start");
outState.putString("saved_lyrics", lyrics);
outState.putString("saved_romaji_lyrics", romanizedLyrics);
outState.putBoolean("romanized", romanized);
super.onSaveInstanceState(outState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_lyrics, menu);
MenuItem menuShareItem = menu.findItem(R.id.toolbar_share);
ShareActionProvider shareActionProvider = (ShareActionProvider)
MenuItemCompat.getActionProvider(menuShareItem);
// Get saved data and set values
MenuItem darkThemeItem = menu.findItem(R.id.dark_theme);
PreferencesManager pm = PreferencesManager.getInstance();
darkThemeItem.setChecked(pm.getDarkThemeSelected());
int fontSize = pm.getFontSize();
lyricsTV.setTextSize((float) fontSize);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.romanize:
convertLyrics();
return true;
case R.id.toolbar_share:
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, lyrics);
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, title);
startActivity(shareIntent);
return true;
case R.id.toolbar_furigana:
openFuriganaActivity();
return true;
case R.id.fav_add_remove:
saveLyrics();
return true;
case R.id.dark_theme:
item.setChecked(!item.isChecked());
PreferencesManager pm = PreferencesManager.getInstance();
pm.setDarkThemeSelected(item.isChecked());
if (item.isChecked()) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
return true;
case R.id.font_size:
showFontSizeDialog();
return true;
case R.id.quit:
this.finishAffinity();
return true;
case R.id.copy:
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(getString(R.string.lyrics), lyrics);
assert clipboard != null;
clipboard.setPrimaryClip(clip);
Toast.makeText(this, getString(R.string.copied), Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void convertLyrics() {
if (!romanized) {
RomanizeAsyncTask romanizeTask = new RomanizeAsyncTask(new AsyncTaskListener<String>() {
@Override
public void onPreTask() {
lyricsTV.setText("");
progressBar.setVisibility(View.VISIBLE);
cancelFAB.setVisibility(View.VISIBLE);
}
@Override
public void onPostTask(String string) {
progressBar.setVisibility(View.GONE);
cancelFAB.setVisibility(View.GONE);
ShowLyricsActivity.this.romanizedLyrics = string;
lyricsTV.setText(string);
romanized = true;
}
@Override
public void onFailure(Exception e, int statusCode) {
Toast.makeText(ShowLyricsActivity.this,
getString(R.string.connection_failed),
Toast.LENGTH_LONG).show();
finish();
// TODO: 24.11.2019 log Exception e and int statusCode
Log.d(TAG, "onFailure: " + e.toString());
}
});
romanizeTask.execute(lyrics);
} else {
// If the text is already converted to romaji then show original Japanese text
lyricsTV.setText(lyrics);
romanized = false;
}
}
private void openFuriganaActivity() {
PreferencesManager pm = PreferencesManager.getInstance();
// Create an intent and open a new activity
Intent intent = new Intent(this, FuriganaActivity.class);
intent.putExtra("lyrics_text", lyrics);
intent.putExtra("font_size", lyricsTV.getTextSize());
intent.putExtra("theme", pm.getDarkThemeSelected());
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
@Override
public void onOkClicked(DialogFragment dialog) {
Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show();
}
@Override
public void onCancelClicked(DialogFragment dialog) {
}
@Override
public void changeFontSize(int i) {
// Get data from DialogFragment interface,
// change font size of lyric text and save the value
lyricsTV.setTextSize((float) i);
PreferencesManager pm = PreferencesManager.getInstance();
pm.setFontSize(i);
}
@Override
public boolean onSupportNavigateUp(){
if (task != null)
task.cancel(true);
finish();
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (hasLyrics)
menu.findItem(R.id.fav_add_remove).setTitle(getString(R.string.favs_remove));
else
menu.findItem(R.id.fav_add_remove).setTitle(getString(R.string.favs_add));
if (romanized) {
menu.findItem(R.id.romanize).setTitle(getString(R.string.show_original));
} else {
menu.findItem(R.id.romanize).setTitle(getString(R.string.romanize));
}
return super.onPrepareOptionsMenu(menu);
}
public void onLoadCancel(View view) {
if (task != null)
task.cancel(true);
finish();
}
private void showFontSizeDialog() {
DialogFragment changeFontAlert = new ChangeFontDialogFragment();
changeFontAlert.show(getSupportFragmentManager(), "changeFontAlert");
}
private void loadIntents() {
if (getIntent().hasExtra("song_url")){
Log.d(TAG, "getIncomingIntent: found intent extras.");
songUrl = getIntent().getStringExtra("song_url");
}
imgUrl = getIntent().getStringExtra("img_url");
cardTitle = getIntent().getStringExtra("title");
cardDesc = getIntent().getStringExtra("description");
}
private void isLyricsInDB(String songUrl) throws ExecutionException, InterruptedException {
this.hasLyrics = lyricsViewModel.isLyricsExists(songUrl);
}
private void saveLyrics() {
if (!hasLyrics) {
long timestamp = System.currentTimeMillis();
FavLyrics savedLyrics = new FavLyrics(cardTitle, cardDesc, songUrl, imgUrl, timestamp);
lyricsViewModel.insert(savedLyrics);
Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show();
this.hasLyrics = true;
} else {
// Remove lyrics
lyricsViewModel.deleteLyricsByUrl(songUrl);
Toast.makeText(this, getString(R.string.removed), Toast.LENGTH_SHORT).show();
this.hasLyrics = false;
finish();
}
}
private void startAsyncTask(String url) {
Log.d(TAG, "startAsyncTask: " + url);
task = new GetDataAsyncTask(new AsyncTaskListener<Document>() {
@Override
public void onPreTask() {
Log.d(TAG, "onPreTask: started");
// show progressbar only in initial loading
progressBar.setVisibility(View.VISIBLE);
cancelFAB.setVisibility(View.VISIBLE);
}
@Override
public void onPostTask(Document doc) {
Log.d(TAG, "onPostTask: started");
progressBar.setVisibility(View.GONE);
cancelFAB.setVisibility(View.GONE);
showLyrics(doc);
}
@Override
public void onFailure(Exception e, int statusCode) {
Toast.makeText(ShowLyricsActivity.this,
getString(R.string.connection_failed),
Toast.LENGTH_LONG).show();
finish();
// TODO: 24.11.2019 log Exception e and int statusCode
}
});
task.execute(url);
}
private void showLyrics(Document doc) {
Log.d(TAG, "showLyrics: activated");
lyrics = doc.select("#Lyric").first().html();
String songName = doc.select("#mnb > div.cap > h2")
.text()
.replace(" 歌詞", "");
title = String.format(Locale.getDefault(), "「%s」\n\n\n", songName);
ShowLyricsActivity.this.setTitle(title);
lyrics = addNewLines(lyrics);
lyrics = lyrics.replace("(△くり返し)", getText(R.string.repeat));
lyrics = lyrics.replace("(※くり返し)", getText(R.string.repeat2));
SpannableString songTitle = new SpannableString(title);
songTitle.setSpan(new StyleSpan(Typeface.BOLD), 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
lyricsTV.setText(songTitle);
lyricsTV.append(lyrics);
Log.d(TAG, "Current artist: " + cardDesc);
Log.d(TAG, "Current song: " + cardTitle);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.utils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
public class HelperClass {
static final List<String> userAgents = Arrays.asList("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15G77",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36");
public static String getUserAgent() {
return userAgents.get(ThreadLocalRandom.current().nextInt((userAgents.size())));
}
public static String urlBuilder(int page, int spinnerPos, String searchInput) {
// Used for simple search
StringBuilder url = new StringBuilder("http://search.j-lyric.net/index.php?&ct=2&ca=2&cl=2&p=");
final int SONG_NAME_SELECTED = 0;
final int ARTIST_SELECTED = 1;
final int LYRICS_SELECTED = 2;
switch (spinnerPos) {
case SONG_NAME_SELECTED:
url.append(page).append("&kt=").append(searchInput);
break;
case ARTIST_SELECTED:
url.append(page).append("&ka=").append(searchInput);
break;
case LYRICS_SELECTED:
url.append(page).append("&kl=").append(searchInput);
break;
}
return url.toString();
}
public static String urlBuilder(int page, String artistText, int artistSpinnerPos, String songText,
int songSpinnerPos, String lyricsText, int lyricsSpinnerPos) {
// Used for detailed search
if (lyricsSpinnerPos > 0) lyricsSpinnerPos += 1;
String url = String.format(Locale.getDefault(),
"http://search.j-lyric.net/index.php?p=%d&kt=%s&ct=%d&ka=%s&ca=%d&kl=%s&cl=%d",
page, songText, songSpinnerPos, artistText, artistSpinnerPos, lyricsText, lyricsSpinnerPos);
return url;
}
public static String addNewLines(String html) {
// Add new lines to the fetched text
Document document = Jsoup.parse(html);
// Makes html() preserve linebreaks and spacing
document.outputSettings(new Document.OutputSettings().prettyPrint(false));
document.select("br").append("\\n");
document.select("p").prepend("\\n\\n");
String s = document.html().replaceAll("\\\\n", "\n");
return Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019 bernd32
*
* 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 com.bernd32.jlyrics.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class PreferencesManager {
/* a thread-safe singleton class to make the global access method
synchronized, so that only one thread can execute this method at a time */
private static final String APP_PREFS = "com.bernd32.jlyrics.prefs";
private static final String FONT_SIZE = "com.example.app.fontsize";
private static final String DARK_THEME = "com.example.app.darktheme";
private static PreferencesManager sInstance;
private final SharedPreferences mPref;
private PreferencesManager(Context context) {
mPref = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE);
}
public static synchronized void initializeInstance(Context context) {
if (sInstance == null) {
sInstance = new PreferencesManager(context);
}
}
public static synchronized PreferencesManager getInstance() {
if (sInstance == null) {
throw new IllegalStateException(PreferencesManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return sInstance;
}
public void setFontSize(int value) {
mPref.edit()
.putInt(FONT_SIZE, value)
.apply();
}
public int getFontSize() {
return mPref.getInt(FONT_SIZE, 20);
}
public void setDarkThemeSelected(boolean value) {
mPref.edit()
.putBoolean(DARK_THEME, value)
.apply();
}
public boolean getDarkThemeSelected() {
return mPref.getBoolean(DARK_THEME, false);
}
public void remove(String key) {
mPref.edit()
.remove(key)
.apply();
}
public boolean clear() {
return mPref.edit()
.clear()
.commit();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
</vector>

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
</vector>

View File

@ -0,0 +1,25 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
</vector>

View File

@ -0,0 +1,21 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
</vector>

View File

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:fillViewport="false">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
tools:context=".ui.LauncherActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:onClick="onLoadLyrics"
android:text="@android:string/search_go"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinner"
android:drawableStart="@drawable/baseline_search_white_24"/>
<EditText
android:id="@+id/search_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
app:layout_constraintHorizontal_weight="3"
android:hint="@string/search"
android:importantForAutofill="no"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<Spinner
android:id="@+id/spinner"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/search_input"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<Button
android:id="@+id/detailed_search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/detailed_search_show"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button"
android:onClick="onDetailedSearch" />
<EditText
android:id="@+id/artist_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="0dp"
android:hint="@string/artist"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/artist_spinner"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/artist_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detailed_search_button"
android:importantForAutofill="no" />
<Spinner
android:id="@+id/artist_spinner"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/artist_input"
app:layout_constraintTop_toBottomOf="@+id/detailed_search_button" />
<EditText
android:id="@+id/song_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="0dp"
android:hint="@string/song"
android:importantForAutofill="no"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/song_spinner"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/song_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_input" />
<Spinner
android:id="@+id/song_spinner"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/song_input"
app:layout_constraintTop_toBottomOf="@+id/artist_spinner" />
<EditText
android:id="@+id/lyrics_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="0dp"
android:hint="@string/lyrics"
android:importantForAutofill="no"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/lyrics_spinner"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/lyrics_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_input" />
<Spinner
android:id="@+id/lyrics_spinner"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/lyrics_input"
app:layout_constraintTop_toBottomOf="@+id/song_spinner" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="lyrics_input,song_spinner,song_input,artist_spinner,artist_input,lyrics_spinner" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.FavoritesActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_favs"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<TextView
android:id="@+id/empty_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/empty_message"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="italic"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
android:visibility="invisible"
tools:visibility="visible"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.FuriganaActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="313dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout"
tools:visibility="visible" />
<include
android:id="@+id/load_cancel"
layout="@layout/cancel_fab"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
app:layout_anchorGravity="bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1.0"
android:paddingBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar">
<WebView
android:id="@+id/webview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:fillViewport="false"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="24dp"
app:layout_constraintEnd_toStartOf="@+id/artist_input"
app:layout_constraintStart_toStartOf="@+id/artist_input"
tools:context=".ui.LauncherActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:icon="@drawable/baseline_search_white_24"
android:text="@string/search"
android:textColor="?android:attr/textColorPrimary"
android:onClick="onLoadLyrics"
app:layout_constraintBottom_toTopOf="@+id/detailed_search_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinner" />
<EditText
android:id="@+id/search_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:hint="@string/search"
android:importantForAutofill="no"
android:inputType="textNoSuggestions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<Spinner
android:id="@+id/spinner"
style="@style/Base.Widget.AppCompat.Spinner.Underlined"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:spinnerMode="dropdown"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search_input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detailed_search_button"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_margin="8dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:icon="@drawable/ic_filter_list_24dp"
android:onClick="onDetailedSearch"
android:text="@string/detailed_search_show"
android:textColor="?android:attr/textColorPrimary"
app:borderWidth="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
<EditText
android:id="@+id/artist_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:hint="@string/artist"
android:importantForAutofill="no"
android:inputType="textNoSuggestions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/artist_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detailed_search_button" />
<Spinner
android:id="@+id/artist_spinner"
style="@style/Base.Widget.AppCompat.Spinner.Underlined"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toTopOf="@+id/song_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_input" />
<EditText
android:id="@+id/song_input"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:hint="@string/song"
android:importantForAutofill="no"
android:inputType="textNoSuggestions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.98"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/song_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_spinner" />
<Spinner
android:id="@+id/song_spinner"
style="@style/Base.Widget.AppCompat.Spinner.Underlined"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toTopOf="@+id/lyrics_input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_input" />
<EditText
android:id="@+id/lyrics_input"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:hint="@string/lyrics"
android:importantForAutofill="no"
android:inputType="textNoSuggestions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@+id/lyrics_spinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_spinner" />
<Spinner
android:id="@+id/lyrics_spinner"
style="@style/Base.Widget.AppCompat.Spinner.Underlined"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lyrics_input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button2"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:icon="@drawable/baseline_search_white_24"
android:text="@string/search"
android:textColor="?android:attr/textColorPrimary"
android:onClick="onLoadLyrics"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lyrics_spinner" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
app:constraint_referenced_ids="lyrics_input,song_spinner,song_input,artist_spinner,artist_input,lyrics_spinner,button2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.SearchActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerv_view"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
app:layout_behavior = "@string/appbar_scrolling_view_behavior"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="30dp"
android:layout_marginEnd="248dp"
app:fabSize="normal"
android:src="@drawable/ic_keyboard_arrow_up_white_24dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible"
app:backgroundTint="@color/colorAccent"
app:tint="?android:attr/textColorPrimaryInverse"
/>
<include
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
app:layout_anchorGravity="bottom"
layout="@layout/cancel_fab"
android:id="@+id/load_cancel"
app:layout_constraintBottom_toBottomOf="@+id/recyclerv_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.ShowLyricsActivity">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar_main"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/lyrics"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:lineSpacingMultiplier="1.3"
android:textAlignment="center"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
tools:visibility="visible" />
<include
android:id="@+id/load_cancel"
layout="@layout/cancel_fab"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
app:layout_anchorGravity="bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,34 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:layout_width="wrap_content"
android:layout_height="48dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="24dp"
android:backgroundTint="@color/colorAccent"
android:text="@string/cancel"
android:textAlignment="center"
android:textColor="?android:attr/textColorPrimaryInverse"
android:visibility="invisible"
app:icon="@drawable/ic_cancel_black_24dp"
app:iconTint="?android:attr/textColorPrimaryInverse"
tools:visibility="visible"
android:onClick="onLoadCancel"
android:scaleX="0.7"
android:scaleY="0.7"
xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<SeekBar
android:max="100"
android:layout_height="wrap_content"
android:id="@+id/seek_bar"
android:layout_width="fill_parent"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginTop="74dp">
</SeekBar>
<TextView
android:layout_height="wrap_content"
android:id="@+id/font_size_textview"
android:paddingTop="15dp"
android:textAppearance="?android:attr/textAppearanceLarge"
android:layout_width="wrap_content" android:layout_alignParentTop="true"
android:layout_centerHorizontal="true">
</TextView>
</RelativeLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="?android:attr/progressBarStyle" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<com.google.android.material.card.MaterialCardView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.MaterialComponents.CardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:id="@+id/parent_layout"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/card_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Song name"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/image"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/card_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="Artist"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/card_title"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/image"
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:color/darker_gray"
android:contentDescription="@string/album_photo" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,23 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.MaterialComponents.Light" />

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/delete_all"
android:title="@string/delete_all"
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/fav_add_remove"
android:title="@string/add_favorites"
app:showAsAction="never" />
<item
android:id="@+id/romanize"
app:showAsAction="never"
android:title="@string/romanize" />
<item
android:id="@+id/toolbar_furigana"
android:icon="@drawable/baseline_g_translate_white_24"
app:showAsAction="never"
android:title="@string/translate">
</item>
<item
android:id="@+id/toolbar_share"
android:icon="@drawable/baseline_share_white_24"
app:showAsAction="never"
android:title="@string/share">
</item>
<item
android:id="@+id/copy"
app:showAsAction="never"
android:title="@string/copy">
</item>
<item
android:id="@+id/font_size"
app:showAsAction="never"
android:title="@string/font_size">
</item>
<item
android:id="@+id/dark_theme"
app:showAsAction="never"
android:title="@string/dark_theme"
android:checkable="true">
</item>
<item
android:id="@+id/quit"
app:showAsAction="never"
android:title="@string/quit">
</item>
</menu>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/toolbar_show_favs"
android:title="@string/favs"
android:icon="@drawable/favorite_border_black"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/dark_theme"
app:showAsAction="never"
android:title="@string/dark_theme"
android:checkable="true">
</item>
<item
android:id="@+id/contact"
app:showAsAction="never"
android:title="@string/contact">
</item>
<item
android:id="@+id/quit"
app:showAsAction="never"
android:title="@string/quit">
</item>
</menu>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<resources>
<color name="colorPrimary">#295d8a</color>
<color name="colorPrimaryDark">#1d4161</color>
<color name="colorAccent">#398bd4</color>
</resources>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<resources>
<color name="ic_launcher_background">#F1F1F1</color>
</resources>

View File

@ -0,0 +1,79 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<resources>
<string name="app_name">JLyrics</string>
<string name="search">Search</string>
<string name="share">Share</string>
<string name="title_found">Found:</string>
<string name="album_photo">Album photo</string>
<string name="detailed_search_hide">Hide detailed search</string>
<string name="detailed_search_show">Show detailed search</string>
<string name="artist">Artist</string>
<string name="song">Song</string>
<string name="lyrics">Lyrics</string>
<string name="ok">OK</string>
<string name="not_found_title">No result found</string>
<string name="repeat"><i>(△Repeat)</i></string>
<string name="repeat2"><i>( ※ Repeat)</i></string>
<string name="not_found_message">Suggestions:\n\n
• Make sure all words are spelled correctly\n
• Try searching in Japanese\n
• Try different search options\n
• Try using detailed search
</string>
<string name="translate">Show furigana</string>
<string name="add_favorites">Add/Remove to favorites</string>
<string name="delete_all">Delete all</string>
<string name="delete_lyrics_preamble">Removed from favorites</string>
<string name="favs_remove">Remove from favs</string>
<string name="favs_add">Add to favs</string>
<string name="favs">Favorites</string>
<string name="copy">Copy</string>
<string name="font_size">Change font size</string>
<string name="dark_theme">Night mode</string>
<string name="quit">Quit</string>
<string name="cancel">Cancel</string>
<string name="change_font">Change font size</string>
<string name="saved">Saved</string>
<string name="removed">Removed</string>
<string name="contact">Contact developer</string>
<string name="copied">Copied</string>
<string name="connection_failed">Connection failed</string>
<string name="romanize">Romanize</string>
<string name="show_original">Show original</string>
<string name="email_address">fallentides@outlook.com</string>
<string name="email_subject_question">Feedback</string>
<string name="empty_message">Favorites are empty</string>
<string-array name="spinner_items">
<item>Song name</item>
<item>Artist</item>
<item>Lyrics</item>
</string-array>
<string-array name="spinner_search_options">
<item>Starts with</item>
<item>Full match</item>
<item>In the middle</item>
<item>Ends with</item>
</string-array>
<string-array name="lyrics_spinner_options">
<item>Starts with</item>
<item>In the middle</item>
<item>Ends with</item>
</string-array>
</resources>

View File

@ -0,0 +1,27 @@
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 bernd32
~
~ 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.
-->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">j-lyric.net</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">images-amazon.com</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,192 @@
ア,a
イ,i
ウ,u
エ,e
オ,o
ー,-
ァ,xa
ィ,xi
ゥ,xu
ェ,xe
ォ,xo
カ,ka
キ,ki
ク,ku
ケ,ke
コ,ko
ガ,ga
ギ,gi
グ,gu
ゲ,ge
ゴ,go
サ,sa
シ,shi
ス,su
セ,se
ソ,so
ザ,za
ジ,ji
ズ,zu
ゼ,ze
ゾ,zo
ジャ,ja
ジュ,ju
ジェ,je
ジョ,jo
タ,ta
チ,chi
ツ,tsu
テ,te
ト,to
ダ,da
ヂ,dji
ヅ,dzu
デ,de
ド,do
ナ,na
ニ,ni
ヌ,nu
ネ,ne
,no
ハ,ha
ヒ,hi
フ,fu
ヘ,he
ホ,ho
バ,ba
ビ,bi
ブ,bu
ベ,be
ボ,bo
パ,pa
ピ,pi
プ,pu
ペ,pe
ポ,po
ヴァ,va
ヴィ,vi
ヴ,vu
ヴェ,ve
ヴォ,vo
ファ,fa
フィ,fi
フェ,fe
フォ,fo
マ,ma
ミ,mi
ム,mu
メ,me
モ,mo
ヤ,ya
ユ,yu
イェ,ye
ヨ,yo
ラ,ra
リ,ri
ル,ru
レ,re
ロ,ro
ワ,wa
ヰ,wi
ヱ,we
ヲ,wo
ン,n
ヵ,xka
ヶ,ga
ヮ,xwa
ャ,xya
ュ,xyu
ョ,xyo
キャ,kya
キィ,kyi
キュ,kyu
キェ,kye
キョ,kyo
ギャ,gya
ギィ,gyi
ギュ,gyu
ギェ,gye
ギョ,gyo
シャ,sha
シィ,syi
シュ,shu
シェ,she
ショ,sho
ジィ,jyi
チャ,cha
チィ,cyi
チュ,chu
チェ,che
チョ,cho
テャ,tha
ティ,thi
テュ,thu
テェ,the
テョ,tho
ヂャ,dya
ヂィ,dyi
ヂュ,dyu
ヂェ,dye
ヂョ,dyo
デャ,dha
ディ,dhi
デュ,dhu
デェ,dhe
デョ,dho
ニャ,nya
ニィ,nyi
ニュ,nyu
ニェ,nye
ニョ,nyo
ヒャ,hya
ヒィ,hyi
ヒュ,hyu
ヒェ,hye
ヒョ,hyo
ビャ,bya
ビィ,byi
ビュ,byu
ビェ,bye
ビョ,byo
ピャ,pya
ピィ,pyi
ピュ,pyu
ピェ,pye
ピョ,pyo
ミャ,mya
ミィ,myi
ミュ,myu
ミェ,mye
ミョ,myo
リャ,lya
リィ,lyi
リュ,lyu
リェ,lye
リョ,lyo
,-
゛,"
゜,'
、,,
。,.
,:
 ,
,@
,(
,)
,
ンア,n'a
んあ,n'a
ンイ,n'i
んい,n'i
ンウ,n'u
んう,n'u
ンエ,n'e
んえ,n'e
ンオ,n'o
んお,n'o
ンヤ,n'ya
んや,n'ya
ンユ,n'yu
んゆ,n'yu
ンヨ,n'yo
んよ,n'yo
Can't render this file because it contains an unexpected character in line 192 and column 13.

48
build.gradle Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 bernd32
*
* 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.
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ext {
roomVersion = '2.2.2'
archLifecycleVersion = '2.2.0-rc02'
}

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