├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── w01d.png
│ │ │ │ ├── w01n.png
│ │ │ │ ├── w02d.png
│ │ │ │ ├── w03d.png
│ │ │ │ ├── w04d.png
│ │ │ │ ├── w11d.png
│ │ │ │ ├── w13d.png
│ │ │ │ ├── w212d.png
│ │ │ │ ├── w500d.png
│ │ │ │ ├── w501d.png
│ │ │ │ └── w50d.png
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.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
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── java
│ │ │ └── net
│ │ │ │ └── bonysoft
│ │ │ │ └── magicmirror
│ │ │ │ ├── modules
│ │ │ │ ├── DashboardModule.java
│ │ │ │ ├── DashboardModuleComposite.java
│ │ │ │ ├── weather
│ │ │ │ │ ├── DisabledWeatherModule.java
│ │ │ │ │ ├── WeatherInfo.java
│ │ │ │ │ ├── WeatherIconMapper.java
│ │ │ │ │ └── WeatherModule.java
│ │ │ │ └── time
│ │ │ │ │ └── TimeModule.java
│ │ │ │ ├── MagicMirrorApplication.java
│ │ │ │ ├── BootReceiver.java
│ │ │ │ ├── SystemUIHider.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── net
│ │ │ └── bonysoft
│ │ │ └── magicmirror
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── net
│ │ └── bonysoft
│ │ └── magicmirror
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle.properties.example
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gradle.properties.example:
--------------------------------------------------------------------------------
1 | # https://home.openweathermap.org/api_keys
2 | openWeatherMapKey=1234567890qwertyuipo
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w01d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w01d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w01n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w01n.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w02d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w02d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w03d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w03d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w04d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w04d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w11d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w11d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w13d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w13d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w212d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w212d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w500d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w500d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w501d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w501d.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/w50d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/drawable/w50d.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 64dp
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danybony/magic-mirror-dashboard/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/DashboardModule.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules;
2 |
3 | public interface DashboardModule {
4 |
5 | void update();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Magic Mirror Dashboard
4 |
5 | (%1$s° | %2$s°)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 21 11:34:03 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #3F51B5
5 | #303F9F
6 | #FF4081
7 |
8 | #000000
9 | #ffffff
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/net/bonysoft/magicmirror/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/bonysoft/magicmirror/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
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 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/MagicMirrorApplication.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
2 |
3 | import android.app.Application;
4 |
5 | import com.novoda.notils.logger.simple.Log;
6 |
7 | import net.danlew.android.joda.JodaTimeAndroid;
8 |
9 | public class MagicMirrorApplication extends Application {
10 |
11 | @Override
12 | public void onCreate() {
13 | super.onCreate();
14 | Log.setShowLogs(BuildConfig.DEBUG);
15 | JodaTimeAndroid.init(this);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 | 108sp
8 | 40sp
9 | 48sp
10 | 26sp
11 | 150dp
12 | 150dp
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/DashboardModuleComposite.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules;
2 |
3 | import java.util.List;
4 |
5 | public class DashboardModuleComposite implements DashboardModule {
6 |
7 | private final List modules;
8 |
9 | public DashboardModuleComposite(List modules) {
10 | this.modules = modules;
11 | }
12 |
13 | @Override
14 | public void update() {
15 | for (DashboardModule module : modules) {
16 | module.update();
17 | }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/BootReceiver.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 |
7 | public class BootReceiver extends BroadcastReceiver {
8 |
9 | @Override
10 | public void onReceive(Context context, Intent intent) {
11 | Intent mainActivityIntent = new Intent(context, MainActivity.class);
12 | mainActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
13 | context.startActivity(mainActivityIntent);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/weather/DisabledWeatherModule.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules.weather;
2 |
3 | import net.bonysoft.magicmirror.modules.DashboardModule;
4 |
5 | class DisabledWeatherModule implements DashboardModule {
6 |
7 | private final WeatherModule.WeatherListener weatherListener;
8 |
9 | DisabledWeatherModule(WeatherModule.WeatherListener weatherListener) {
10 | this.weatherListener = weatherListener;
11 | }
12 |
13 | @Override
14 | public void update() {
15 | weatherListener.onCurrentWeatherUnavailable();
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/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 /Users/daniele/Downloads/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 | gradle.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # IDEA/Android Studio ignores
30 | .idea/
31 | *.iml
32 | *.ipr
33 | *.iws
34 | **/.idea/workspace.xml
35 | **/.idea/tasks.xml
36 | **/.idea/datasources.xml
37 | **/.idea/dataSources.ids
38 | **/.idea/gradle.xml
39 | **/.idea/libraries
40 | **/.idea/dictionaries
41 |
42 | # OSX
43 | *.DS_Store
44 |
45 | # Google play publishing plugin
46 | mobile/src/main/play/
47 |
48 | # Temp local file
49 | team-props/api/response.xml
50 | test-acceptance/.calabash_settings
51 |
52 | # Generated during build
53 | com_crashlytics_export_strings.xml
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # magic-mirror-dashboard
2 | Make your mirror even smarter!
3 | This app is meant to be installed on phones, tablets or TV sticks used in smart mirrors or similar always-on dashboards.
4 |
5 | ## Development
6 | In order to use all the features of the app, when building it from source, please make a copy of the `gradle.properties.example` file, rename it to `gradle.properties` and add the required keys.
7 |
8 | ## License
9 |
10 | ```
11 | Copyright 2016 Daniele Bonaldo
12 |
13 | Licensed under the Apache License, Version 2.0 (the "License");
14 | you may not use this file except in compliance with the License.
15 | You may obtain a copy of the License at
16 |
17 | http://www.apache.org/licenses/LICENSE-2.0
18 |
19 | Unless required by applicable law or agreed to in writing, software
20 | distributed under the License is distributed on an "AS IS" BASIS,
21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 | See the License for the specific language governing permissions and
23 | limitations under the License.
24 |
25 | ```
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
15 |
16 |
19 |
20 |
23 |
24 |
25 |
26 |
29 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/weather/WeatherInfo.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules.weather;
2 |
3 | public class WeatherInfo {
4 |
5 | private final float temperature;
6 | private final float minTemperature;
7 | private final float maxTemperature;
8 | private final String condition;
9 | private final String iconId;
10 |
11 | public WeatherInfo(float temperature, float minTemperature, float maxTemperature, String condition, String iconId) {
12 | this.temperature = temperature;
13 | this.minTemperature = minTemperature;
14 | this.maxTemperature = maxTemperature;
15 | this.condition = condition;
16 | this.iconId = iconId;
17 | }
18 |
19 | public float getTemperature() {
20 | return temperature;
21 | }
22 |
23 | public float getMinTemperature() {
24 | return minTemperature;
25 | }
26 |
27 | public float getMaxTemperature() {
28 | return maxTemperature;
29 | }
30 |
31 | public String getCondition() {
32 | return condition;
33 | }
34 |
35 | public String getIconId() {
36 | return iconId;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | def openWeatherMapKey = hasProperty('openWeatherMapKey') ? openWeatherMapKey : 'provide this in gradle.properties'
4 |
5 | android {
6 | compileSdkVersion 23
7 | buildToolsVersion "23.0.2"
8 |
9 | defaultConfig {
10 | applicationId "net.bonysoft.magicmirror"
11 | minSdkVersion 16
12 | targetSdkVersion 23
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | buildConfigField "String", "OPENWEATHERMAP_API_KEY", "\"$openWeatherMapKey\""
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(dir: 'libs', include: ['*.jar'])
28 | testCompile 'junit:junit:4.12'
29 | compile 'com.android.support:appcompat-v7:23.1.1'
30 | compile 'com.novoda:notils:2.2.15'
31 | compile 'net.danlew:android.joda:2.9.3'
32 |
33 | // Weatherlib
34 | compile 'com.survivingwithandroid:weatherlib:1.6.0'
35 | compile 'com.survivingwithandroid:weatherlib_okhttpclient:1.6.0'
36 | compile 'com.squareup.okhttp:okhttp:2.0.0'
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/weather/WeatherIconMapper.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules.weather;
2 |
3 | import net.bonysoft.magicmirror.R;
4 |
5 |
6 | public class WeatherIconMapper {
7 |
8 | public static int getWeatherResource(String id) {
9 | if (id.equals("01d"))
10 | return R.drawable.w01d;
11 | else if (id.equals("01n"))
12 | return R.drawable.w01n;
13 | else if (id.equals("02d") || id.equals("02n"))
14 | return R.drawable.w02d;
15 | else if (id.equals("03d") || id.equals("03n"))
16 | return R.drawable.w03d;
17 | else if (id.equals("03d") || id.equals("03n"))
18 | return R.drawable.w03d;
19 | else if (id.equals("04d") || id.equals("04n"))
20 | return R.drawable.w04d;
21 | else if (id.equals("09d") || id.equals("09n"))
22 | return R.drawable.w500d;
23 | else if (id.equals("10d") || id.equals("10n"))
24 | return R.drawable.w501d;
25 | else if (id.equals("11d") || id.equals("11n"))
26 | return R.drawable.w212d;
27 | else if (id.equals("13d") || id.equals("13n"))
28 | return R.drawable.w13d;
29 | else if (id.equals("50d") || id.equals("50n"))
30 | return R.drawable.w50d;
31 |
32 | return R.drawable.w01d;
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/SystemUIHider.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
2 |
3 | import android.os.Build;
4 | import android.view.View;
5 |
6 | public final class SystemUIHider {
7 |
8 | private final View view;
9 |
10 | public SystemUIHider(View view) {
11 | this.view = view;
12 | }
13 |
14 | public void hideSystemUi() {
15 | view.setSystemUiVisibility(getFlags());
16 | view.invalidate();
17 | }
18 |
19 | private int getFlags() {
20 | int fullScreenFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
21 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
22 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
23 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
24 | | View.SYSTEM_UI_FLAG_FULLSCREEN;
25 |
26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
27 | fullScreenFlags = fullScreenFlags | View.SYSTEM_UI_FLAG_IMMERSIVE;
28 | }
29 | return fullScreenFlags;
30 | }
31 |
32 | public void showSystemUi() {
33 | view.setSystemUiVisibility(
34 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
35 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
36 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
37 | );
38 | view.invalidate();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/time/TimeModule.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules.time;
2 |
3 | import android.widget.TextView;
4 |
5 | import net.bonysoft.magicmirror.modules.DashboardModule;
6 |
7 | import org.joda.time.DateTime;
8 | import org.joda.time.format.DateTimeFormatter;
9 | import org.joda.time.format.DateTimeFormatterBuilder;
10 |
11 | public class TimeModule implements DashboardModule {
12 |
13 | private static final DateTimeFormatter HOUR_MINUTES_FORMATTER = new DateTimeFormatterBuilder()
14 | .appendHourOfDay(2)
15 | .appendLiteral(':')
16 | .appendMinuteOfHour(2)
17 | .toFormatter();
18 |
19 | private static final DateTimeFormatter DAY_DAYMONTH_MONTH = new DateTimeFormatterBuilder()
20 | .appendDayOfWeekShortText()
21 | .appendLiteral(" ")
22 | .appendDayOfMonth(1)
23 | .appendLiteral(" ")
24 | .appendMonthOfYearShortText()
25 | .toFormatter();
26 |
27 | private final TextView timeLabel;
28 | private final TextView dateLabel;
29 |
30 | public TimeModule(TextView timeLabel, TextView dateLabel) {
31 | this.timeLabel = timeLabel;
32 | this.dateLabel = dateLabel;
33 | }
34 |
35 | @Override
36 | public void update() {
37 | DateTime dateTime = DateTime.now();
38 | String formattedTime = HOUR_MINUTES_FORMATTER.print(dateTime);
39 | timeLabel.setText(formattedTime);
40 |
41 | String formattedDate = DAY_DAYMONTH_MONTH.print(dateTime);
42 | dateLabel.setText(formattedDate);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
23 |
24 |
30 |
31 |
32 |
33 |
38 |
39 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.IntentFilter;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.view.View;
11 | import android.view.WindowManager;
12 | import android.widget.ImageView;
13 | import android.widget.TextView;
14 |
15 | import com.novoda.notils.caster.Views;
16 |
17 | import net.bonysoft.magicmirror.modules.DashboardModule;
18 | import net.bonysoft.magicmirror.modules.DashboardModuleComposite;
19 | import net.bonysoft.magicmirror.modules.time.TimeModule;
20 | import net.bonysoft.magicmirror.modules.weather.WeatherIconMapper;
21 | import net.bonysoft.magicmirror.modules.weather.WeatherInfo;
22 | import net.bonysoft.magicmirror.modules.weather.WeatherModule;
23 |
24 | import java.math.RoundingMode;
25 | import java.text.DecimalFormat;
26 | import java.util.ArrayList;
27 | import java.util.List;
28 |
29 | public class MainActivity extends AppCompatActivity {
30 |
31 | private SystemUIHider systemUIHider;
32 |
33 | private TextView timeLabel;
34 | private TextView dateLabel;
35 | private TextView weatherTemperatureLabel;
36 | private TextView todayForecastLabel;
37 | private DashboardModule modules;
38 | private ImageView todayForecastIcon;
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_main);
44 |
45 | timeLabel = Views.findById(this, R.id.label_time);
46 | dateLabel = Views.findById(this, R.id.label_date);
47 | weatherTemperatureLabel = Views.findById(this, R.id.label_weather_temperature);
48 | todayForecastLabel = Views.findById(this, R.id.label_today_forecast);
49 | todayForecastIcon = Views.findById(this, R.id.weather_icon);
50 |
51 | systemUIHider = new SystemUIHider(findViewById(android.R.id.content));
52 | keepScreenOn();
53 |
54 | createModules();
55 | }
56 |
57 | private void keepScreenOn() {
58 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
59 | }
60 |
61 | private void createModules() {
62 | List modulesList = new ArrayList<>();
63 | modulesList.add(new TimeModule(timeLabel, dateLabel));
64 | modulesList.add(WeatherModule.newInstance(this, weatherListener));
65 |
66 | modules = new DashboardModuleComposite(modulesList);
67 | }
68 |
69 | @Override
70 | protected void onResume() {
71 | super.onResume();
72 | registerTimeTickReceiver();
73 | updateModules();
74 | systemUIHider.hideSystemUi();
75 | }
76 |
77 | private void registerTimeTickReceiver() {
78 | IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_TICK);
79 | registerReceiver(timeTickReceiver, intentFilter);
80 | }
81 |
82 | private void updateModules() {
83 | modules.update();
84 | }
85 |
86 | @Override
87 | protected void onPause() {
88 | super.onPause();
89 | unregisterReceiver(timeTickReceiver);
90 | systemUIHider.showSystemUi();
91 | }
92 |
93 | private final BroadcastReceiver timeTickReceiver = new BroadcastReceiver() {
94 | @Override
95 | public void onReceive(Context context, Intent intent) {
96 | updateModules();
97 | }
98 | };
99 |
100 | private final WeatherModule.WeatherListener weatherListener = new WeatherModule.WeatherListener() {
101 |
102 | @Override
103 | public void onCurrentWeatherFetched(WeatherInfo currentInfo) {
104 | DecimalFormat temperatureFormat = getTemperatureFormat();
105 | String currentTemperature = temperatureFormat.format(currentInfo.getTemperature()) + "°";
106 | weatherTemperatureLabel.setVisibility(View.VISIBLE);
107 | weatherTemperatureLabel.setText(currentTemperature);
108 | }
109 |
110 | @Override
111 | public void onTodayForecastFetched(WeatherInfo todayInfo) {
112 | DecimalFormat temperatureFormat = getTemperatureFormat();
113 | String minTemperature = temperatureFormat.format(todayInfo.getMinTemperature());
114 | String maxTemperature = temperatureFormat.format(todayInfo.getMaxTemperature());
115 | String todayCondition = getString(R.string.weather_condition, minTemperature, maxTemperature);
116 | todayForecastLabel.setVisibility(View.VISIBLE);
117 | todayForecastLabel.setText(todayCondition);
118 |
119 | int weatherResource = WeatherIconMapper.getWeatherResource(todayInfo.getIconId());
120 | todayForecastIcon.setImageResource(weatherResource);
121 | }
122 |
123 | @Override
124 | public void onCurrentWeatherUnavailable() {
125 | weatherTemperatureLabel.setVisibility(View.GONE);
126 | }
127 |
128 | @Override
129 | public void onTodayForecastUnavailable() {
130 | todayForecastLabel.setVisibility(View.GONE);
131 | }
132 | };
133 |
134 | @NonNull
135 | private DecimalFormat getTemperatureFormat() {
136 | DecimalFormat temperatureFormat = new DecimalFormat("#.#");
137 | temperatureFormat.setRoundingMode(RoundingMode.HALF_DOWN);
138 | return temperatureFormat;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bonysoft/magicmirror/modules/weather/WeatherModule.java:
--------------------------------------------------------------------------------
1 | package net.bonysoft.magicmirror.modules.weather;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 |
6 | import com.novoda.notils.logger.simple.Log;
7 | import com.survivingwithandroid.weather.lib.WeatherClient;
8 | import com.survivingwithandroid.weather.lib.WeatherConfig;
9 | import com.survivingwithandroid.weather.lib.exception.WeatherLibException;
10 | import com.survivingwithandroid.weather.lib.exception.WeatherProviderInstantiationException;
11 | import com.survivingwithandroid.weather.lib.model.CurrentWeather;
12 | import com.survivingwithandroid.weather.lib.model.DayForecast;
13 | import com.survivingwithandroid.weather.lib.model.Weather;
14 | import com.survivingwithandroid.weather.lib.model.WeatherForecast;
15 | import com.survivingwithandroid.weather.lib.provider.openweathermap.OpenweathermapProviderType;
16 | import com.survivingwithandroid.weather.lib.request.WeatherRequest;
17 |
18 | import java.util.concurrent.TimeUnit;
19 |
20 | import net.bonysoft.magicmirror.BuildConfig;
21 | import net.bonysoft.magicmirror.modules.DashboardModule;
22 |
23 | public class WeatherModule implements DashboardModule {
24 |
25 | private static final long MIN_UPDATE_DELAY = TimeUnit.MINUTES.toMillis(10);
26 | private static final String API_KEY = BuildConfig.OPENWEATHERMAP_API_KEY;
27 |
28 | private static final double LONDON_LONGITUDE = -0.0956086;
29 | private static final double LONDON_LATITUDE = 51.541549;
30 |
31 | private final WeatherClient client;
32 | private final WeatherListener weatherListener;
33 | private long lastUpdate = 0;
34 |
35 | public static DashboardModule newInstance(Context context, WeatherListener weatherListener) {
36 | WeatherClient.ClientBuilder builder = new WeatherClient.ClientBuilder();
37 | WeatherConfig config = buildWeatherConfig();
38 |
39 | try {
40 | WeatherClient client = builder.attach(context)
41 | .provider(new OpenweathermapProviderType())
42 | .httpClient(com.survivingwithandroid.weather.lib.client.okhttp.WeatherDefaultClient.class)
43 | .config(config)
44 | .build();
45 |
46 | return new WeatherModule(client, weatherListener);
47 |
48 | } catch (WeatherProviderInstantiationException e) {
49 | Log.e(e, "Error creating weather client");
50 | return new DisabledWeatherModule(weatherListener);
51 | }
52 | }
53 |
54 | @NonNull
55 | private static WeatherConfig buildWeatherConfig() {
56 | WeatherConfig config = new WeatherConfig();
57 | config.unitSystem = WeatherConfig.UNIT_SYSTEM.M;
58 | config.lang = "en";
59 | config.maxResult = 5;
60 | config.numDays = 6;
61 | config.ApiKey = API_KEY;
62 | return config;
63 | }
64 |
65 | public WeatherModule(WeatherClient client, WeatherListener weatherListener) {
66 | this.client = client;
67 | this.weatherListener = weatherListener;
68 | }
69 |
70 | @Override
71 | public void update() {
72 | if (tooCloseToLastUpdate()) {
73 | return;
74 | }
75 | lastUpdate = System.currentTimeMillis();
76 |
77 | // TODO: get current devicePosition
78 | double longitude = LONDON_LONGITUDE;
79 | double latitude = LONDON_LATITUDE;
80 | WeatherRequest request = new WeatherRequest(longitude, latitude);
81 |
82 | client.getCurrentCondition(request, new CurrentWeatherListener(weatherListener));
83 | client.getForecastWeather(request, new TodayForecastListener(weatherListener));
84 | }
85 |
86 | private boolean tooCloseToLastUpdate() {
87 | long now = System.currentTimeMillis();
88 | return now - lastUpdate < MIN_UPDATE_DELAY;
89 | }
90 |
91 | private static class CurrentWeatherListener implements WeatherClient.WeatherEventListener {
92 |
93 | private final WeatherListener weatherListener;
94 |
95 | private CurrentWeatherListener(WeatherListener weatherListener) {
96 | this.weatherListener = weatherListener;
97 | }
98 |
99 | @Override
100 | public void onWeatherRetrieved(CurrentWeather weather) {
101 | Weather.Temperature temperature = weather.weather.temperature;
102 | WeatherInfo currentWeather = new WeatherInfo(
103 | temperature.getTemp(),
104 | temperature.getMinTemp(),
105 | temperature.getMaxTemp(),
106 | weather.weather.currentCondition.getCondition(),
107 | weather.weather.currentCondition.getIcon());
108 | weatherListener.onCurrentWeatherFetched(currentWeather);
109 | }
110 |
111 | @Override
112 | public void onWeatherError(WeatherLibException e) {
113 | Log.e(e, "Error while fetching weather conditions");
114 | weatherListener.onCurrentWeatherUnavailable();
115 | }
116 |
117 | @Override
118 | public void onConnectionError(Throwable e) {
119 | Log.e(e, "Connection error while fetching weather conditions");
120 | weatherListener.onCurrentWeatherUnavailable();
121 | }
122 | }
123 |
124 | private static class TodayForecastListener implements WeatherClient.ForecastWeatherEventListener {
125 |
126 | private final WeatherListener weatherListener;
127 |
128 | private TodayForecastListener(WeatherListener weatherListener) {
129 | this.weatherListener = weatherListener;
130 | }
131 |
132 | @Override
133 | public void onWeatherRetrieved(WeatherForecast forecast) {
134 | DayForecast todayForecast = forecast.getForecast(0);
135 |
136 | WeatherInfo todayWeather = new WeatherInfo(
137 | todayForecast.forecastTemp.day,
138 | todayForecast.forecastTemp.min,
139 | todayForecast.forecastTemp.max,
140 | todayForecast.weather.currentCondition.getCondition(),
141 | todayForecast.weather.currentCondition.getIcon()
142 | );
143 | weatherListener.onTodayForecastFetched(todayWeather);
144 | }
145 |
146 | @Override
147 | public void onWeatherError(WeatherLibException e) {
148 | Log.e(e, "Error while fetching weather forecast");
149 | weatherListener.onTodayForecastUnavailable();
150 | }
151 |
152 | @Override
153 | public void onConnectionError(Throwable e) {
154 | Log.e(e, "Error while fetching weather forecast");
155 | weatherListener.onTodayForecastUnavailable();
156 | }
157 | }
158 |
159 | public interface WeatherListener {
160 |
161 | void onCurrentWeatherFetched(WeatherInfo currentInfo);
162 |
163 | void onTodayForecastFetched(WeatherInfo todayInfo);
164 |
165 | void onCurrentWeatherUnavailable();
166 |
167 | void onTodayForecastUnavailable();
168 |
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------