├── .gitignore ├── LICENSE ├── Privacy.md ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── nl │ │ └── implode │ │ └── weer │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── databases │ │ │ └── cities.db │ ├── java │ │ ├── com │ │ │ └── skego │ │ │ │ └── converter │ │ │ │ └── TimezoneMapper.java │ │ └── nl │ │ │ └── implode │ │ │ └── weer │ │ │ ├── AppCompatPreferenceActivity.java │ │ │ ├── DelayAutoCompleteTextView.java │ │ │ ├── ForecastWidget.java │ │ │ ├── ForecastWidgetConfigureActivity.java │ │ │ ├── ForecastWidgetDark.java │ │ │ ├── ForecastWidgetDarkConfigureActivity.java │ │ │ ├── ForecastWidgetService.java │ │ │ ├── SettingsActivity.java │ │ │ ├── WeatherStation.java │ │ │ ├── WeatherStationAutoCompleteAdapter.java │ │ │ └── WeatherStationsDatabase.java │ └── res │ │ ├── drawable-anydpi-v21 │ │ └── ic_settings_black_24dp.xml │ │ ├── drawable-hdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_info_white.png │ │ ├── ic_map.png │ │ ├── ic_notifications_black_24dp.png │ │ ├── ic_settings_black_18dp.png │ │ ├── ic_settings_white_18dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-mdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_info_white.png │ │ ├── ic_map.png │ │ ├── ic_notifications_black_24dp.png │ │ ├── ic_settings_black_18dp.png │ │ ├── ic_settings_white_18dp.png │ │ ├── ic_sync_black_24dp.png │ │ ├── icon_12.png │ │ ├── icon_14.png │ │ ├── icon_20.png │ │ ├── icon_23.png │ │ ├── icon_26.png │ │ ├── icon_27.png │ │ ├── icon_28.png │ │ ├── icon_30.png │ │ ├── icon_31.png │ │ ├── icon_32.png │ │ ├── icon_33.png │ │ ├── icon_35.png │ │ ├── icon_37.png │ │ ├── icon_39.png │ │ ├── icon_41.png │ │ ├── icon_45.png │ │ ├── icon_46.png │ │ └── icon_47.png │ │ ├── drawable-nodpi │ │ ├── forecastwidget_dark_preview.png │ │ └── forecastwidget_preview.png │ │ ├── drawable-v21 │ │ ├── ic_info_black_24dp.xml │ │ ├── ic_notifications_black_24dp.xml │ │ └── ic_sync_black_24dp.xml │ │ ├── drawable-xhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_info_white.png │ │ ├── ic_map.png │ │ ├── ic_notifications_black_24dp.png │ │ ├── ic_settings_black_18dp.png │ │ ├── ic_settings_white_18dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_info_white.png │ │ ├── ic_map.png │ │ ├── ic_notifications_black_24dp.png │ │ ├── ic_settings_black_18dp.png │ │ ├── ic_settings_white_18dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_info_black_24dp.png │ │ ├── ic_info_white.png │ │ ├── ic_map.png │ │ ├── ic_notifications_black_24dp.png │ │ └── ic_sync_black_24dp.png │ │ ├── layout │ │ ├── autocomplete_line.xml │ │ ├── day.xml │ │ ├── day_dark.xml │ │ ├── forecast.xml │ │ ├── forecast_dark.xml │ │ ├── forecast_widget.xml │ │ ├── forecast_widget_configure.xml │ │ ├── forecast_widget_dark.xml │ │ ├── item_weatherstation.xml │ │ ├── times.xml │ │ └── times_dark.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-v14 │ │ └── dimens.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── forecast_dark_widget_info.xml │ │ ├── forecast_widget_info.xml │ │ ├── pref_general.xml │ │ ├── pref_headers.xml │ │ └── pref_headers_dark.xml │ └── test │ └── java │ └── nl │ └── implode │ └── weer │ └── ExampleUnitTest.java ├── build.gradle ├── fastlane └── metadata │ └── android │ ├── de-DE │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt │ ├── en-US │ ├── changelogs │ │ ├── 12.txt │ │ ├── 13.txt │ │ ├── 14.txt │ │ ├── 15.txt │ │ ├── 16.txt │ │ ├── 17.txt │ │ ├── 18.txt │ │ ├── 19.txt │ │ ├── 20.txt │ │ ├── 21.txt │ │ ├── 22.txt │ │ ├── 23.txt │ │ └── 24.txt │ ├── full_description.txt │ ├── phoneScreenshots │ │ ├── 1-widget-4x4-light.png │ │ ├── 2-widget-4x4-dark.png │ │ ├── 3-widget-search-light.png │ │ ├── 4-settings-light.png │ │ ├── 5-settings2-light.png │ │ ├── 6-settings2-dark.png │ │ ├── 7-widget-5x5-light.png │ │ └── 8-widget-5x5-dark.png │ ├── short_description.txt │ └── title.txt │ └── nl-NL │ ├── changelogs │ ├── 15.txt │ ├── 16.txt │ ├── 17.txt │ ├── 18.txt │ ├── 19.txt │ ├── 20.txt │ ├── 21.txt │ ├── 22.txt │ ├── 23.txt │ └── 24.txt │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── projectFilesBackup └── .idea │ └── workspace.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Intellij 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/libraries 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild 49 | 50 | # Google Services (e.g. APIs or Firebase) 51 | google-services.json 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sander Baas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Privacy.md: -------------------------------------------------------------------------------- 1 | ## Weather Widget Privacy Policy 2 | 3 | 4 | This is the developer's policy on how your personal information may be collected, used, disclosed and secured. These policies are stated to ensure you know what personal details you may or may not disclose when the software is being utilized and the choices you have associated with that data. 5 | If you decide to continue using this software, it means you accept all terms and conditions stated by the developer in this policy. Kindly note that this privacy policy is restricted only to Weather Widget software and the developer is not and will not be liable to be held in account for the privacy practices of any third-party tools or services that may be contained in the software. 6 | 7 | --- 8 | 9 | ## Information Collection And Use 10 | 11 | To function basically as intended, the developer doesn't collection any form of information personally when the software is utilized and the software doesn't require any permission either. It is a simple widget software. However, the software directly uses the OpenWeatherMap API. 12 | 13 | 14 | --- 15 | 16 | ## Types of Data Collected 17 | 18 | ### Personal Data 19 | 20 | Due to the integration of OpenWeatherMap API in the software personal data may be collected while the Weather Widget is in use. This data is in no way retrieved by the developer of Weather Widget. 21 | 22 | ### Usage Data 23 | 24 | This app does not record usage information, however, usage statistics are captured by OpenWeatherMap with respect to their [policy](https://openweathermap.org/privacy-policy). 25 | 26 | ### Use of Data 27 | 28 | With the integration of OpenWeatherMap API, the data collected is used to allow Weather Widget 29 | 30 | - Access current weather data for any location including over 200,000 cities. 31 | 32 | --- 33 | 34 | ## Security Of Data 35 | 36 | There are no security risks that may likely occur while using Weather Widget software. However, with the inclusion of OpenWeatherMap API, it is necessary to read how they (OpenWeatherMap) secure your data that may be collected while you utilize Weather Widget. The developer of Weather Widget assumes no responsibility for data integrity issues caused by using this application. 37 | 38 | --- 39 | 40 | ## Changes To This Privacy Policy 41 | 42 | This privacy policy statement is not final and is subject to changes at later times. This is done to accommodate changes in the software's architecture due to further developments and that these changes may not align with several statements and policies stated in the document. With that stated, you are advised to check this policy statement occasionally for any changes that may have occurred. Changes to this policy become effective as soon as they are made. Also occasionally check policies of third-party software that is linked with Weather Widget. 43 | 44 | --- 45 | 46 | 47 | ## Disclaimer 48 | 49 | Weather Widget is developed in good faith to help end users keep tabs of weather variations at different intervals. The software has an external dependency (OpenWeatherMap API) that might collect some information when you utilize Weather widget. This is not directly controlled by the developer of Weather Widget neither does the developer has oversight of the operations of OpenWeatherMap hence, the developer assumes no responsibility for any losses and/or damages of data that are in connection with the use of this software. 50 | 51 | --- 52 | 53 | 54 | ## Contact Us 55 | 56 | Weather Widget app would certainly improve with contributions from the open source community. Feel free to make contributions and suggestions to this project either by opening an issue or implementing opening pull requests of those suggestions and improvements. For further inquiry regarding this privacy policy or any other issue relating to the development or within the circumference of Weather Widget software, please contact the developer by opening an issue on the app's official repository [here](https://github.com/sanderbaas/WeatherWidget) on Github. Please also check if the issue you want to open hasn't already been opened. 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Weather Widget 2 | 3 | Add one or more widgets to your homescreen with 4 to 6 day weather forecasts in selected locations. Each day 4 | is split into blocks of three hours and the information included in each forecast are: 5 | - weather icon indicating sunshine, clouds and precipitation 6 | - temperature (Kelvin, Celcius or Fahrenheit) 7 | - wind direction 8 | - wind speed (Beaufort) 9 | - amount of rain (mm or inch) 10 | - amount of snow (mm or inch) 11 | 12 | The source of the forecasts is [OpenWeatherMap](https://openweathermap.org/) and is refreshed every half hour or when one of the widgets is 13 | tapped. Temperature can be displayed in one of three scales: Kelvin, Celcius or Farenheit and is shown in blue 14 | when temperature is below freezing and red otherwise. Rain and snow forecasts can be displayed in mm or inches. 15 | 16 | The times of the blocks can either be of the forecast location itself or the location of the phone and can be 17 | shown in 24-hour format of am/pm-format. 18 | 19 | A widget has a minimal size of 4 x 3 and can be larger. When a widget is larger, more forecasts will fit. It is 20 | possible to change the location after adding a widget by tapping the gear icon. 21 | 22 | This app is available in English, Dutch, German and Chinese. 23 | 24 | Weather icons thanks to (MerlinTheRed)[https://www.deviantart.com/merlinthered/art/plain-weather-icons-157162192] 25 | 26 | ## Screenshots 27 | 28 | ### Home screen widget 29 | ![alt text](fastlane/metadata/android/en-US/phoneScreenshots/1-widget-4x4-light.png "Home screen widget") 30 | ![alt text](fastlane/metadata/android/en-US/phoneScreenshots/2-widget-4x4-dark.png "Dark home screen widget") 31 | 32 | ### Config screen of widget 33 | ![alt text](fastlane/metadata/android/en-US/phoneScreenshots/3-widget-search-light.png "Config screen of widget") 34 | 35 | ### Settings screen of app 36 | ![alt text](fastlane/metadata/android/en-US/phoneScreenshots/5-settings2-light.png "Settings screen of app") 37 | ![alt text](fastlane/metadata/android/en-US/phoneScreenshots/6-settings2-dark.png "Dark theme settings screen of app") 38 | 39 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | 6 | lintOptions { 7 | abortOnError false 8 | } 9 | defaultConfig { 10 | applicationId "nl.implode.weer" 11 | minSdkVersion 22 12 | targetSdkVersion 27 13 | versionCode 24 14 | versionName "1.5.1" 15 | setProperty("archivesBaseName", "weather-$versionName") 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | debug { 23 | applicationIdSuffix ".debug" 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | testImplementation 'junit:junit:4.12' 31 | implementation 'com.intuit.sdp:sdp-android:1.0.4' 32 | implementation 'com.android.support:appcompat-v7:27.1.1' 33 | implementation 'com.android.support:support-v4:27.1.1' 34 | implementation 'com.squareup.okhttp3:okhttp:3.5.0' 35 | implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:+' 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/sander/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/nl/implode/weer/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/assets/databases/cities.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/assets/databases/cities.db -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.preference.PreferenceActivity; 7 | import android.preference.PreferenceManager; 8 | import android.support.annotation.LayoutRes; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.app.ActionBar; 11 | import android.support.v7.app.AppCompatDelegate; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.MenuInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | 17 | /** 18 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls 19 | * to be used with AppCompat. 20 | */ 21 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity { 22 | 23 | private AppCompatDelegate mDelegate; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | getDelegate().installViewFactory(); 28 | SharedPreferences sharedPrefs = PreferenceManager 29 | .getDefaultSharedPreferences(this); 30 | 31 | Boolean prefDarkTheme = sharedPrefs.getBoolean("use_dark_theme", false); 32 | Integer theme = R.style.AppTheme; 33 | if (prefDarkTheme) { 34 | theme = R.style.AppThemeDark; 35 | } 36 | 37 | this.setTheme(theme); 38 | getDelegate().onCreate(savedInstanceState); 39 | super.onCreate(savedInstanceState); 40 | } 41 | 42 | @Override 43 | protected void onPostCreate(Bundle savedInstanceState) { 44 | super.onPostCreate(savedInstanceState); 45 | getDelegate().onPostCreate(savedInstanceState); 46 | } 47 | 48 | public ActionBar getSupportActionBar() { 49 | return getDelegate().getSupportActionBar(); 50 | } 51 | 52 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 53 | getDelegate().setSupportActionBar(toolbar); 54 | } 55 | 56 | @Override 57 | public MenuInflater getMenuInflater() { 58 | return getDelegate().getMenuInflater(); 59 | } 60 | 61 | @Override 62 | public void setContentView(@LayoutRes int layoutResID) { 63 | getDelegate().setContentView(layoutResID); 64 | } 65 | 66 | @Override 67 | public void setContentView(View view) { 68 | getDelegate().setContentView(view); 69 | } 70 | 71 | @Override 72 | public void setContentView(View view, ViewGroup.LayoutParams params) { 73 | getDelegate().setContentView(view, params); 74 | } 75 | 76 | @Override 77 | public void addContentView(View view, ViewGroup.LayoutParams params) { 78 | getDelegate().addContentView(view, params); 79 | } 80 | 81 | @Override 82 | protected void onPostResume() { 83 | super.onPostResume(); 84 | getDelegate().onPostResume(); 85 | } 86 | 87 | @Override 88 | protected void onTitleChanged(CharSequence title, int color) { 89 | super.onTitleChanged(title, color); 90 | getDelegate().setTitle(title); 91 | } 92 | 93 | @Override 94 | public void onConfigurationChanged(Configuration newConfig) { 95 | super.onConfigurationChanged(newConfig); 96 | getDelegate().onConfigurationChanged(newConfig); 97 | } 98 | 99 | @Override 100 | protected void onStop() { 101 | super.onStop(); 102 | getDelegate().onStop(); 103 | } 104 | 105 | @Override 106 | protected void onDestroy() { 107 | super.onDestroy(); 108 | getDelegate().onDestroy(); 109 | } 110 | 111 | public void invalidateOptionsMenu() { 112 | getDelegate().invalidateOptionsMenu(); 113 | } 114 | 115 | private AppCompatDelegate getDelegate() { 116 | if (mDelegate == null) { 117 | mDelegate = AppCompatDelegate.create(this, null); 118 | } 119 | return mDelegate; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/DelayAutoCompleteTextView.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.AutoCompleteTextView; 9 | import android.widget.ProgressBar; 10 | 11 | /** 12 | * Created by sander on 20-1-17. 13 | */ 14 | public class DelayAutoCompleteTextView extends AutoCompleteTextView { 15 | 16 | private static final int MESSAGE_TEXT_CHANGED = 100; 17 | private static final int DEFAULT_AUTOCOMPLETE_DELAY = 750; 18 | 19 | private int mAutoCompleteDelay = DEFAULT_AUTOCOMPLETE_DELAY; 20 | private ProgressBar mLoadingIndicator; 21 | 22 | private final Handler mHandler = new Handler() { 23 | @Override 24 | public void handleMessage(Message msg) { 25 | DelayAutoCompleteTextView.super.performFiltering((CharSequence) msg.obj, msg.arg1); 26 | } 27 | }; 28 | 29 | public DelayAutoCompleteTextView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | } 32 | 33 | public void setLoadingIndicator(ProgressBar progressBar) { 34 | mLoadingIndicator = progressBar; 35 | } 36 | 37 | public void setAutoCompleteDelay(int autoCompleteDelay) { 38 | mAutoCompleteDelay = autoCompleteDelay; 39 | } 40 | 41 | @Override 42 | protected void performFiltering(CharSequence text, int keyCode) { 43 | if (mLoadingIndicator != null) { 44 | mLoadingIndicator.setVisibility(View.VISIBLE); 45 | } 46 | mHandler.removeMessages(MESSAGE_TEXT_CHANGED); 47 | mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_TEXT_CHANGED, text), mAutoCompleteDelay); 48 | } 49 | 50 | @Override 51 | public void onFilterComplete(int count) { 52 | if (mLoadingIndicator != null) { 53 | mLoadingIndicator.setVisibility(View.GONE); 54 | } 55 | super.onFilterComplete(count); 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/ForecastWidget.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.appwidget.AppWidgetManager; 4 | import android.appwidget.AppWidgetProvider; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | 11 | import static nl.implode.weer.ForecastWidgetService.enqueueWork; 12 | 13 | /** 14 | * Implementation of App Widget functionality. 15 | * App Widget Configuration implemented in {@link ForecastWidgetConfigureActivity ForecastWidgetConfigureActivity} 16 | */ 17 | public class ForecastWidget extends AppWidgetProvider { 18 | static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, 19 | int appWidgetId) { 20 | int[] allWidgetIds = new int[] {appWidgetId}; 21 | 22 | // Build the intent to call the service 23 | Intent intent = new Intent(context.getApplicationContext(), ForecastWidgetService.class); 24 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds); 25 | 26 | // Update the widgets via the service 27 | ForecastWidgetService.enqueueWork(context, intent); 28 | } 29 | 30 | @Override 31 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 32 | // There may be multiple widgets active, so update all of them 33 | for (int appWidgetId : appWidgetIds) { 34 | Bundle options=appWidgetManager.getAppWidgetOptions(appWidgetId); 35 | onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, options); 36 | } 37 | } 38 | 39 | @Override 40 | public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { 41 | updateAppWidget(context, appWidgetManager, appWidgetId); 42 | } 43 | 44 | @Override 45 | public void onDeleted(Context context, int[] appWidgetIds) { 46 | // When the user deletes the widget, delete the preference associated with it. 47 | for (int appWidgetId : appWidgetIds) { 48 | ForecastWidgetConfigureActivity.deletePref(context, "stationName", appWidgetId); 49 | ForecastWidgetConfigureActivity.deletePref(context, "stationCountry", appWidgetId); 50 | ForecastWidgetConfigureActivity.deletePref(context, "stationId", appWidgetId); 51 | ForecastWidgetConfigureActivity.deletePref(context, "widgetStyle", appWidgetId); 52 | } 53 | } 54 | 55 | @Override 56 | public void onEnabled(Context context) { 57 | // Enter relevant functionality for when the first widget is created 58 | } 59 | 60 | @Override 61 | public void onDisabled(Context context) { 62 | // Enter relevant functionality for when the last widget is disabled 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/ForecastWidgetConfigureActivity.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.appwidget.AppWidgetManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.os.Bundle; 10 | import android.preference.PreferenceManager; 11 | import android.util.Log; 12 | import android.view.View; 13 | import android.widget.AdapterView; 14 | import android.widget.TextView; 15 | 16 | /** 17 | * The configuration screen for the {@link ForecastWidget ForecastWidget} AppWidget. 18 | */ 19 | public class ForecastWidgetConfigureActivity extends Activity { 20 | 21 | private static final String PREFS_NAME = "nl.implode.weer.ForecastWidget"; 22 | private static final String PREF_PREFIX_KEY = "appwidget_"; 23 | int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; 24 | DelayAutoCompleteTextView mAppWidgetLocation; 25 | TextView mStationId; 26 | String stationId; 27 | String stationName; 28 | String stationCountry; 29 | 30 | protected boolean darkWidget; 31 | 32 | View.OnClickListener mOnClickListener = new View.OnClickListener() { 33 | public void onClick(View v) { 34 | final Context context = ForecastWidgetConfigureActivity.this; 35 | if (stationName != null && stationId != null) { 36 | 37 | // When the button is clicked, store the settings locally 38 | savePref(context, "stationName", mAppWidgetId, stationName); 39 | savePref(context, "stationCountry", mAppWidgetId, stationCountry); 40 | savePref(context, "stationId", mAppWidgetId, stationId); 41 | savePref(context, "widgetStyle", mAppWidgetId, darkWidget?"dark":"light"); 42 | 43 | // It is the responsibility of the configuration activity to update the app widget 44 | AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 45 | ForecastWidget.updateAppWidget(context, appWidgetManager, mAppWidgetId); 46 | 47 | // Make sure we pass back the original appWidgetId 48 | Intent resultValue = new Intent(); 49 | resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); 50 | setResult(RESULT_OK, resultValue); 51 | finish(); 52 | return; 53 | } 54 | new AlertDialog.Builder(context) 55 | .setTitle("No station selected") 56 | .setMessage("Please select a station from the list before adding widget") 57 | .setPositiveButton("OK", null) 58 | .show(); 59 | } 60 | }; 61 | 62 | public ForecastWidgetConfigureActivity() { 63 | super(); 64 | this.darkWidget = false; 65 | } 66 | 67 | // Write the prefix to the SharedPreferences object for this widget 68 | static void savePref(Context context, String tag, int appWidgetId, String text) { 69 | SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit(); 70 | prefs.putString(PREF_PREFIX_KEY + tag + appWidgetId, text); 71 | prefs.apply(); 72 | } 73 | 74 | // Read the prefix from the SharedPreferences object for this widget. 75 | // If there is no preference saved, get the default from a resource 76 | static String loadPref(Context context, String tag, int appWidgetId) { 77 | SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0); 78 | String value = prefs.getString(PREF_PREFIX_KEY + tag + appWidgetId, null); 79 | if (value != null) { 80 | return value; 81 | } else { 82 | return ""; 83 | } 84 | } 85 | 86 | static void deletePref(Context context, String tag, int appWidgetId) { 87 | SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit(); 88 | prefs.remove(PREF_PREFIX_KEY + tag + appWidgetId); 89 | prefs.apply(); 90 | } 91 | 92 | @Override 93 | public void onCreate(Bundle icicle) { 94 | super.onCreate(icicle); 95 | 96 | // Set the result to CANCELED. This will cause the widget host to cancel 97 | // out of the widget placement if the user presses the back button. 98 | setResult(RESULT_CANCELED); 99 | 100 | SharedPreferences sharedPrefs = PreferenceManager 101 | .getDefaultSharedPreferences(this); 102 | 103 | Boolean prefDarkTheme = sharedPrefs.getBoolean("use_dark_theme", false); 104 | Integer theme = R.style.AppTheme; 105 | if (prefDarkTheme) { 106 | theme = R.style.AppThemeDark; 107 | } 108 | 109 | this.setTheme(theme); 110 | 111 | setContentView(R.layout.forecast_widget_configure); 112 | final DelayAutoCompleteTextView mAppWidgetLocation = (DelayAutoCompleteTextView) findViewById(R.id.appwidget_location); 113 | mAppWidgetLocation.setThreshold(3); 114 | mAppWidgetLocation.setAdapter(new WeatherStationAutoCompleteAdapter(this)); // 'this' is Activity instance 115 | mAppWidgetLocation.setLoadingIndicator( 116 | (android.widget.ProgressBar) findViewById(R.id.pb_loading_indicator)); 117 | mAppWidgetLocation.setOnItemClickListener(new AdapterView.OnItemClickListener() { 118 | @Override 119 | public void onItemClick(AdapterView adapterView, View view, int position, long id) { 120 | WeatherStation weatherStation = (WeatherStation) adapterView.getItemAtPosition(position); 121 | mAppWidgetLocation.getText().clear(); 122 | mAppWidgetLocation.append(weatherStation.name + ", " + weatherStation.country); 123 | stationName = weatherStation.name; 124 | stationCountry = weatherStation.country; 125 | stationId = String.valueOf(weatherStation._id); 126 | } 127 | }); 128 | 129 | findViewById(R.id.add_button).setOnClickListener(mOnClickListener); 130 | 131 | // Find the widget id from the intent. 132 | Intent intent = getIntent(); 133 | Bundle extras = intent.getExtras(); 134 | if (extras != null) { 135 | mAppWidgetId = extras.getInt( 136 | AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 137 | } 138 | 139 | // If this activity was started with an intent without an app widget ID, finish with an error. 140 | if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { 141 | finish(); 142 | return; 143 | } 144 | 145 | String defaultName = loadPref(ForecastWidgetConfigureActivity.this, "stationName", mAppWidgetId); 146 | String defaultCountry =loadPref(ForecastWidgetConfigureActivity.this, "stationCountry", mAppWidgetId); 147 | if (defaultName != "" && defaultCountry != "") { 148 | mAppWidgetLocation.setText(defaultName + ", " + defaultCountry); 149 | } 150 | } 151 | } 152 | 153 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/ForecastWidgetDark.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | public class ForecastWidgetDark extends ForecastWidget {} 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/ForecastWidgetDarkConfigureActivity.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | public class ForecastWidgetDarkConfigureActivity extends ForecastWidgetConfigureActivity { 4 | public ForecastWidgetDarkConfigureActivity() { 5 | this.darkWidget = true; 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/ForecastWidgetService.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.app.PendingIntent; 4 | import android.appwidget.AppWidgetManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.res.Configuration; 9 | import android.graphics.Color; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.os.Looper; 13 | import android.os.Message; 14 | import android.preference.PreferenceManager; 15 | import android.support.annotation.NonNull; 16 | import android.support.v4.app.JobIntentService; 17 | import android.util.Log; 18 | import android.widget.RemoteViews; 19 | 20 | import org.json.JSONArray; 21 | import org.json.JSONObject; 22 | 23 | import java.text.SimpleDateFormat; 24 | import java.util.Calendar; 25 | import java.util.Date; 26 | import java.util.Iterator; 27 | import java.util.Locale; 28 | import java.util.TimeZone; 29 | 30 | import com.skedgo.converter.TimezoneMapper; 31 | 32 | public class ForecastWidgetService extends JobIntentService { 33 | 34 | public static final int JOB_ID = 1; 35 | 36 | public static void enqueueWork(Context context, Intent work) { 37 | enqueueWork(context, ForecastWidgetService.class, JOB_ID, work); 38 | } 39 | 40 | @Override 41 | protected void onHandleWork(@NonNull Intent intent) { 42 | handleStart(intent); 43 | } 44 | 45 | private Context gContext; 46 | private AppWidgetManager appWidgetManager; 47 | 48 | protected boolean dark; 49 | 50 | private int getLayout(String id) { 51 | switch(id){ 52 | case "forecast_widget": 53 | return dark ? R.layout.forecast_widget_dark : R.layout.forecast_widget; 54 | case "day": 55 | return dark ? R.layout.day_dark : R.layout.day; 56 | case "forecast": 57 | return dark ? R.layout.forecast_dark : R.layout.forecast; 58 | case "times": 59 | return dark ? R.layout.times_dark : R.layout.times; 60 | case "color_temp": 61 | return dark ? 0xFFe91e63 : Color.RED; 62 | case "color_temp_freezing": 63 | return dark ? 0xFF7dc6bf : 0xFF3F51B5; 64 | } 65 | return 0; 66 | } 67 | 68 | private String getIconImage(String iconCode) { 69 | switch(iconCode) { 70 | case "01d": return "icon_32"; 71 | case "02d": return "icon_30"; 72 | case "03d": return "icon_28"; 73 | case "04d": return "icon_26"; 74 | case "09d": return "icon_12"; 75 | case "10d": return "icon_39"; 76 | case "11d": return "icon_37"; 77 | case "13d": return "icon_41"; 78 | case "50d": return "icon_20"; 79 | 80 | case "01n": return "icon_31"; 81 | case "02n": return "icon_33"; 82 | case "03n": return "icon_27"; 83 | case "04n": return "icon_26"; 84 | case "09n": return "icon_12"; 85 | case "10n": return "icon_45"; 86 | case "11n": return "icon_47"; 87 | case "13n": return "icon_46"; 88 | case "50n": return "icon_20"; 89 | 90 | case "r": return "icon_12"; 91 | case "sn50": return "icon_14"; 92 | case "t50": return "icon_35"; 93 | case "w50": return "icon_23"; 94 | } 95 | return ""; 96 | } 97 | 98 | private void processForecasts(JSONObject forecast, Integer widgetId) { 99 | Integer maxDays = 4; 100 | CharSequence widgetStyle = ForecastWidgetConfigureActivity.loadPref(gContext, "widgetStyle", widgetId); 101 | dark = widgetStyle.equals("dark"); 102 | RemoteViews views = new RemoteViews(gContext.getPackageName(), getLayout("forecast_widget")); 103 | CharSequence stationName = ForecastWidgetConfigureActivity.loadPref(gContext, "stationName", widgetId); 104 | CharSequence stationCountry = ForecastWidgetConfigureActivity.loadPref(gContext, "stationCountry", widgetId); 105 | 106 | Bundle options = appWidgetManager.getAppWidgetOptions(widgetId); 107 | Integer minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT); 108 | Integer maxHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT); 109 | 110 | if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){ 111 | // portrait: minWidth x maxHeight 112 | maxDays = (int) Math.floor((maxHeight-55)/85); 113 | } 114 | 115 | if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 116 | // landscape: maxWidth x minHeight 117 | maxDays = (int) Math.floor((minHeight-55)/85); 118 | } 119 | 120 | views.setTextViewText(R.id.stationName, stationName); 121 | views.setTextViewText(R.id.stationCountry, stationCountry); 122 | 123 | JSONObject days = new JSONObject(); 124 | // rainScale (0 mm, 1 inch) 125 | // tempScale (0 Celsius, 1 Fahrenheit, 2 Kelvin) 126 | // time (0 24h, 1 am/pm) 127 | SharedPreferences sharedPrefs = PreferenceManager 128 | .getDefaultSharedPreferences(this); 129 | 130 | Boolean useLocalTime = sharedPrefs.getBoolean("use_local_time", false); 131 | String prefTime = sharedPrefs.getString("time", "0"); 132 | String prefTempScale = sharedPrefs.getString("temp_scale", "0"); 133 | String prefRainScale = sharedPrefs.getString("rain_scale", "0"); 134 | String timeZone = ""; 135 | 136 | try { 137 | if (forecast.has("city") && forecast.getJSONObject("city").has("coord")) { 138 | Double lat = forecast.getJSONObject("city").getJSONObject("coord").getDouble("lat"); 139 | Double lon = forecast.getJSONObject("city").getJSONObject("coord").getDouble("lon"); 140 | timeZone = TimezoneMapper.latLngToTimezoneString(lat, lon); 141 | } 142 | //only update view when we have new forecast data, preventing empty results 143 | if (forecast.has("list") && forecast.getJSONArray("list").length() > 0) { 144 | Calendar cal = Calendar.getInstance(); 145 | Date updateTime = cal.getTime(); 146 | SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.GERMANY); 147 | if (prefTime.equals("1")) { 148 | sdf = new SimpleDateFormat("KK:mm a"); 149 | } 150 | String lastUpdate = sdf.format(updateTime); 151 | 152 | views.setTextViewText(R.id.updateTime, lastUpdate); 153 | 154 | 155 | JSONArray list = forecast.getJSONArray("list"); 156 | String[] times = { 157 | "0:00", "3:00","6:00","9:00","12:00","15:00","18:00","21:00" 158 | }; 159 | Integer dayNum = 0; 160 | Integer timeNum = 0; 161 | for (int i = 0; i < list.length(); i++) { 162 | cal.setTimeInMillis(list.getJSONObject(i).getInt("dt") * 1000L); 163 | SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd"); 164 | if (useLocalTime) { 165 | sdfDate.setTimeZone(TimeZone.getTimeZone(timeZone)); 166 | } 167 | String day = sdfDate.format(cal.getTime()); 168 | if (!days.has(day)) { 169 | dayNum++; 170 | days.put(day, new JSONArray()); 171 | } 172 | if (dayNum == 2) { 173 | // gather times of second day 174 | SimpleDateFormat sdfTime = new SimpleDateFormat("H:mm"); 175 | if (prefTime.equals("1")) { 176 | sdfTime = new SimpleDateFormat("K:mma"); 177 | } 178 | if (useLocalTime) { 179 | sdfTime.setTimeZone(TimeZone.getTimeZone(timeZone)); 180 | } 181 | String time = sdfTime.format(cal.getTime()); 182 | time = time.replace("a.m.","").replace("p.m.","p"); 183 | time = time.replace("AM","").replace("PM","p"); 184 | time = time.replace("A.M.","").replace("P.M.","p"); 185 | times[timeNum] = time; 186 | timeNum++; 187 | } 188 | days.getJSONArray(day).put(list.getJSONObject(i)); 189 | } 190 | 191 | // remove old forecasts 192 | views.removeAllViews(R.id.widgetForecasts); 193 | 194 | // add times 195 | for (int l=0; l keys = days.keys(); 202 | Integer numDays = 0; 203 | while (keys.hasNext() && numDays < maxDays) { 204 | numDays++; 205 | String dayName = (String) keys.next(); 206 | SimpleDateFormat sdfDate = new SimpleDateFormat("EEEE d MMMM"); 207 | SimpleDateFormat keyDate = new SimpleDateFormat("yyyy-MM-dd"); 208 | 209 | String dayLabel = sdfDate.format(keyDate.parse(dayName)); 210 | JSONArray dayForecasts = days.getJSONArray(dayName); 211 | // add tablerow with just day name 212 | RemoteViews dayLineView = new RemoteViews(gContext.getPackageName(), getLayout("day")); 213 | dayLineView.setTextViewText(R.id.day, dayLabel); 214 | views.addView(R.id.widgetForecasts, dayLineView); 215 | 216 | if (numDays < 2) { 217 | for (int n = 0; n < (8 - dayForecasts.length()); n++) { 218 | // add empty forecast 219 | RemoteViews forecastView = new RemoteViews(gContext.getPackageName(), getLayout("forecast")); 220 | views.addView(R.id.widgetForecasts, forecastView); 221 | } 222 | } 223 | 224 | for (int j = 0; j < dayForecasts.length(); j++) { 225 | JSONObject dayForecast = dayForecasts.getJSONObject(j); 226 | Double temp = Double.valueOf(dayForecast.getJSONObject("main").getString("temp")); 227 | Boolean isFreezing = temp < 273.15; 228 | 229 | String tempSuffix = "" + (char) 0x00B0; 230 | if (prefTempScale.equals("0")) { 231 | // celsius 232 | temp = temp - 273.15; 233 | } 234 | if (prefTempScale.equals("1")) { 235 | // fahrenheit 236 | Double factor = (double)9/(double)5; 237 | temp = factor * (temp - 273.15) + 32; 238 | } 239 | if (prefTempScale.equals("2")) { 240 | // kelvin 241 | tempSuffix = "" + (char) 0x004B; 242 | } 243 | 244 | int tempColor = getLayout(isFreezing ? "color_temp_freezing" : "color_temp"); 245 | 246 | RemoteViews forecastView = new RemoteViews(gContext.getPackageName(), getLayout("forecast")); 247 | forecastView.setTextViewText(R.id.forecast_temp, String.valueOf(Math.round(temp)) + tempSuffix); 248 | 249 | forecastView.setTextColor(R.id.forecast_temp, tempColor); 250 | String rain = ""; 251 | if (dayForecast.has("rain") && dayForecast.getJSONObject("rain").has("3h")) { 252 | String sRain = dayForecast.getJSONObject("rain").getString("3h"); 253 | Float fRain = Float.valueOf(sRain); 254 | String postFix = " ㎜"; 255 | if (prefRainScale.equals("1")) { 256 | // inches 257 | postFix = "\""; 258 | fRain = fRain / new Float(2.54); 259 | } 260 | String lessThan = ""; 261 | if (fRain < 0.1) { 262 | lessThan = "<"; 263 | fRain = new Float(0.1); 264 | } 265 | rain = lessThan + String.format("%.1f", fRain) + postFix; 266 | } 267 | 268 | if (dayForecast.has("snow") && dayForecast.getJSONObject("snow").has("3h")) { 269 | String sSnow = dayForecast.getJSONObject("snow").getString("3h"); 270 | Float fSnow = Float.valueOf(sSnow); 271 | String postFix = " ㎜"; 272 | if (prefRainScale.equals("1")) { 273 | // inches 274 | postFix = "\""; 275 | fSnow = fSnow / new Float(2.54); 276 | } 277 | String lessThan = ""; 278 | if (fSnow < 0.1) { 279 | lessThan = "<"; 280 | fSnow = new Float(0.1); 281 | } 282 | rain = lessThan + String.format("%.1f", fSnow) + postFix; 283 | } 284 | forecastView.setTextViewText(R.id.forecast_rain, rain); 285 | 286 | String wind = ""; 287 | if (dayForecast.has("wind") && dayForecast.getJSONObject("wind").has("speed") && dayForecast.getJSONObject("wind").has("deg")) { 288 | String speed = dayForecast.getJSONObject("wind").getString("speed"); 289 | String deg = dayForecast.getJSONObject("wind").getString("deg"); 290 | Float fSpeed = Float.valueOf(speed); 291 | Float fDeg = Float.valueOf(deg); 292 | String bft = "0"; 293 | if (fSpeed >= 0.3 && fSpeed < 1.6) { bft = "1"; } 294 | if (fSpeed >= 1.6 && fSpeed < 3.4) { bft = "2"; } 295 | if (fSpeed >= 3.4 && fSpeed < 5.5) { bft = "3"; } 296 | if (fSpeed >= 5.5 && fSpeed < 8.0) { bft = "4"; } 297 | if (fSpeed >= 8.0 && fSpeed < 10.8) { bft = "5"; } 298 | if (fSpeed >= 10.8 && fSpeed < 13.9) { bft = "6"; } 299 | if (fSpeed >= 13.9 && fSpeed < 17.2) { bft = "7"; } 300 | if (fSpeed >= 17.2 && fSpeed < 20.8) { bft = "8"; } 301 | if (fSpeed >= 20.8 && fSpeed < 24.5) { bft = "9"; } 302 | if (fSpeed >= 24.5 && fSpeed < 28.5) { bft = "10"; } 303 | if (fSpeed >= 28.5 && fSpeed < 32.7) { bft = "11"; } 304 | if (fSpeed >= 32.7) { bft = "12"; } 305 | 306 | String dir = "wind_n"; 307 | if (fDeg >= 22.5 && fDeg < 67.5) { dir = "wind_ne"; } 308 | if (fDeg >= 67.5 && fDeg < 112.5) { dir = "wind_e"; } 309 | if (fDeg >= 112.5 && fDeg < 157.5) { dir = "wind_se"; } 310 | if (fDeg >= 157.5 && fDeg < 202.5) { dir = "wind_s"; } 311 | if (fDeg >= 202.5 && fDeg < 247.5) { dir = "wind_sw"; } 312 | if (fDeg >= 247.5 && fDeg < 292.5) { dir = "wind_w"; } 313 | if (fDeg >= 292.5 && fDeg < 337.5) { dir = "wind_nw"; } 314 | 315 | wind = (char) 0x2332 + " " + gContext.getResources().getString(gContext.getResources().getIdentifier(dir, "string", gContext.getPackageName())) + " " + bft; 316 | 317 | } 318 | forecastView.setTextViewText(R.id.forecast_wind, wind); 319 | 320 | if (dayForecast.has("weather") && dayForecast.getJSONArray("weather").length()>0) { 321 | JSONArray weather = dayForecast.getJSONArray("weather"); 322 | if (weather.getJSONObject(0).has("icon")) { 323 | String iconCode = weather.getJSONObject(0).getString("icon"); 324 | String icon = getIconImage(iconCode); 325 | forecastView.setImageViewResource(R.id.forecast_icon, gContext.getResources().getIdentifier(icon, "drawable", gContext.getPackageName())); 326 | } 327 | } 328 | 329 | views.addView(R.id.widgetForecasts, forecastView); 330 | } 331 | 332 | if (numDays > 1) { 333 | for (int n = 0; n < (8 - dayForecasts.length()); n++) { 334 | // add empty forecast 335 | RemoteViews forecastView = new RemoteViews(gContext.getPackageName(), getLayout("forecast")); 336 | views.addView(R.id.widgetForecasts, forecastView); 337 | } 338 | } 339 | } 340 | } 341 | 342 | Intent clickIntent = new Intent(gContext, ForecastWidget.class); 343 | clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); 344 | 345 | int[] appWidgetIds = new int[] {widgetId}; 346 | clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); 347 | 348 | PendingIntent pendingIntent = PendingIntent.getBroadcast(gContext, 349 | widgetId, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT); 350 | views.setOnClickPendingIntent(R.id.widgetForecasts, pendingIntent); 351 | 352 | Class configurationClass = ForecastWidgetConfigureActivity.class; 353 | if (dark) { 354 | configurationClass = ForecastWidgetDarkConfigureActivity.class; 355 | } 356 | Intent configurationIntent = new Intent(gContext, configurationClass); 357 | 358 | // Create a extra giving the App Widget Id 359 | configurationIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); 360 | 361 | // Create a pending intent giving configurationIntent as parameter 362 | PendingIntent configurationPendingIntent = PendingIntent.getActivity(gContext, 363 | widgetId, configurationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 364 | // Here we fecth the layout item and give it a action 365 | 366 | // Setting onClick event that will lauch ConfigurationActivity 367 | views.setOnClickPendingIntent(R.id.settings, configurationPendingIntent); 368 | 369 | // Instruct the widget manager to update the widget 370 | appWidgetManager.updateAppWidget(widgetId, views); 371 | }catch(Exception e) { 372 | e.printStackTrace(); 373 | } finally { 374 | 375 | } 376 | } 377 | 378 | private void handleStart(Intent intent) { 379 | appWidgetManager = AppWidgetManager.getInstance(this 380 | .getApplicationContext()); 381 | 382 | int[] allWidgetIds = intent 383 | .getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); 384 | gContext = this.getApplicationContext(); 385 | 386 | for (final int widgetId : allWidgetIds) { 387 | final String stationId = ForecastWidgetConfigureActivity.loadPref(gContext, "stationId", widgetId); 388 | new Thread() { 389 | public void run() { 390 | Looper.prepare(); 391 | JSONObject forecast = new JSONObject(); 392 | WeatherStationsDatabase weatherStationsDatabase = new WeatherStationsDatabase(gContext); 393 | WeatherStation weatherStation = null; 394 | if (!stationId.isEmpty()) { 395 | weatherStation = weatherStationsDatabase.findWeatherStation(Integer.valueOf(stationId)); 396 | forecast = weatherStation.get5DayForecast(); 397 | } 398 | Bundle bundle = new Bundle(); 399 | bundle.putString("forecast", forecast.toString()); 400 | bundle.putInt("widgetId", widgetId); 401 | Message msg = new Message(); 402 | msg.setData(bundle); 403 | 404 | Handler mHandler = new Handler(new Handler.Callback() { 405 | 406 | @Override 407 | public boolean handleMessage(Message msg) { 408 | Bundle bundle = msg.getData(); 409 | String jsonForecast = bundle.getString("forecast"); 410 | Integer mWidgetId = bundle.getInt("widgetId"); 411 | JSONObject forecast; 412 | try { 413 | forecast = new JSONObject(jsonForecast); 414 | processForecasts(forecast, mWidgetId); 415 | }catch(Exception e) { 416 | Log.d("weer", e.getMessage()); 417 | } 418 | return false; 419 | } 420 | }); 421 | mHandler.sendMessage(msg); 422 | Looper.loop(); 423 | } 424 | }.start(); 425 | } 426 | stopSelf(); 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.res.Configuration; 9 | import android.content.res.Resources; 10 | import android.media.Ringtone; 11 | import android.media.RingtoneManager; 12 | import android.net.Uri; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.preference.ListPreference; 16 | import android.preference.Preference; 17 | import android.preference.PreferenceActivity; 18 | import android.support.v7.app.ActionBar; 19 | import android.preference.PreferenceFragment; 20 | import android.preference.PreferenceManager; 21 | import android.preference.RingtonePreference; 22 | import android.text.TextUtils; 23 | import android.util.Log; 24 | import android.view.MenuItem; 25 | 26 | import java.util.List; 27 | 28 | import static android.preference.PreferenceManager.getDefaultSharedPreferences; 29 | 30 | /** 31 | * A {@link PreferenceActivity} that presents a set of application settings. On 32 | * handset devices, settings are presented as a single list. On tablets, 33 | * settings are split by category, with category headers shown to the left of 34 | * the list of settings. 35 | *

36 | * See 37 | * Android Design: Settings for design guidelines and the Settings 39 | * API Guide for more information on developing a Settings UI. 40 | */ 41 | public class SettingsActivity extends AppCompatPreferenceActivity { 42 | /** 43 | * A preference value change listener that updates the preference's summary 44 | * to reflect its new value. 45 | */ 46 | private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { 47 | @Override 48 | public boolean onPreferenceChange(Preference preference, Object value) { 49 | String stringValue = value.toString(); 50 | 51 | if (preference instanceof ListPreference) { 52 | // For list preferences, look up the correct display value in 53 | // the preference's 'entries' list. 54 | ListPreference listPreference = (ListPreference) preference; 55 | int index = listPreference.findIndexOfValue(stringValue); 56 | 57 | // Set the summary to reflect the new value. 58 | preference.setSummary( 59 | index >= 0 60 | ? listPreference.getEntries()[index] 61 | : null); 62 | 63 | } else { 64 | // For all other preferences, set the summary to the value's 65 | // simple string representation. 66 | preference.setSummary(stringValue); 67 | } 68 | return true; 69 | } 70 | }; 71 | 72 | /** 73 | * Helper method to determine if the device has an extra-large screen. For 74 | * example, 10" tablets are extra-large. 75 | */ 76 | private static boolean isXLargeTablet(Context context) { 77 | return (context.getResources().getConfiguration().screenLayout 78 | & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; 79 | } 80 | 81 | /** 82 | * Binds a preference's summary to its value. More specifically, when the 83 | * preference's value is changed, its summary (line of text below the 84 | * preference title) is updated to reflect the value. The summary is also 85 | * immediately updated upon calling this method. The exact display format is 86 | * dependent on the type of preference. 87 | * 88 | * @see #sBindPreferenceSummaryToValueListener 89 | */ 90 | private static void bindPreferenceSummaryToValue(Preference preference) { 91 | // Set the listener to watch for value changes. 92 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); 93 | 94 | // Trigger the listener immediately with the preference's 95 | // current value. 96 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, 97 | getDefaultSharedPreferences(preference.getContext()) 98 | .getString(preference.getKey(), "")); 99 | } 100 | 101 | @Override 102 | protected void onCreate(Bundle savedInstanceState) { 103 | super.onCreate(savedInstanceState); 104 | setupActionBar(); 105 | } 106 | 107 | /** 108 | * Set up the {@link android.app.ActionBar}, if the API is available. 109 | */ 110 | private void setupActionBar() { 111 | ActionBar actionBar = getSupportActionBar(); 112 | if (actionBar != null) { 113 | // Show the Up button in the action bar. 114 | actionBar.setDisplayHomeAsUpEnabled(true); 115 | } 116 | } 117 | 118 | /** 119 | * {@inheritDoc} 120 | */ 121 | @Override 122 | public boolean onIsMultiPane() { 123 | return isXLargeTablet(this); 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | */ 129 | @Override 130 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 131 | public void onBuildHeaders(List

target) { 132 | int headerFile = R.xml.pref_headers; 133 | SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); 134 | Boolean prefDarkTheme = sharedPrefs.getBoolean("use_dark_theme", false); 135 | 136 | if (prefDarkTheme) { 137 | headerFile = R.xml.pref_headers_dark; 138 | } 139 | loadHeadersFromResource(headerFile, target); 140 | } 141 | 142 | /** 143 | * This method stops fragment injection in malicious applications. 144 | * Make sure to deny any unknown fragments here. 145 | */ 146 | protected boolean isValidFragment(String fragmentName) { 147 | return PreferenceFragment.class.getName().equals(fragmentName) 148 | || GeneralPreferenceFragment.class.getName().equals(fragmentName); 149 | } 150 | 151 | /** 152 | * This fragment shows general preferences only. It is used when the 153 | * activity is showing a two-pane settings UI. 154 | */ 155 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 156 | public static class GeneralPreferenceFragment extends PreferenceFragment { 157 | @Override 158 | public void onCreate(Bundle savedInstanceState) { 159 | super.onCreate(savedInstanceState); 160 | addPreferencesFromResource(R.xml.pref_general); 161 | setHasOptionsMenu(true); 162 | 163 | // Bind the summaries of EditText/List/Dialog/Ringtone preferences 164 | // to their values. When their values change, their summaries are 165 | // updated to reflect the new value, per the Android Design 166 | // guidelines. 167 | bindPreferenceSummaryToValue(findPreference("time")); 168 | bindPreferenceSummaryToValue(findPreference("temp_scale")); 169 | bindPreferenceSummaryToValue(findPreference("rain_scale")); 170 | } 171 | 172 | @Override 173 | public boolean onOptionsItemSelected(MenuItem item) { 174 | int id = item.getItemId(); 175 | if (id == android.R.id.home) { 176 | startActivity(new Intent(getActivity(), SettingsActivity.class)); 177 | return true; 178 | } 179 | return super.onOptionsItemSelected(item); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/WeatherStation.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.os.StrictMode; 4 | import android.util.Log; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.util.ArrayList; 11 | 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.Request; 14 | import okhttp3.Response; 15 | 16 | /** 17 | * Created by sander on 19-1-17. 18 | */ 19 | public class WeatherStation { 20 | public Integer _id; 21 | public String name; 22 | public String country; 23 | public String latitude; 24 | public String longitude; 25 | 26 | public WeatherStation(Integer id, String name, String country, String latitude, String longitude) { 27 | this._id = id; 28 | this.name = name; 29 | this.country = country; 30 | this.latitude = latitude; 31 | this.longitude = longitude; 32 | } 33 | 34 | // Constructor to convert JSON object into a Java class instance 35 | public WeatherStation(JSONObject object){ 36 | try { 37 | Log.d("nl.implode.weer", object.getString("name")); 38 | this._id = object.getInt("_id"); 39 | this.name = object.getString("name"); 40 | this.country = object.getString("country"); 41 | this.longitude = "0"; 42 | this.latitude = "0"; 43 | } catch (JSONException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | public JSONObject get5DayForecast() { 49 | JSONObject jsonResult = new JSONObject(); 50 | 51 | StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); 52 | StrictMode.setThreadPolicy(policy); 53 | String apiEntryPoint = "https://api.openweathermap.org/data/2.5/forecast?"; 54 | String appId = "fbc3d19917801786e46dbacd55d2ee9c"; 55 | Integer maxResults = 10; 56 | 57 | String url = apiEntryPoint + "appid=" + appId + "&id=" + _id; 58 | OkHttpClient client = new OkHttpClient(); 59 | Request request = new Request.Builder().url(url).build(); 60 | String result = ""; 61 | try { 62 | Response response = client.newCall(request).execute(); 63 | result = response.body().string(); 64 | jsonResult = new JSONObject(result); 65 | } catch(Exception e) { 66 | e.printStackTrace(); 67 | } 68 | 69 | return jsonResult; 70 | } 71 | 72 | // Factory method to convert an array of JSON objects into a list of objects 73 | // User.fromJson(jsonArray); 74 | public static ArrayList fromJson(JSONArray jsonObjects) { 75 | ArrayList weatherStations = new ArrayList(); 76 | for (int i = 0; i < jsonObjects.length(); i++) { 77 | try { 78 | weatherStations.add(new WeatherStation(jsonObjects.getJSONObject(i))); 79 | } catch (JSONException e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | return weatherStations; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/WeatherStationAutoCompleteAdapter.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.graphics.Paint; 9 | import android.net.Uri; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.BaseAdapter; 14 | import android.widget.Button; 15 | import android.widget.Filter; 16 | import android.widget.Filterable; 17 | import android.widget.TextView; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by sander on 20-1-17. 24 | */ 25 | public class WeatherStationAutoCompleteAdapter extends BaseAdapter implements Filterable { 26 | 27 | private static final int MAX_RESULTS = 20; 28 | private Context mContext; 29 | private List resultList = new ArrayList(); 30 | 31 | public WeatherStationAutoCompleteAdapter(Context context) { 32 | mContext = context; 33 | } 34 | 35 | @Override 36 | public int getCount() { 37 | return resultList.size(); 38 | } 39 | 40 | @Override 41 | public WeatherStation getItem(int index) { 42 | return resultList.get(index); 43 | } 44 | 45 | @Override 46 | public long getItemId(int position) { 47 | return position; 48 | } 49 | 50 | @Override 51 | public View getView(int position, View convertView, ViewGroup parent) { 52 | if (convertView == null) { 53 | LayoutInflater inflater = (LayoutInflater) mContext 54 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 55 | //convertView = inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); 56 | convertView = inflater.inflate(R.layout.autocomplete_line, parent, false); 57 | } 58 | TextView text1 = (TextView) convertView.findViewById(android.R.id.text1); 59 | TextView text2 = (TextView) convertView.findViewById(android.R.id.text2); 60 | // Populate the data into the template view using the data object 61 | text1.setText(getItem(position).name + ", "+getItem(position).country); 62 | text2.setText(getItem(position).latitude + ", " + getItem(position).longitude); 63 | text2.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG); 64 | final String lat = getItem(position).latitude; 65 | final String lon = getItem(position).longitude; 66 | text2.setOnClickListener(new View.OnClickListener() { 67 | @Override 68 | public void onClick(View v) { 69 | 70 | String url = "https://www.openstreetmap.org/#map=11/"+lat+"/"+lon; 71 | 72 | Intent i = new Intent(Intent.ACTION_VIEW); 73 | i.setData(Uri.parse(url)); 74 | mContext.startActivity(i); 75 | } 76 | }); 77 | 78 | return convertView; 79 | } 80 | 81 | @Override 82 | public Filter getFilter() { 83 | Filter filter = new Filter() { 84 | @Override 85 | protected Filter.FilterResults performFiltering(CharSequence constraint) { 86 | Filter.FilterResults filterResults = new Filter.FilterResults(); 87 | if (constraint != null) { 88 | List weatherStations = findWeatherStations(mContext, constraint.toString()); 89 | 90 | // Assign the data to the FilterResults 91 | filterResults.values = weatherStations; 92 | filterResults.count = weatherStations.size(); 93 | } 94 | return filterResults; 95 | } 96 | 97 | @Override 98 | protected void publishResults(CharSequence constraint, Filter.FilterResults results) { 99 | if (results != null && results.count > 0) { 100 | resultList = (List) results.values; 101 | notifyDataSetChanged(); 102 | } else { 103 | notifyDataSetInvalidated(); 104 | } 105 | }}; 106 | return filter; 107 | } 108 | 109 | /** 110 | * Returns a search result for the given a name. 111 | */ 112 | private List findWeatherStations(Context context, String name) { 113 | WeatherStationsDatabase weatherStationsDatabase = new WeatherStationsDatabase(context); 114 | SQLiteDatabase db = weatherStationsDatabase.getReadableDatabase(); 115 | String[] parts = name.split(","); 116 | String cityOrLat = parts[0]; 117 | String countryOrLon = ""; 118 | if (parts.length>1) { 119 | countryOrLon = parts[1].trim(); 120 | } 121 | Cursor cursor = db.rawQuery("SELECT * FROM cities" + 122 | " WHERE (name LIKE ? AND country LIKE ?)" + 123 | " OR (lat LIKE ? AND lon LIKE ?) LIMIT 20;", 124 | new String[] { 125 | "%"+cityOrLat+"%", 126 | "%"+countryOrLon+"%", 127 | cityOrLat+"%", 128 | countryOrLon+"%" 129 | }); 130 | List searchResults = new ArrayList(); 131 | try { 132 | while (cursor.moveToNext()) { 133 | searchResults.add(new WeatherStation( 134 | cursor.getInt(cursor.getColumnIndex("_id")), 135 | cursor.getString(cursor.getColumnIndex("name")), 136 | cursor.getString(cursor.getColumnIndex("country")), 137 | cursor.getString(cursor.getColumnIndex("lat")), 138 | cursor.getString(cursor.getColumnIndex("lon")) 139 | )); 140 | } 141 | } finally { 142 | cursor.close(); 143 | db.close(); 144 | } 145 | return searchResults; 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /app/src/main/java/nl/implode/weer/WeatherStationsDatabase.java: -------------------------------------------------------------------------------- 1 | package nl.implode.weer; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.database.sqlite.SQLiteDatabase; 6 | 7 | import com.readystatesoftware.sqliteasset.SQLiteAssetHelper; 8 | 9 | /** 10 | * Created by sander on 23-1-17. 11 | */ 12 | public class WeatherStationsDatabase extends SQLiteAssetHelper { 13 | 14 | private static final String DATABASE_NAME = "cities.db"; 15 | private static final int DATABASE_VERSION = 1; 16 | 17 | public WeatherStationsDatabase(Context context) { 18 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 19 | } 20 | 21 | public WeatherStation findWeatherStation(int id) { 22 | SQLiteDatabase db = super.getReadableDatabase(); 23 | Cursor cursor = db.rawQuery("SELECT * FROM cities WHERE _id="+id+";", null); 24 | WeatherStation weatherStation = null; 25 | try { 26 | while (cursor.moveToNext()) { 27 | weatherStation = new WeatherStation( 28 | cursor.getInt(cursor.getColumnIndex("_id")), 29 | cursor.getString(cursor.getColumnIndex("name")), 30 | cursor.getString(cursor.getColumnIndex("country")), 31 | cursor.getString(cursor.getColumnIndex("lat")), 32 | cursor.getString(cursor.getColumnIndex("lon") 33 | )); 34 | } 35 | } finally { 36 | cursor.close(); 37 | db.close(); 38 | } 39 | return weatherStation; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v21/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_info_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_map.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_settings_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_settings_white_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_info_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_map.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_settings_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_settings_white_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_12.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_14.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_20.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_23.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_26.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_27.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_28.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_30.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_31.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_32.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_33.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_35.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_37.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_39.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_41.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_45.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_46.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon_47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-mdpi/icon_47.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/forecastwidget_dark_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-nodpi/forecastwidget_dark_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/forecastwidget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-nodpi/forecastwidget_preview.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_sync_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_info_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_map.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_settings_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_settings_white_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_info_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_map.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_settings_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings_white_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_settings_white_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_info_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxxhdpi/ic_info_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxxhdpi/ic_map.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxxhdpi/ic_notifications_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderbaas/WeatherWidget/2d416b597d4722d4a229222642b5eb5af468d5ae/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/layout/autocomplete_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/day.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/day_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/forecast.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 18 | 24 | 25 | 32 | 33 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/forecast_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 18 | 24 | 25 | 32 | 33 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/forecast_widget.xml: -------------------------------------------------------------------------------- 1 | 8 | 18 | 19 | 28 | 29 | 39 | 40 | 48 | 54 |