First commit

This commit is contained in:
bernd32 2020-02-25 15:56:01 +05:00
commit ee3c850181
107 changed files with 4634 additions and 0 deletions

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

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>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

61
app/build.gradle Normal file
View File

@ -0,0 +1,61 @@
apply plugin: 'com.android.application'
android {
testOptions {
unitTests.returnDefaultValues = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.bernd32.weatherdemo"
minSdkVersion 24
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'
implementation 'androidx.preference:preference:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.material:material:1.2.0-alpha03'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
implementation "com.squareup.retrofit2:adapter-rxjava2:2.4.0"
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
implementation "androidx.room:room-rxjava2:2.2.3"
// GSON body parser
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
// RecyclerView
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'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.facebook.stetho:stetho:1.5.1'
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.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

View File

@ -0,0 +1,27 @@
package com.bernd32.weatherdemo;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.bernd32.weatherdemo", appContext.getPackageName());
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bernd32.weatherdemo">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
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.AddNewLocation"></activity>
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,10 @@
package com.bernd32.weatherdemo;
/**
* Constants for our API
*/
public class Constants {
public static final String BASE_URL = "http://api.openweathermap.org";
public static final String API_KEY = "8296eaaa0ef012da8f776aa0ec890817";
}

View File

@ -0,0 +1,32 @@
package com.bernd32.weatherdemo;
import com.bernd32.weatherdemo.models.forecastdata.ForecastData;
import com.bernd32.weatherdemo.models.weatherdata.WeatherData;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Here we define the Retrofit endpoints with Observable return types to use it later
* in RxJava.
*/
public interface EndpointInterface {
@GET("data/2.5/weather")
Observable<WeatherData> getCurrentConditions(@Query("units") String units,
@Query("APPID") String apiKey,
@Query("q") String city,
@Query("lang") String lang,
@Query("lat") String lat,
@Query("lon") String lon);
@GET("data/2.5/forecast")
Observable<ForecastData> getForecast(@Query("units") String units,
@Query("APPID") String apiKey,
@Query("q") String city,
@Query("lang") String lang,
@Query("lat") String lat,
@Query("lon") String lon);
}

View File

@ -0,0 +1,41 @@
package com.bernd32.weatherdemo;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.bernd32.weatherdemo.models.UserLocation;
import java.util.List;
import io.reactivex.Flowable;
/**
* This class is used for accessing the database by defining DAO methods
* https://developer.android.com/training/data-storage/room/accessing-data
*/
@Dao
public interface LocationsDao {
// Notifies its active observers when the data has changed.
@Query("SELECT * FROM locations")
Flowable<List<UserLocation>> getAllLocations();
@Query("SELECT * from locations ORDER BY id DESC")
Flowable<List<UserLocation>> getLocationsById();
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(UserLocation location);
@Query("DELETE FROM locations")
void deleteAll();
@Delete
void deleteLocation(UserLocation userLocation);
@Query("DELETE FROM locations WHERE id = :id")
void deleteLocationById(int id);
}

View File

@ -0,0 +1,56 @@
package com.bernd32.weatherdemo;
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 com.bernd32.weatherdemo.models.UserLocation;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = {UserLocation.class}, version = 2, exportSchema = false)
public abstract class LocationsRoomDatabase extends RoomDatabase {
public abstract LocationsDao locationsDao();
// marking the instance as volatile to ensure atomic access to the variable
private static volatile LocationsRoomDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService databaseWriteExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
public static LocationsRoomDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (LocationsRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
LocationsRoomDatabase.class, "locations_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 Callback sRoomDatabaseCallback = new Callback() {
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
};
}

View File

@ -0,0 +1,46 @@
package com.bernd32.weatherdemo;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import static com.bernd32.weatherdemo.Constants.BASE_URL;
/**
* This class supplies an instance of Retrofit adapter
*/
public class RetrofitAdapter {
private static Retrofit retrofit;
private static Gson gson;
public static Retrofit getInstance() {
if (retrofit == null) {
if (gson == null) {
gson = new GsonBuilder().setLenient().create();
}
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
return retrofit;
}
}

View File

@ -0,0 +1,44 @@
package com.bernd32.weatherdemo.models;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/**
* Model for Room
*/
@Entity(tableName = "locations")
public class UserLocation {
@PrimaryKey(autoGenerate = true)
private Integer id;
@NonNull
@ColumnInfo(name = "city")
private String city;
public UserLocation(@NonNull String city) {
this.city = city;
}
@NonNull
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}

View File

@ -0,0 +1,41 @@
package com.bernd32.weatherdemo.models;
/**
* Model for RecyclerView
*/
public class WeatherItem {
private int mImgResource;
private String mTitle;
private String mValue;
private String imgUrl;
public WeatherItem(int mImgResource, String mTitle, String mValue) {
this.mImgResource = mImgResource;
this.mTitle = mTitle;
this.mValue = mValue;
}
public WeatherItem(String imgUrl, String mTitle, String mValue) {
this.imgUrl = imgUrl;
this.mTitle = mTitle;
this.mValue = mValue;
}
public String getImgUrl() {
return imgUrl;
}
public int getImgResource() {
return mImgResource;
}
public String getTitle() {
return mTitle;
}
public String getValue() {
return mValue;
}
}

View File

@ -0,0 +1,54 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class City {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("coord")
@Expose
private Coord coord;
@SerializedName("country")
@Expose
private String country;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Coord getCoord() {
return coord;
}
public void setCoord(Coord coord) {
this.coord = coord;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}

View File

@ -0,0 +1,21 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Clouds {
@SerializedName("all")
@Expose
private Integer all;
public Integer getAll() {
return all;
}
public void setAll(Integer all) {
this.all = all;
}
}

View File

@ -0,0 +1,32 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Coord {
@SerializedName("lat")
@Expose
private Double lat;
@SerializedName("lon")
@Expose
private Double lon;
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
}

View File

@ -0,0 +1,65 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class ForecastData {
@SerializedName("cod")
@Expose
private String cod;
@SerializedName("message")
@Expose
private Double message;
@SerializedName("cnt")
@Expose
private Integer cnt;
@SerializedName("list")
@Expose
private java.util.List<com.bernd32.weatherdemo.models.forecastdata.List> list = null;
@SerializedName("city")
@Expose
private City city;
public String getCod() {
return cod;
}
public void setCod(String cod) {
this.cod = cod;
}
public Double getMessage() {
return message;
}
public void setMessage(Double message) {
this.message = message;
}
public Integer getCnt() {
return cnt;
}
public void setCnt(Integer cnt) {
this.cnt = cnt;
}
public java.util.List<com.bernd32.weatherdemo.models.forecastdata.List> getList() {
return list;
}
public void setList(java.util.List<com.bernd32.weatherdemo.models.forecastdata.List> list) {
this.list = list;
}
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}
}

View File

@ -0,0 +1,98 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class List {
@SerializedName("dt")
@Expose
private Integer dt;
@SerializedName("main")
@Expose
private Main main;
@SerializedName("weather")
@Expose
private java.util.List<Weather> weather = null;
@SerializedName("clouds")
@Expose
private Clouds clouds;
@SerializedName("wind")
@Expose
private Wind wind;
@SerializedName("snow")
@Expose
private Snow snow;
@SerializedName("sys")
@Expose
private Sys sys;
@SerializedName("dt_txt")
@Expose
private String dtTxt;
public Integer getDt() {
return dt;
}
public void setDt(Integer dt) {
this.dt = dt;
}
public Main getMain() {
return main;
}
public void setMain(Main main) {
this.main = main;
}
public java.util.List<Weather> getWeather() {
return weather;
}
public void setWeather(java.util.List<Weather> weather) {
this.weather = weather;
}
public Clouds getClouds() {
return clouds;
}
public void setClouds(Clouds clouds) {
this.clouds = clouds;
}
public Wind getWind() {
return wind;
}
public void setWind(Wind wind) {
this.wind = wind;
}
public Snow getSnow() {
return snow;
}
public void setSnow(Snow snow) {
this.snow = snow;
}
public Sys getSys() {
return sys;
}
public void setSys(Sys sys) {
this.sys = sys;
}
public String getDtTxt() {
return dtTxt;
}
public void setDtTxt(String dtTxt) {
this.dtTxt = dtTxt;
}
}

View File

@ -0,0 +1,95 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Main {
@SerializedName("temp")
@Expose
private Double temp;
@SerializedName("temp_min")
@Expose
private Double tempMin;
@SerializedName("temp_max")
@Expose
private Double tempMax;
@SerializedName("pressure")
@Expose
private Integer pressure;
@SerializedName("sea_level")
@Expose
private Double seaLevel;
@SerializedName("grnd_level")
@Expose
private Integer grndLevel;
@SerializedName("humidity")
@Expose
private Integer humidity;
@SerializedName("temp_kf")
@Expose
private Double tempKf;
public Double getTemp() {
return temp;
}
public void setTemp(Double temp) {
this.temp = temp;
}
public Double getTempMin() {
return tempMin;
}
public void setTempMin(Double tempMin) {
this.tempMin = tempMin;
}
public Double getTempMax() {
return tempMax;
}
public void setTempMax(Double tempMax) {
this.tempMax = tempMax;
}
public Integer getPressure() {
return pressure;
}
public void setPressure(Integer pressure) {
this.pressure = pressure;
}
public Double getSeaLevel() {
return seaLevel;
}
public void setSeaLevel(Double seaLevel) {
this.seaLevel = seaLevel;
}
public Integer getGrndLevel() {
return grndLevel;
}
public void setGrndLevel(Integer grndLevel) {
this.grndLevel = grndLevel;
}
public Integer getHumidity() {
return humidity;
}
public void setHumidity(Integer humidity) {
this.humidity = humidity;
}
public Double getTempKf() {
return tempKf;
}
}

View File

@ -0,0 +1,21 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Snow {
@SerializedName("3h")
@Expose
private Double _3h;
public Double get3h() {
return _3h;
}
public void set3h(Double _3h) {
this._3h = _3h;
}
}

View File

@ -0,0 +1,21 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Sys {
@SerializedName("pod")
@Expose
private String pod;
public String getPod() {
return pod;
}
public void setPod(String pod) {
this.pod = pod;
}
}

View File

@ -0,0 +1,54 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Weather {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("main")
@Expose
private String main;
@SerializedName("description")
@Expose
private String description;
@SerializedName("icon")
@Expose
private String icon;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMain() {
return main;
}
public void setMain(String main) {
this.main = main;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
}

View File

@ -0,0 +1,32 @@
package com.bernd32.weatherdemo.models.forecastdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Wind {
@SerializedName("speed")
@Expose
private Double speed;
@SerializedName("deg")
@Expose
private Double deg;
public Double getSpeed() {
return speed;
}
public void setSpeed(Double speed) {
this.speed = speed;
}
public Double getDeg() {
return deg;
}
public void setDeg(Double deg) {
this.deg = deg;
}
}

View File

@ -0,0 +1,21 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Clouds {
@SerializedName("all")
@Expose
private Integer all;
public Integer getAll() {
return all;
}
public void setAll(Integer all) {
this.all = all;
}
}

View File

@ -0,0 +1,32 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Coord {
@SerializedName("lon")
@Expose
private Double lon;
@SerializedName("lat")
@Expose
private Double lat;
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
}

View File

@ -0,0 +1,76 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Main {
@SerializedName("temp")
@Expose
private Double temp;
@SerializedName("feels_like")
@Expose
private Double feelsLike;
@SerializedName("temp_min")
@Expose
private Double tempMin;
@SerializedName("temp_max")
@Expose
private Double tempMax;
@SerializedName("pressure")
@Expose
private Integer pressure;
@SerializedName("humidity")
@Expose
private Integer humidity;
public Double getTemp() {
return temp;
}
public void setTemp(Double temp) {
this.temp = temp;
}
public Double getFeelsLike() {
return feelsLike;
}
public void setFeelsLike(Double feelsLike) {
this.feelsLike = feelsLike;
}
public Double getTempMin() {
return tempMin;
}
public void setTempMin(Double tempMin) {
this.tempMin = tempMin;
}
public Double getTempMax() {
return tempMax;
}
public void setTempMax(Double tempMax) {
this.tempMax = tempMax;
}
public Integer getPressure() {
return pressure;
}
public void setPressure(Integer pressure) {
this.pressure = pressure;
}
public Integer getHumidity() {
return humidity;
}
public void setHumidity(Integer humidity) {
this.humidity = humidity;
}
}

View File

@ -0,0 +1,76 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Sys {
@SerializedName("type")
@Expose
private Integer type;
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("message")
@Expose
private Double message;
@SerializedName("country")
@Expose
private String country;
@SerializedName("sunrise")
@Expose
private Integer sunrise;
@SerializedName("sunset")
@Expose
private Integer sunset;
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getMessage() {
return message;
}
public void setMessage(Double message) {
this.message = message;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Integer getSunrise() {
return sunrise;
}
public void setSunrise(Integer sunrise) {
this.sunrise = sunrise;
}
public Integer getSunset() {
return sunset;
}
public void setSunset(Integer sunset) {
this.sunset = sunset;
}
}

View File

@ -0,0 +1,54 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Weather {
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("main")
@Expose
private String main;
@SerializedName("description")
@Expose
private String description;
@SerializedName("icon")
@Expose
private String icon;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getMain() {
return main;
}
public void setMain(String main) {
this.main = main;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
}

View File

@ -0,0 +1,144 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class WeatherData {
@SerializedName("coord")
@Expose
private Coord coord;
@SerializedName("weather")
@Expose
private List<Weather> weather = null;
@SerializedName("base")
@Expose
private String base;
@SerializedName("main")
@Expose
private Main main;
@SerializedName("wind")
@Expose
private Wind wind;
@SerializedName("clouds")
@Expose
private Clouds clouds;
@SerializedName("dt")
@Expose
private Integer dt;
@SerializedName("sys")
@Expose
private Sys sys;
@SerializedName("timezone")
@Expose
private Integer timezone;
@SerializedName("id")
@Expose
private Integer id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("cod")
@Expose
private Integer cod;
public Coord getCoord() {
return coord;
}
public void setCoord(Coord coord) {
this.coord = coord;
}
public List<Weather> getWeather() {
return weather;
}
public void setWeather(List<Weather> weather) {
this.weather = weather;
}
public String getBase() {
return base;
}
public void setBase(String base) {
this.base = base;
}
public Main getMain() {
return main;
}
public void setMain(Main main) {
this.main = main;
}
public Wind getWind() {
return wind;
}
public void setWind(Wind wind) {
this.wind = wind;
}
public Clouds getClouds() {
return clouds;
}
public void setClouds(Clouds clouds) {
this.clouds = clouds;
}
public Integer getDt() {
return dt;
}
public void setDt(Integer dt) {
this.dt = dt;
}
public Sys getSys() {
return sys;
}
public void setSys(Sys sys) {
this.sys = sys;
}
public Integer getTimezone() {
return timezone;
}
public void setTimezone(Integer timezone) {
this.timezone = timezone;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCod() {
return cod;
}
public void setCod(Integer cod) {
this.cod = cod;
}
}

View File

@ -0,0 +1,32 @@
package com.bernd32.weatherdemo.models.weatherdata;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Wind {
@SerializedName("speed")
@Expose
private Double speed;
@SerializedName("deg")
@Expose
private Double deg;
public Double getSpeed() {
return speed;
}
public void setSpeed(Double speed) {
this.speed = speed;
}
public Double getDeg() {
return deg;
}
public void setDeg(Double deg) {
this.deg = deg;
}
}

View File

@ -0,0 +1,116 @@
package com.bernd32.weatherdemo.presenter;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import com.bernd32.weatherdemo.EndpointInterface;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.RetrofitAdapter;
import com.bernd32.weatherdemo.models.weatherdata.WeatherData;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Retrofit;
import static com.bernd32.weatherdemo.Constants.API_KEY;
/**
* Here we get current conditions information by calling our weather API,
* and then update our UI via the callback interface
*/
public class CurrentConditionsPresenter {
private View mView;
private Context mContext;
private static final String TAG = "CurrentConditionsPresenter";
public CurrentConditionsPresenter(View view, Context context) {
mView = view;
mContext = context;
}
public void getCurrentConditions(String latitude, String longitude, String city) {
Log.d(TAG, "getCurrentConditions: started");
Log.d(TAG, "getCurrentConditions: lat/long=" + latitude + "/"+ longitude);
Log.d(TAG, "getCurrentConditions: city=" + city);
Retrofit retrofit = RetrofitAdapter.getInstance();
EndpointInterface apiService = retrofit.create(EndpointInterface.class);
// Format latitude and longitude values
/* String formattedLat = new DecimalFormat("###.###").format(latitude);
String formattedLon = new DecimalFormat("###.###").format(longitude);*/
// Get the observable Weather object
apiService.getCurrentConditions("metric", API_KEY, city,
Locale.getDefault().getLanguage(), latitude, longitude)
.throttleFirst(10, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(__ -> mView.showProgressBar(true))
.doOnTerminate(() -> mView.showProgressBar(false))
.subscribe(new Observer<WeatherData>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(WeatherData weatherData) {
String location = String.format("%s, %s",
weatherData.getName(), weatherData.getSys().getCountry());
String url = String.format("http://openweathermap.org/img/wn/%s@2x.png",
weatherData.getWeather().get(0).getIcon());
String temperature = String.format(Locale.getDefault(), "%d%s",
Math.round(weatherData.getMain().getTemp()), "°C");
String pressure = String.format(Locale.getDefault(), "%d %s",
weatherData.getMain().getPressure(),
mContext.getString(R.string.pressure_unit));
String humidity = String.format(Locale.getDefault(), "%d %%",
weatherData.getMain().getHumidity());
String wind = String.format(Locale.getDefault(), "%.2f %s",
weatherData.getWind().getSpeed(),
mContext.getString(R.string.meters_per_second));
mView.updateTemperature(temperature);
mView.updateLocation(location);
mView.updateWeatherText(weatherData.getWeather().get(0).getDescription());
mView.updateIcon(url);
mView.updateDetails(pressure, wind, humidity);
}
@Override
public void onError(Throwable t) {
Log.e(TAG, "onError: ", t.fillInStackTrace());
Log.e(TAG, "onError: ", t.getCause());
mView.showError(t);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
});
}
public interface View {
void updateTemperature(String temp);
void updateLocation(String location);
void updateWeatherText(String weatherText);
void updateIcon(String url);
void updateDetails(String pressure, String wind, String humidity);
void showProgressBar(boolean show);
void showError(Throwable t);
}
}

View File

@ -0,0 +1,173 @@
package com.bernd32.weatherdemo.presenter;
import android.util.Log;
import com.bernd32.weatherdemo.EndpointInterface;
import com.bernd32.weatherdemo.RetrofitAdapter;
import com.bernd32.weatherdemo.models.forecastdata.ForecastData;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Retrofit;
import static com.bernd32.weatherdemo.Constants.API_KEY;
/**
* Here we get current conditions information by calling our weather API,
* and then update our UI via the callback interface. We also performing
* a lot of formatting to make everything look clean :)
*/
public class ForecastPresenter {
private static final String TAG = "ForecastPresenter";
private View mView;
private List<String> mDates = new ArrayList<>();
private List<Double> mMaxTempAll = new ArrayList<>();
private List<Double> mMinTempAll = new ArrayList<>();
private List<String> mDescriptions = new ArrayList<>();
private List<String> mImgUrls = new ArrayList<>();
public ForecastPresenter(View view) {
this.mView = view;
}
public void getForecast(String latitude, String longitude, String city) {
Log.d(TAG, "getForecast: started");
Retrofit retrofit = RetrofitAdapter.getInstance();
EndpointInterface apiService = retrofit.create(EndpointInterface.class);
apiService.getForecast("metric", API_KEY, city,
Locale.getDefault().getLanguage(), latitude, longitude)
.throttleFirst(10, TimeUnit.MINUTES)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ForecastData>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ForecastData forecastData) {
forecastDataHandle(forecastData);
}
@Override
public void onError(Throwable e) {
Log.d(TAG, "onError: started");
Log.e(TAG, "onError: ", e.fillInStackTrace());
Log.e(TAG, "onError: ", e.getCause());
}
@Override
public void onComplete() {
}
});
}
private void forecastDataHandle(ForecastData forecastData) {
// Populate array list with values emitted from rxjava
int size= forecastData.getList().size();
for (int i = 0; i < size; i++) {
mMaxTempAll.add(forecastData.getList().get(i).getMain().getTempMax());
mMinTempAll.add(forecastData.getList().get(i).getMain().getTempMin());
mDates.add(formatUnixTime(forecastData.getList().get(i).getDt()));
mDescriptions.add(forecastData.getList().get(i).getWeather().get(0).getDescription());
mImgUrls.add(formatImgUrl(forecastData.getList().get(i).getWeather().get(0).getIcon()));
}
Log.d(TAG, "forecastDataHandle: mImgUrls = " + mImgUrls);
// We get 5 day / 3 hour forecast data, 40 items in total
// But we don't need such precise forecast, we just need
// to get 5 day / 1 day forecast, with min and max temps.
// But first we should calculate an arithmetic mean for the
// min/max temperature and add it to result array
final List<Double> maxTempAvg = getMaxAverage(mMaxTempAll, 8);
final List<Double> minTempAvg = getMinAverage(mMinTempAll, 8);
final List<String> avgMinMax = formatMinMaxValues(maxTempAvg, minTempAvg);
// Now we need to take every 8th element from mTemperatures, mDates, etc.
final List<String> dates = takeEveryNthElement(mDates, 8);
final List<String> descriptions = takeEveryNthElement(mDescriptions, 8);
final List<String> imgUrls = takeEveryNthElement(mImgUrls, 8);
// Send all of this to the UI
mView.updateForecast(avgMinMax, dates, descriptions, imgUrls);
}
private List<String> formatMinMaxValues(List<Double> maxTempAvg, List<Double> minTempAvg) {
List<String> result = new ArrayList<>();
for (int i = 0; i<maxTempAvg.size(); i++) {
long max = Math.round(maxTempAvg.get(i));
long min = Math.round(minTempAvg.get(i));
String fString = max + " .. " + min;
result.add(fString);
}
return result;
}
private List<Double> getMinAverage(List<Double> list, int offset) {
List<Double> temp = new ArrayList<>();
for (int i = 0; i < list.size(); i += offset) {
int toIndex = i + offset;
if (toIndex > list.size()) break;
temp.add(findMin(list.subList(i, toIndex)));
}
return temp;
}
private List<Double> getMaxAverage(List<Double> list, int offset) {
List<Double> temp = new ArrayList<>();
for (int i = 0; i < list.size(); i += offset) {
int toIndex = i + offset;
if (toIndex > list.size()) break;
temp.add(findMax(list.subList(i, toIndex)));
}
return temp;
}
private List<String> takeEveryNthElement(List<String> list, int nth) {
return IntStream.range(0, list.size())
.filter(n -> n % nth == 0)
.mapToObj(list::get)
.collect(Collectors.toList());
}
private double findMin(List<Double> subList) {
return subList.stream().mapToDouble(val -> val).min().orElse(0.0);
}
private double findMax(List<Double> subList) {
return subList.stream().mapToDouble(val -> val).max().orElse(0.0);
}
private String formatImgUrl(String iconCode) {
return String.format("http://openweathermap.org/img/wn/%s@2x.png", iconCode);
}
private String formatUnixTime(Integer unixSeconds) {
String[] locale = {Locale.getDefault().getLanguage(), Locale.getDefault().getCountry()};
Date date = new java.util.Date(unixSeconds*1000L);
SimpleDateFormat sdf = new java.text.SimpleDateFormat(
"EEEE dd MMMM",
new Locale(locale[0], locale[1]));
sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+5"));
return sdf.format(date);
}
public interface View {
void updateForecast(List<String> avgMinMax, List<String> mDates,
List<String> mDescriptions, List<String> mImgUrls);
}
}

View File

@ -0,0 +1,223 @@
package com.bernd32.weatherdemo.ui;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.weatherdemo.LocationsDao;
import com.bernd32.weatherdemo.LocationsRoomDatabase;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.models.UserLocation;
import com.bernd32.weatherdemo.ui.adapters.LocationsRecyclerAdapter;
import com.facebook.stetho.Stetho;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.schedulers.Schedulers;
/**
* In this activity user can add a new location manually or choose existing locations.
* Also here we load/save this data in a local database using Room
*/
public class AddNewLocation extends AppCompatActivity {
private static final String TAG = "AddNewLocation";
private RecyclerView mRecyclerView;
private LocationsDao mLocationsDao;
private EditText cityEdit;
private CompositeDisposable mDisposable;
private LocationsRecyclerAdapter adapter;
private LocationsRoomDatabase db;
private Button addButton;
private TextView emptyMsg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_new_location);
Stetho.initializeWithDefaults(this);
AddNewLocation.this.setTitle(getString(R.string.add_new_location));
cityEdit = findViewById(R.id.city_edit_text);
addButton = findViewById(R.id.button_add);
emptyMsg = findViewById(R.id.empty_message);
mRecyclerView = findViewById(R.id.recycler_view_locations);
mRecyclerView.setHasFixedSize(true);
adapter = new LocationsRecyclerAdapter(this, new ArrayList<>());
mRecyclerView.setAdapter(adapter);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
db = LocationsRoomDatabase.getDatabase(this);
mLocationsDao = db.locationsDao();
mDisposable = new CompositeDisposable();
addButton.setEnabled(false);
cityEdit.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(s.toString().trim().length()==0){
addButton.setEnabled(false);
} else {
addButton.setEnabled(true);
}
}
@Override
public void afterTextChanged(Editable s) { }
});
mDisposable.add(
mLocationsDao.getAllLocations()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<UserLocation>>() {
@Override
public void accept(List<UserLocation> userLocations) throws Exception {
adapter.addItems(userLocations);
// Show a message if the list is empty
emptyMsg.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
}));
deleteItemBySwipe();
}
private void deleteItemBySwipe() {
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();
UserLocation location = adapter.getItem(position);
Toast.makeText(AddNewLocation.this,
getString(R.string.delete_item_msg),
Toast.LENGTH_SHORT).show();
deleteLocation(location);
}
});
// Attach the item touch helper to the recycler view
helper.attachToRecyclerView(mRecyclerView);
}
private void deleteLocation(UserLocation location) {
Completable.fromAction(() -> {
mLocationsDao.deleteLocation(location);
})
.subscribeOn(Schedulers.io())
.subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e.fillInStackTrace());
showInformationDialog(getString(R.string.error), e.getLocalizedMessage());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mDisposable.dispose();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.add_new_location_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.delete_all) {
deleteAllItems();
Toast.makeText(this, getString(R.string.all_items_deleted_msg), Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
private void deleteAllItems() {
Completable.fromAction(() -> {
mLocationsDao.deleteAll();
})
.subscribeOn(Schedulers.io())
.subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e.fillInStackTrace());
showInformationDialog(getString(R.string.error), e.getLocalizedMessage());
}
});
}
public void onAddButton(View view) {
String city = cityEdit.getText().toString();
Completable.fromAction(() -> {
UserLocation userLocation = new UserLocation(city);
LocationsRoomDatabase.databaseWriteExecutor.execute(() -> {
mLocationsDao.insert(userLocation);
});
}).subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
Log.d(TAG, "onComplete: done!");
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e.fillInStackTrace());
}
});
}
public void showInformationDialog(String title, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(AddNewLocation.this);
builder.setTitle(title)
.setMessage(message)
.setIcon(R.drawable.ic_info_outline_black_24dp)
.setCancelable(true)
.setNegativeButton("OK", (dialog, id) -> dialog.cancel());
AlertDialog alert = builder.create();
alert.show();
}
}

View File

@ -0,0 +1,168 @@
package com.bernd32.weatherdemo.ui;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.models.WeatherItem;
import com.bernd32.weatherdemo.presenter.CurrentConditionsPresenter;
import com.bernd32.weatherdemo.ui.adapters.RecyclerAdapter;
import com.bernd32.weatherdemo.utils.PreferencesManager;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
/**
* Here we display current weather data by overriding callback interface methods
* sent by presenter.CurrentConditionsPresenter
*/
public class CurrentWeatherFragment extends Fragment implements CurrentConditionsPresenter.View {
private static final String TAG = "CurrentWeatherFragment";
private TextView mCity, mTemp, mWeatherText;
private ImageView mIcon;
private RecyclerView mRecyclerView;
private ProgressBar mProgressBar;
private ArrayList<WeatherItem> mItems = new ArrayList<>();
private String mImgUrl;
public CurrentWeatherFragment() {
// Required empty public constructor
}
/**
* @return A new instance of fragment CurrentWeatherFragment.
*/
public static CurrentWeatherFragment newInstance() {
return new CurrentWeatherFragment();
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
Log.d(TAG, "onSaveInstanceState: start");
outState.putString("image_url", mImgUrl);
super.onSaveInstanceState(outState);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: started");
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState == null) {
getData();
} else {
updateIcon(mImgUrl);
}
}
private void getData() {
// Read longitude and latitude values from PrefManager
PreferencesManager.initializeInstance(getContext());
PreferencesManager prefManager = PreferencesManager.getInstance();
String longitude = prefManager.getLongitude();
String latitude = prefManager.getLatitude();
String city = prefManager.getCity();
// Invoke the Presenter
CurrentConditionsPresenter conditionsPresenter = new CurrentConditionsPresenter(this, getContext());
conditionsPresenter.getCurrentConditions(latitude, longitude, city);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: started");
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_current_weather, container, false);
mCity = root.findViewById(R.id.city_tv);
mTemp = root.findViewById(R.id.temperature_tv);
mWeatherText = root.findViewById(R.id.weatherText);
mIcon = root.findViewById(R.id.weatherIcon);
mProgressBar = root.findViewById(R.id.progressBar);
mRecyclerView = root.findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setAdapter(new RecyclerAdapter(getContext(), mItems));
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
return root;
}
@Override
public void updateTemperature(String temp) {
mTemp.setText(temp);
}
@Override
public void updateLocation(String location) {
mCity.setText(location);
}
@Override
public void updateWeatherText(String weatherText) {
mWeatherText.setText(weatherText);
}
@Override
public void updateIcon(String url) {
mImgUrl = url;
Glide.with(this).load(mImgUrl).into(mIcon);
}
@Override
public void updateDetails(String pressure, String wind, String humidity) {
// Clear old information
mItems.clear();
mItems.add(new WeatherItem(R.drawable.ic_wi_barometer, getString(R.string.pressure), pressure));
mItems.add(new WeatherItem(R.drawable.ic_wi_day_windy, getString(R.string.wind), wind));
mItems.add(new WeatherItem(R.drawable.ic_wi_humidity, getString(R.string.humidity), humidity));
saveItems(mItems);
}
@Override
public void showProgressBar(boolean show) {
if (show) {
mProgressBar.setVisibility(View.VISIBLE);
} else {
mProgressBar.setVisibility(View.INVISIBLE);
}
}
@Override
public void showError(Throwable t) {
String title = getString(R.string.error);
String message = t.getLocalizedMessage();
Activity act = getActivity();
if (act instanceof MainActivity) {
((MainActivity) act).showInformationDialog(title, message);
}
}
private void saveItems(ArrayList<WeatherItem> mItems) {
RecyclerView.Adapter mAdapter = new RecyclerAdapter(getContext(), mItems);
mRecyclerView.setAdapter(mAdapter);
}
}

View File

@ -0,0 +1,103 @@
package com.bernd32.weatherdemo.ui;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.models.WeatherItem;
import com.bernd32.weatherdemo.presenter.ForecastPresenter;
import com.bernd32.weatherdemo.ui.adapters.RecyclerAdapter;
import com.bernd32.weatherdemo.utils.PreferencesManager;
import java.util.ArrayList;
import java.util.List;
/**
* Here we display forecast data by overriding callback interface methods
* sent by presenter.ForecastPresenter
*/
public class ForecastFragment extends Fragment implements ForecastPresenter.View{
private static final String TAG = "ForecastFragment";
private ArrayList<WeatherItem> mItems = new ArrayList<>();
private RecyclerView mRecyclerView;
public ForecastFragment() {
// Required empty public constructor
}
/**
* @return A new instance of fragment ForecastFragment.
*/
public static ForecastFragment newInstance() {
return new ForecastFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate: started");
setRetainInstance(true);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState == null) {
getData();
}
}
private void getData() {
// Read longitude and latitude values from PrefManager
PreferencesManager.initializeInstance(getContext());
PreferencesManager prefManager = PreferencesManager.getInstance();
String longitude = prefManager.getLongitude();
String latitude = prefManager.getLatitude();
String city = prefManager.getCity();
// Invoke the Presenter
ForecastPresenter forecastPresenter = new ForecastPresenter(this);
forecastPresenter.getForecast(latitude, longitude, city);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_forecast, container, false);
mRecyclerView = root.findViewById(R.id.recyclerView2);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setAdapter(new RecyclerAdapter(getContext(), mItems));
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
return root;
}
private void saveItems(ArrayList<WeatherItem> mItems) {
RecyclerView.Adapter mAdapter = new RecyclerAdapter(getContext(), mItems);
mRecyclerView.setAdapter(mAdapter);
}
@Override
public void updateForecast(List<String> avgMinMax, List<String> mDates,
List<String> mDescriptions, List<String> mImgUrls) {
mItems.clear();
for (int i = 0; mDates.size() > i; i++) {
mItems.add(new WeatherItem(mImgUrls.get(i), mDates.get(i), avgMinMax.get(i)));
}
Log.d(TAG, "updateForecast: mImgUrls" + mImgUrls);
saveItems(mItems);
}
}

View File

@ -0,0 +1,166 @@
package com.bernd32.weatherdemo.ui;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager.widget.ViewPager;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.ui.adapters.TabsPagerAdapter;
import com.bernd32.weatherdemo.utils.LocationProvider;
import com.bernd32.weatherdemo.utils.PreferencesManager;
import com.google.android.material.tabs.TabLayout;
import static com.bernd32.weatherdemo.utils.LocationProvider.PERMISSION_ID;
/**
* Choose what parameters (city name or location coordinates) we're using to call
* the weather API. Save params to PrefManager to use it across the app. Use
* utils.LocationProvider to get user's latitude and longitude if we're using
* location coordinates. Setup the UI (tabs, viewpager and fragments).
*/
public class MainActivity extends AppCompatActivity implements LocationProvider.Callback{
private static final String TAG = "MainActivity";
private PreferencesManager prefManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: started");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PreferencesManager.initializeInstance(this);
prefManager = PreferencesManager.getInstance();
if (savedInstanceState == null) {
prefManager.clear();
}
startApplication();
}
private void startApplication() {
/*
If we have a city value in Intent then it means that user selected
a location from AddNewLocation activity. In this case we just initialize
the UI and use location name as a parameter
*/
if (getIntent().hasExtra("city")) {
prefManager.saveCity(getIntent().getStringExtra("city"));
initUI();
} else {
/*
Otherwise, we request an user location data (latitude and longitude) and
use them as parameters. To achieve that we get user coordinates from the
LocationProvider and then return results via Callback interface by overriding
setResult() method.
*/
LocationProvider locationProvider = new LocationProvider(this, this);
locationProvider.getLastLocation();
}
}
// Start the app after we got permission result
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_ID) {// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onRequestPermissionsResult: granted");
// Permission was granted, yay!
startApplication();
} else {
Log.d(TAG, "onRequestPermissionsResult: denied");
// permission denied :( Open AddNewLocation activity where user
// can add a location for weather manually
startActivity(new Intent(this, AddNewLocation.class));
}
return;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.open_saved_locations:
startActivity(new Intent(this, AddNewLocation.class));
return true;
case R.id.update:
startApplication();
Toast.makeText(this, getString(R.string.updated_msg), Toast.LENGTH_SHORT).show();
return true;
case R.id.about_dialog:
showInformationDialog(
getString(R.string.about),
getString(R.string.about_message)
);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void setResult(Location location) {
// Get location from LocationProvider.Callback interface
double lat = location.getLatitude();
double lon = location.getLongitude();
Log.d(TAG, "setResult: started");
Log.d(TAG, "onNext: lat = " + lat);
Log.d(TAG, "onNext: long = " + lon);
// Save longitude and latitude values via PrefManager
prefManager.saveLatitude(String.valueOf(lat));
prefManager.saveLongitude(String.valueOf(lon));
// Show tab fragments
initUI();
}
@Override
public void locationTurnedOff() {
// Show an alert dialog if location is turned off
FragmentManager fm = getSupportFragmentManager();
TurnedOffLocationDialog alertDialog = TurnedOffLocationDialog.newInstance();
alertDialog.show(fm, "fragment_alert");
}
private void initUI() {
// Show tab fragments
TabsPagerAdapter tabsPagerAdapter = new TabsPagerAdapter(this, getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(tabsPagerAdapter);
TabLayout tabs = findViewById(R.id.tabs);
tabs.setupWithViewPager(viewPager);
}
public void showInformationDialog(String title, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(title)
.setMessage(message)
.setIcon(R.drawable.ic_info_outline_black_24dp)
.setCancelable(true)
.setNegativeButton("OK", (dialog, id) -> dialog.cancel());
AlertDialog alert = builder.create();
alert.show();
}
}

View File

@ -0,0 +1,55 @@
package com.bernd32.weatherdemo.ui;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.bernd32.weatherdemo.R;
/**
* This dialog fragment is showed if user's location if turned off
*/
public class TurnedOffLocationDialog extends DialogFragment {
private static final String TAG = "TurnedOffLocationDialog";
public TurnedOffLocationDialog() {
}
public static TurnedOffLocationDialog newInstance() {
TurnedOffLocationDialog frag = new TurnedOffLocationDialog();
return frag;
}
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
Log.d(TAG, "onCreateDialog: started");
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.location_turned_off)
.setPositiveButton(R.string.turn_on_location, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
})
.setNegativeButton(R.string.enter_a_city, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
startActivity(new Intent(getContext(), AddNewLocation.class));
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}

View File

@ -0,0 +1,100 @@
package com.bernd32.weatherdemo.ui.adapters;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.models.UserLocation;
import com.bernd32.weatherdemo.ui.MainActivity;
import com.google.android.material.card.MaterialCardView;
import java.util.List;
/**
* RecyclerView adapter used in ui.AddNewLocation activity
*/
public class LocationsRecyclerAdapter extends RecyclerView.Adapter<LocationsRecyclerAdapter.BaseViewHolder> {
private List<UserLocation> mItems;
private Context mContext;
private static final String TAG = "LocationsRecyclerAdapter";
public LocationsRecyclerAdapter(Context context, List<UserLocation> items) {
this.mItems = items;
this.mContext = context;
}
public void addItems(List<UserLocation> postItems) {
mItems.addAll(postItems);
mItems = postItems;
notifyDataSetChanged();
}
public void clear() {
mItems.clear();
notifyDataSetChanged();
}
public UserLocation getItem(int position) {
return mItems.get(position);
}
@NonNull
@Override
public LocationsRecyclerAdapter.BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.location_item, parent, false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
UserLocation currentItem = mItems.get(position);
// Since we have location names in getCity(), we should capitalize the first letter
String cityName = currentItem.getCity();
String capitalizedCityName = cityName.substring(0, 1).toUpperCase() + cityName.substring(1);
holder.itemValue.setText(capitalizedCityName);
holder.parentLayout.setOnClickListener(view -> {
Intent intent = new Intent(mContext, MainActivity.class);
intent.putExtra("city", holder.itemValue.getText().toString());
mContext.startActivity(intent);
});
// Show a tooltip on a long click
holder.parentLayout.setOnLongClickListener(view -> {
Toast.makeText(mContext, mContext.getString(R.string.tooltip_msg), Toast.LENGTH_SHORT).show();
return true;
});
}
@Override
public int getItemCount() {
return mItems == null ? 0 : mItems.size();
}
public static class BaseViewHolder extends RecyclerView.ViewHolder {
private TextView itemValue;
MaterialCardView parentLayout;
BaseViewHolder(@NonNull View itemView) {
super(itemView);
itemValue = itemView.findViewById(R.id.item_value);
parentLayout = itemView.findViewById(R.id.parent_layout);
}
}
}

View File

@ -0,0 +1,71 @@
package com.bernd32.weatherdemo.ui.adapters;
import android.content.Context;
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.weatherdemo.R;
import com.bernd32.weatherdemo.models.WeatherItem;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
/**
* RecyclerView adapter used in our fragments (ui.CurrentWeatherFragment and ui.ForecastFragment)
*/
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.BaseViewHolder> {
private ArrayList<WeatherItem> mWeatherItems;
private Context mContext;
private static final String TAG = "RecyclerAdapter";
public RecyclerAdapter(Context context, ArrayList<WeatherItem> items) {
this.mWeatherItems = items;
this.mContext = context;
}
public static class BaseViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
private TextView titleText, valueText;
BaseViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image);
titleText = itemView.findViewById(R.id.card_title);
valueText = itemView.findViewById(R.id.card_value);
}
}
@NonNull
@Override
public RecyclerAdapter.BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.forecast_item, parent, false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
WeatherItem currentItem = mWeatherItems.get(position);
holder.imageView.setImageResource(currentItem.getImgResource());
if (currentItem.getImgResource() != 0) {
holder.imageView.setImageResource(currentItem.getImgResource());
} else if (currentItem.getImgUrl() != null) {
Glide.with(mContext).load(currentItem.getImgUrl()).into(holder.imageView);
}
holder.titleText.setText(currentItem.getTitle());
holder.valueText.setText(currentItem.getValue());
}
@Override
public int getItemCount() {
return mWeatherItems.size();
}
}

View File

@ -0,0 +1,53 @@
package com.bernd32.weatherdemo.ui.adapters;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.bernd32.weatherdemo.R;
import com.bernd32.weatherdemo.ui.CurrentWeatherFragment;
import com.bernd32.weatherdemo.ui.ForecastFragment;
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class TabsPagerAdapter extends FragmentPagerAdapter {
@StringRes
private static final int[] TAB_TITLES =
new int[]{R.string.tab_text_1, R.string.tab_text_2};
private final Context mContext;
public TabsPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
@NonNull
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
if (position == 0) {
return CurrentWeatherFragment.newInstance();
} else {
return ForecastFragment.newInstance();
}
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return mContext.getResources().getString(TAB_TITLES[position]);
}
@Override
public int getCount() {
return 2;
}
}

View File

@ -0,0 +1,119 @@
package com.bernd32.weatherdemo.utils;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Looper;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
/**
* This class provides user's location coordinates, it is also checking permissions
* and requesting location data. Part of the code were taken from here:
* https://www.androdocs.com/java/getting-current-location-latitude-longitude-in-android-using-java.html
*/
public class LocationProvider {
public static final int PERMISSION_ID = 44;
private FusedLocationProviderClient mFusedLocationClient;
private Context mContext;
private Callback mCallback;
private static final String TAG = "LocationProvider";
public LocationProvider(Context context, Callback callback) {
this.mCallback = callback;
this.mContext = context;
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(mContext);
}
public void getLastLocation(){
Log.d(TAG, "getLastLocation: started");
if (checkPermissions()) {
if (isLocationEnabled()) {
mFusedLocationClient.getLastLocation().addOnCompleteListener(
task -> {
Location location = task.getResult();
if (location == null) {
LocationProvider.this.requestNewLocationData();
} else {
mCallback.setResult(location);
}
}
);
} else {
mCallback.locationTurnedOff();
}
} else {
requestPermissions();
}
}
@SuppressLint("MissingPermission")
private void requestNewLocationData(){
Log.d(TAG, "requestNewLocationData: ");
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(0);
mLocationRequest.setFastestInterval(0);
mLocationRequest.setNumUpdates(1);
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(mContext);
mFusedLocationClient.requestLocationUpdates(
mLocationRequest, mLocationCallback,
Looper.myLooper()
);
}
private LocationCallback mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Log.d(TAG, "onLocationResult: started");
Location mLocation = locationResult.getLastLocation();
mCallback.setResult(mLocation);
}
};
private boolean checkPermissions() {
return ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
Log.d(TAG, "requestPermissions: ");
ActivityCompat.requestPermissions(
(Activity) mContext,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
},
PERMISSION_ID
);
}
private boolean isLocationEnabled() {
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(
LocationManager.NETWORK_PROVIDER
);
}
public interface Callback {
void setResult(Location location);
void locationTurnedOff();
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.weatherdemo.utils;
import android.content.Context;
import android.content.SharedPreferences;
/**
* PrefManager utility to save/load/clear data
*/
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 LAT = "com.example.app.lat";
private static final String LON = "com.example.app.lon";
private static final String CITY = "com.example.app.city";
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 saveCity(String value) {
mPref.edit()
.putString(CITY, value)
.apply();
}
public String getCity() {
return mPref.getString(CITY, "");
}
public void saveLatitude(String value) {
mPref.edit()
.putString(LAT, value)
.apply();
}
public void saveLongitude(String value) {
mPref.edit()
.putString(LON, value)
.apply();
}
public String getLatitude() {
return mPref.getString(LAT, "");
}
public String getLongitude() {
return mPref.getString(LON, "");
}
public void remove(String key) {
mPref.edit()
.remove(key)
.apply();
}
public boolean clear() {
return mPref.edit()
.clear()
.commit();
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="72dp" android:viewportHeight="1024"
android:viewportWidth="1024" android:width="72dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#5CA4F6" android:pathData="M659.2,717.95a673.73,673.73 0,0 1,75.33 57.02,458.75 458.75,0 0,1 41.34,41.09c17.15,20.03 25.86,38.02 25.47,55.62 -0.58,20.22 -12.61,40.77 -35.84,62.4 -35.52,34.88 -88.26,57.15 -148.29,57.15 -24,0 -48.7,-3.84 -73.41,-11.33a338.43,338.43 0,0 1,-78.78 -36.29c-105.34,-62.21 -186.37,-142.02 -263.49,-259.39 -56.77,-86.4 -95.87,-176 -117.7,-266.62 -12.74,-52.93 -18.56,-101.25 -17.92,-139.01 0.13,-62.46 21.76,-115.07 62.78,-157.7a237.57,237.57 0,0 1,72.83 -52.29c18.82,-8.7 35.01,-12.99 49.86,-12.99 6.78,0 13.25,0.9 19.2,2.75 17.66,5.31 31.49,18.18 40.96,38.66 17.47,37.76 34.05,91.9 45.31,148.29 8.7,43.01 -1.73,59.39 -50.11,83.46l-10.3,4.35 -10.18,4.22 -13.44,5.57 -32.7,13.63a29.18,29.18 0,0 0,-16.58 35.97A933.76,933.76 0,0 0,326.08 603.52a940.99,940.99 0,0 0,148.22 177.66,28.99 28.99,0 0,0 19.2,7.81l2.37,-0.13a29.38,29.38 0,0 0,21.12 -11.07c11.9,-15.36 24,-29.06 35.2,-39.68 24.64,-23.42 46.72,-35.2 67.65,-35.2 5.44,0 10.82,0.77 16,2.37 4.99,1.54 13.82,6.4 23.36,12.67zM666.56,512.51A183.62,183.62 0,0 0,522.24 320.38a25.6,25.6 0,1 0,-10.82 50.05,132.35 132.35,0 0,1 103.94,138.56 25.6,25.6 0,0 0,51.14 3.52zM800.51,525.12a318.21,318.21 0,0 0,-230.4 -331.46,25.6 25.6,0 0,0 -13.89,49.28 266.88,266.88 0,0 1,193.22 278.14,25.6 25.6,0 0,0 51.07,4.03z"/>
<path android:fillColor="#5CA4F6" android:pathData="M942.08,535.42A460.16,460.16 0,0 0,611.01 57.73a25.6,25.6 0,1 0,-14.21 49.28,408.96 408.96,0 0,1 294.14,424.45 25.6,25.6 0,0 0,51.14 3.97z"/>
</vector>

View File

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,9 @@
<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="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,4 @@
<vector android:height="50dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="50dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<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="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,9 @@
<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,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>

View File

@ -0,0 +1,9 @@
<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="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#FF000000"
android:pathData="M7.69,13.2c0,-0.99 0.19,-1.94 0.58,-2.85c0.39,-0.91 0.91,-1.68 1.57,-2.33s1.44,-1.17 2.34,-1.56c0.9,-0.39 1.85,-0.58 2.84,-0.58c0.99,0 1.94,0.19 2.85,0.58c0.9,0.39 1.68,0.91 2.33,1.56c0.65,0.65 1.17,1.43 1.56,2.33s0.58,1.85 0.58,2.85c0,1.62 -0.48,3.06 -1.44,4.34c-0.96,1.27 -2.2,2.14 -3.71,2.61v3.29h-4.24v-3.25c-1.54,-0.45 -2.81,-1.32 -3.79,-2.61S7.69,14.83 7.69,13.2zM9.3,13.2c0,1.55 0.56,2.88 1.69,3.99c1.11,1.12 2.45,1.68 4.02,1.68c1.03,0 1.99,-0.25 2.86,-0.76c0.88,-0.51 1.57,-1.2 2.09,-2.07c0.51,-0.87 0.77,-1.82 0.77,-2.85c0,-0.77 -0.15,-1.5 -0.45,-2.21s-0.71,-1.31 -1.22,-1.82c-0.51,-0.51 -1.12,-0.92 -1.83,-1.22c-0.71,-0.3 -1.44,-0.45 -2.21,-0.45c-0.77,0 -1.5,0.15 -2.21,0.45s-1.31,0.71 -1.82,1.22c-0.51,0.51 -0.92,1.12 -1.22,1.82C9.45,11.7 9.3,12.43 9.3,13.2zM9.88,13.56v-0.72h2.17v0.72H9.88zM10.97,10.02l0.52,-0.52l1.52,1.52l-0.52,0.53L10.97,10.02zM13.5,14.95c0,-0.42 0.15,-0.78 0.44,-1.09c0.29,-0.31 0.65,-0.47 1.06,-0.48l2.73,-4.49l0.66,0.35l-2.02,4.83c0.18,0.25 0.26,0.54 0.26,0.88c0,0.44 -0.15,0.81 -0.46,1.11c-0.31,0.3 -0.68,0.45 -1.12,0.45c-0.43,0 -0.8,-0.15 -1.1,-0.45C13.65,15.76 13.5,15.39 13.5,14.95zM14.81,10.28V8.12h0.69v2.17H14.81zM17.75,13.55v-0.74h2.17v0.74H17.75z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#FF000000"
android:pathData="M1.48,21.1c0,0.24 0.09,0.44 0.27,0.6c0.17,0.17 0.37,0.25 0.61,0.25h5.88c0.26,0 0.48,0.09 0.68,0.28c0.2,0.19 0.3,0.42 0.3,0.68s-0.1,0.5 -0.3,0.69c-0.2,0.19 -0.42,0.29 -0.68,0.29c-0.26,0 -0.48,-0.1 -0.68,-0.3c-0.18,-0.16 -0.38,-0.24 -0.61,-0.24c-0.24,0 -0.44,0.08 -0.6,0.24c-0.16,0.16 -0.24,0.36 -0.24,0.6c0,0.24 0.08,0.44 0.24,0.6c0.53,0.53 1.16,0.8 1.89,0.8c0.74,0 1.37,-0.26 1.88,-0.78s0.78,-1.15 0.78,-1.89s-0.26,-1.37 -0.78,-1.89c-0.52,-0.52 -1.15,-0.78 -1.88,-0.78H2.36c-0.24,0 -0.44,0.08 -0.62,0.25C1.57,20.67 1.48,20.87 1.48,21.1zM1.48,18.09c0,0.23 0.09,0.42 0.27,0.58c0.16,0.16 0.37,0.24 0.61,0.24h10.97c0.74,0 1.37,-0.26 1.89,-0.78c0.52,-0.52 0.78,-1.15 0.78,-1.89c0,-0.74 -0.26,-1.36 -0.78,-1.88c-0.52,-0.51 -1.15,-0.77 -1.89,-0.77c-0.76,0 -1.38,0.25 -1.88,0.76c-0.16,0.16 -0.23,0.37 -0.23,0.61c0,0.24 0.08,0.44 0.23,0.6c0.15,0.15 0.35,0.23 0.6,0.23c0.24,0 0.44,-0.08 0.62,-0.23c0.19,-0.19 0.41,-0.28 0.68,-0.28s0.49,0.09 0.68,0.28s0.29,0.42 0.29,0.68c0,0.27 -0.1,0.5 -0.29,0.69c-0.19,0.19 -0.42,0.29 -0.68,0.29H2.36c-0.24,0 -0.44,0.09 -0.62,0.26C1.57,17.66 1.48,17.86 1.48,18.09zM7.27,11.55c0,-0.24 0.09,-0.44 0.26,-0.62c0.18,-0.16 0.38,-0.24 0.6,-0.24h2.03c0.23,0 0.42,0.08 0.58,0.25c0.16,0.17 0.23,0.37 0.23,0.61c0,0.24 -0.08,0.44 -0.23,0.6c-0.16,0.17 -0.35,0.25 -0.58,0.25H8.13c-0.24,0 -0.44,-0.08 -0.61,-0.25C7.35,11.98 7.27,11.78 7.27,11.55zM10.12,4.63c0,-0.23 0.08,-0.43 0.23,-0.61c0.19,-0.16 0.41,-0.24 0.64,-0.24c0.22,0 0.42,0.08 0.59,0.24l1.43,1.47c0.16,0.15 0.24,0.35 0.24,0.59c0,0.24 -0.08,0.44 -0.24,0.6s-0.36,0.24 -0.59,0.24c-0.24,0 -0.44,-0.08 -0.59,-0.24l-1.47,-1.43C10.2,5.08 10.12,4.88 10.12,4.63zM12.68,11.43v-0.07c0.02,-0.91 0.27,-1.75 0.74,-2.53c0.48,-0.77 1.11,-1.38 1.9,-1.83c0.79,-0.45 1.65,-0.67 2.57,-0.67c0.7,0 1.37,0.14 2.02,0.42c0.64,0.28 1.2,0.65 1.66,1.12c0.47,0.47 0.84,1.02 1.11,1.66s0.41,1.32 0.41,2.02c0,0.94 -0.23,1.8 -0.69,2.6s-1.09,1.43 -1.88,1.89c-0.79,0.47 -1.66,0.7 -2.6,0.71h-0.21c-0.07,0 -0.13,-0.02 -0.17,-0.07c-0.05,-0.05 -0.07,-0.11 -0.07,-0.18v-1.22c0,-0.13 0.07,-0.2 0.22,-0.2h0.24c0.96,-0.01 1.79,-0.35 2.47,-1.05c0.68,-0.69 1.03,-1.52 1.03,-2.49c0,-0.96 -0.35,-1.78 -1.04,-2.47c-0.69,-0.68 -1.52,-1.02 -2.5,-1.02c-0.94,0 -1.76,0.32 -2.44,0.98c-0.68,0.65 -1.04,1.44 -1.08,2.37c0,0.06 -0.03,0.11 -0.08,0.17s-0.14,0.09 -0.26,0.09H12.9C12.75,11.67 12.68,11.59 12.68,11.43zM17.03,21.31v-1.99c0,-0.24 0.08,-0.44 0.25,-0.6s0.37,-0.24 0.6,-0.24c0.25,0 0.45,0.08 0.61,0.24c0.16,0.16 0.24,0.36 0.24,0.6v1.99c0,0.24 -0.08,0.45 -0.25,0.62c-0.17,0.17 -0.37,0.25 -0.6,0.25c-0.24,0 -0.44,-0.08 -0.6,-0.25C17.12,21.76 17.03,21.55 17.03,21.31zM17.03,3.83V1.78c0,-0.23 0.08,-0.43 0.25,-0.6s0.37,-0.25 0.6,-0.25c0.24,0 0.44,0.08 0.6,0.25s0.25,0.37 0.25,0.6v2.04c0,0.23 -0.08,0.42 -0.25,0.58c-0.17,0.15 -0.37,0.23 -0.6,0.23c-0.24,0 -0.44,-0.08 -0.6,-0.23C17.12,4.25 17.03,4.06 17.03,3.83zM22.56,17.01c0,-0.23 0.08,-0.42 0.23,-0.56c0.15,-0.16 0.34,-0.23 0.57,-0.23c0.24,0 0.44,0.08 0.6,0.23l1.45,1.42c0.16,0.17 0.24,0.38 0.24,0.61c0,0.23 -0.08,0.43 -0.24,0.59c-0.4,0.31 -0.8,0.31 -1.2,0l-1.42,-1.43C22.63,17.48 22.56,17.26 22.56,17.01zM22.56,6.08c0,-0.25 0.08,-0.45 0.23,-0.59l1.42,-1.47c0.18,-0.16 0.37,-0.24 0.59,-0.24c0.23,0 0.43,0.08 0.6,0.25c0.17,0.17 0.25,0.37 0.25,0.6c0,0.25 -0.08,0.46 -0.24,0.62l-1.45,1.43c-0.18,0.16 -0.38,0.24 -0.6,0.24c-0.23,0 -0.41,-0.08 -0.57,-0.24C22.63,6.52 22.56,6.32 22.56,6.08zM24.82,11.55c0,-0.24 0.08,-0.44 0.24,-0.62c0.16,-0.16 0.35,-0.24 0.57,-0.24h2.02c0.23,0 0.43,0.09 0.61,0.26s0.26,0.37 0.26,0.6c0,0.23 -0.09,0.43 -0.26,0.6c-0.18,0.17 -0.38,0.25 -0.61,0.25h-2.02c-0.23,0 -0.42,-0.08 -0.58,-0.25C24.89,11.99 24.82,11.79 24.82,11.55z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#FF000000"
android:pathData="M7.56,17.19c0,-0.88 0.24,-1.89 0.72,-3.03s1.1,-2.25 1.86,-3.31c1.56,-2.06 2.92,-3.62 4.06,-4.67l0.75,-0.72c0.25,0.26 0.53,0.5 0.83,0.72c0.41,0.42 1.04,1.11 1.88,2.09s1.57,1.85 2.17,2.65c0.71,1.01 1.32,2.1 1.81,3.25s0.74,2.16 0.74,3.03c0,1 -0.19,1.95 -0.58,2.86c-0.39,0.91 -0.91,1.7 -1.57,2.36c-0.66,0.66 -1.45,1.19 -2.37,1.58c-0.92,0.39 -1.89,0.59 -2.91,0.59c-1,0 -1.95,-0.19 -2.86,-0.57c-0.91,-0.38 -1.7,-0.89 -2.36,-1.55c-0.66,-0.65 -1.19,-1.44 -1.58,-2.35S7.56,18.23 7.56,17.19zM9.82,14.26c0,0.83 0.17,1.49 0.52,1.99c0.35,0.49 0.88,0.74 1.59,0.74c0.72,0 1.25,-0.25 1.61,-0.74c0.35,-0.49 0.53,-1.15 0.54,-1.99c-0.01,-0.84 -0.19,-1.5 -0.54,-2c-0.35,-0.49 -0.89,-0.74 -1.61,-0.74c-0.71,0 -1.24,0.25 -1.59,0.74C9.99,12.76 9.82,13.42 9.82,14.26zM11.39,14.26c0,-0.15 0,-0.27 0,-0.35s0.01,-0.19 0.02,-0.33c0.01,-0.14 0.02,-0.25 0.05,-0.32s0.05,-0.16 0.09,-0.24c0.04,-0.08 0.09,-0.15 0.15,-0.18c0.07,-0.04 0.14,-0.06 0.23,-0.06c0.14,0 0.25,0.04 0.33,0.12s0.14,0.21 0.17,0.38c0.03,0.18 0.05,0.32 0.06,0.45s0.01,0.3 0.01,0.52c0,0.23 0,0.4 -0.01,0.52c-0.01,0.12 -0.03,0.27 -0.06,0.45c-0.03,0.17 -0.09,0.3 -0.17,0.38s-0.19,0.12 -0.33,0.12c-0.09,0 -0.16,-0.02 -0.23,-0.06c-0.07,-0.04 -0.12,-0.1 -0.15,-0.18c-0.04,-0.08 -0.07,-0.17 -0.09,-0.24c-0.02,-0.08 -0.04,-0.19 -0.05,-0.32c-0.01,-0.14 -0.02,-0.25 -0.02,-0.32S11.39,14.41 11.39,14.26zM11.98,22.01h1.32l4.99,-10.74h-1.35L11.98,22.01zM16.28,19.02c0.01,0.84 0.2,1.5 0.55,2c0.35,0.49 0.89,0.74 1.6,0.74c0.72,0 1.25,-0.25 1.6,-0.74c0.35,-0.49 0.52,-1.16 0.53,-2c-0.01,-0.84 -0.18,-1.5 -0.53,-1.99c-0.35,-0.49 -0.88,-0.74 -1.6,-0.74c-0.71,0 -1.25,0.25 -1.6,0.74C16.47,17.52 16.29,18.18 16.28,19.02zM17.85,19.02c0,-0.23 0,-0.4 0.01,-0.52c0.01,-0.12 0.03,-0.27 0.06,-0.45s0.09,-0.3 0.17,-0.38s0.19,-0.12 0.33,-0.12c0.09,0 0.17,0.02 0.24,0.06c0.07,0.04 0.12,0.1 0.16,0.19c0.04,0.09 0.07,0.17 0.1,0.24s0.04,0.18 0.05,0.32l0.01,0.32l0,0.34c0,0.16 0,0.28 0,0.35l-0.01,0.32l-0.05,0.32l-0.1,0.24l-0.16,0.19l-0.24,0.06c-0.14,0 -0.25,-0.04 -0.33,-0.12s-0.14,-0.21 -0.17,-0.38c-0.03,-0.18 -0.05,-0.33 -0.06,-0.45S17.85,19.25 17.85,19.02z"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:angle="0"
android:startColor="#00BCD4"
android:endColor="#1976D2" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Raleway&amp;weight=600"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:padding="12dp"
tools:context=".ui.AddNewLocation">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textSize="18sp"
android:text="@string/add_a_new_city" />
<EditText
android:id="@+id/city_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/city_name"
android:text="" />
<Button
android:id="@+id/button_add"
android:onClick="onAddButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add" />
<TextView
android:id="@+id/empty_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_message"
android:layout_marginTop="20dp"
android:textStyle="italic"
android:visibility="gone"
android:layout_gravity="center"
tools:visibility="visible"/>
<ScrollView
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"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_locations"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/design_default_color_background"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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.MainActivity"
>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.MaterialComponents.ActionBar"
>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:fillViewport="true"
app:tabMode="fixed"
style="@style/Widget.MaterialComponents.TabLayout.Colored"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:cardCornerRadius="4dp">
<ImageView
android:src="@drawable/location_cardview_item_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:color/darker_gray" />
<TextView
android:id="@+id/card_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Title"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintStart_toEndOf="@+id/image"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/card_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="Value"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toEndOf="@+id/image"
app:layout_constraintTop_toBottomOf="@+id/card_title"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/current_weather_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MainActivity">
<TextView
android:id="@+id/temperature_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:freezesText="true"
android:textAppearance="@style/TextAppearance.AppCompat.Display3"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/city_tv"
tools:visibility="visible" />
<TextView
android:id="@+id/weatherText"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="8dp"
android:freezesText="true"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/temperature_tv"
tools:visibility="visible" />
<TextView
android:id="@+id/empty_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="City list is empty. Add a city! "
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/weatherText" />
<TextView
android:id="@+id/city_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:freezesText="true"
android:drawableStart="@drawable/ic_location_on_black_24dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/weatherIcon"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="8dp"
android:contentDescription="@string/weather_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/weatherText" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/design_default_color_background"
android:padding="8dp"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/weatherIcon" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="182dp"
tools:layout_editor_absoluteY="326dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<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:padding="8dp"
tools:context=".ui.MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/design_default_color_background"
android:scrollbars="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
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"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:cardElevation="0dp"
app:cardMaxElevation="0dp"
android:id="@+id/parent_layout"
app:cardCornerRadius="4dp">
<ImageView
android:src="@drawable/location_cardview_item_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<TextView
android:id="@+id/item_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textStyle="bold"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<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,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/open_saved_locations"
android:title="@string/add_new_location"
android:icon="@drawable/ic_add_black_24dp"
app:showAsAction="ifRoom" />
<item android:id="@+id/update"
android:title="@string/update"
android:icon="@drawable/ic_refresh_black_24dp"
app:showAsAction="ifRoom" />
<item android:id="@+id/about_dialog"
android:title="@string/about"
app:showAsAction="never"
android:icon="@drawable/ic_info_outline_black_24dp" />
</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.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Weather Demo App</string>
<string name="about">О программе</string>
<string name="about_message">Weather App Demo 1.0.0\n\nСделано bernd32 с целью практики использования Retrofit и RxJava.\n\nМой Github: https://github.com/bernd32\nEmail: fallentides@outlook.com</string>
<string name="add">Добавить</string>
<string name="add_a_new_city">Добавить новый город:</string>
<string name="add_new_location">"Добавить новое местоположение… "</string>
<string name="all_items_deleted_msg">Очищено</string>
<string name="city_name">Название города (например: Екатеринбург)</string>
<string name="current_location">Текущее местоположение</string>
<string name="delete_all">Очистить</string>
<string name="delete_item_msg">Местоположение удалено</string>
<string name="empty_message">Список пуст…</string>
<string name="enter_a_city">Введите название города</string>
<string name="error">Ошибка</string>
<string name="humidity">Влажность</string>
<string name="icon">Иконка</string>
<string name="location">Местоположение</string>
<string name="location_turned_off">Включить локацию</string>
<string name="pressure">Давление</string>
<string name="save">Сохранить</string>
<string name="tab_text_1">Текущая погода</string>
<string name="tab_text_2">Прогноз</string>
<string name="tooltip_msg">Свайпните элемент для удаления</string>
<string name="turn_on_location">Включить локацию</string>
<string name="update">Обновить</string>
<string name="updated_msg">Обновлено</string>
<string name="weather_icon">Иконка</string>
<string name="wind">Скорость ветра</string>
<string name="meters_per_second">м/с</string>
<string name="pressure_unit">гПа</string>
</resources>

View File

@ -0,0 +1,12 @@
<resources>
<!-- Reply Preference -->
<string-array name="reply_entries">
<item>Reply</item>
<item>Reply to all</item>
</string-array>
<string-array name="reply_values">
<item>reply</item>
<item>reply_all</item>
</string-array>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#2196F3</color>
<color name="colorPrimaryDark">#1976D2</color>
<color name="colorAccent">#FF5252</color>
<color name="gray">#e3e3e3</color>
</resources>

View File

@ -0,0 +1,8 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="appbar_padding">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
</resources>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/raleway_semibold</item>
</array>
</resources>

View File

@ -0,0 +1,43 @@
<resources>
<string name="app_name">Weather Demo App</string>
<string name="tab_text_1">Current weather</string>
<string name="tab_text_2">Forecast</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="weather_icon">Weather icon</string>
<string name="icon">Icon</string>
<string name="pressure">Pressure</string>
<string name="wind">Wind</string>
<string name="humidity">Humidity</string>
<string name="location_turned_off">Turn on location</string>
<string name="current_location">Current location</string>
<string name="enter_a_city">Enter a city</string>
<string name="save">Save</string>
<!-- Preference Titles -->
<!-- Messages Preferences -->
<!-- Sync Preferences -->
<string name="location">Location</string>
<string name="update">Update…</string>
<string name="turn_on_location">Turn on location</string>
<string name="add_new_location">Add new location… </string>
<string name="add_a_new_city">Add a new city by name:</string>
<string name="city_name">City name (e.g.: London, UK)</string>
<string name="add">Add</string>
<string name="delete_item_msg">Location deleted</string>
<string name="tooltip_msg">Swipe it to delete</string>
<string name="empty_message">City list is empty…</string>
<string name="updated_msg">Updated</string>
<string name="about">About</string>
<string name="about_message">Weather Demo 1.0.0\n\nThis is a demo app created by
bernd32 for educational purposes.\n\nMy Github: https://github.com/bernd32\nEmail: fallentides@outlook.com
</string>
<string name="error">Error</string>
<string name="delete_all">Clear</string>
<string name="all_items_deleted_msg">Cleared</string>
<string name="meters_per_second">m/s</string>
<string name="pressure_unit">hPa</string>
</resources>

View File

@ -0,0 +1,20 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">openweathermap.org</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,17 @@
package com.bernd32.weatherdemo;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -0,0 +1,12 @@
package com.bernd32.weatherdemo.utils;
import org.junit.Test;
import static org.junit.Assert.*;
public class LocationProviderTest {
@Test
public void getLastLocation() {
}
}

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