Skip to content

Commit b16cf12

Browse files
committed
Separate Mote model and API use from UI Fragment and improve error handling
1 parent cbf890e commit b16cf12

12 files changed

Lines changed: 568 additions & 142 deletions

File tree

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/proguard-rules.pro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
#}
1818

1919
# Application classes that will be serialized/deserialized over Gson
20-
-keep class com.earthstormsoftware.motecontrol.MoteStatus { *; }
21-
-keep interface com.earthstormsoftware.motecontrol.MoteAPI { *; }
20+
-keep class com.earthstormsoftware.motecontrol.com.earthstormsoftware.motecontrol.moteutil.MoteAPIResponse { *; }
21+
-keep interface com.earthstormsoftware.motecontrol.com.earthstormsoftware.motecontrol.moteutil.MoteAPI { *; }
2222

2323
# Rules for using the Support classes
2424
-keep public class android.support.v7.widget.** { *; }

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66

77
<application
8+
android:name="com.earthstormsoftware.motecontrol.MoteControl"
89
android:allowBackup="true"
910
android:icon="@mipmap/ic_launcher"
1011
android:label="@string/app_name"

app/src/main/java/com/earthstormsoftware/motecontrol/AboutFragment.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import android.widget.TextView;
2727

2828
/**
29-
* A placeholder fragment containing a simple view.
29+
* This fragment displays information about the app itself, including version and builds.
3030
*/
3131
public class AboutFragment extends Fragment {
3232

@@ -63,9 +63,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
6363
tvVersionCode.setText(getString(R.string.lblVersionCode) + ": " + Integer.toString(iVersionCode));
6464

6565
} catch (PackageManager.NameNotFoundException e) {
66-
tvAppVersion.setText("Version not known");
67-
tvAppBuild.setText("Build not known");
68-
tvVersionCode.setText("VersionCode not known");
66+
tvAppVersion.setText(R.string.version_not_known);
67+
tvAppBuild.setText(R.string.build_not_known);
68+
tvVersionCode.setText(R.string.versioncode_not_known);
6969
}
7070
return rootView;
7171
}

app/src/main/java/com/earthstormsoftware/motecontrol/MainActivityFragment.java

Lines changed: 115 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616

1717
package com.earthstormsoftware.motecontrol;
1818

19+
import android.content.BroadcastReceiver;
20+
import android.content.Context;
21+
import android.content.Intent;
22+
import android.content.IntentFilter;
1923
import android.content.SharedPreferences;
2024
import android.graphics.Color;
2125
import android.graphics.drawable.GradientDrawable;
2226
import android.preference.PreferenceManager;
2327
import android.support.v4.app.Fragment;
2428
import android.os.Bundle;
29+
import android.util.Log;
2530
import android.view.LayoutInflater;
2631
import android.view.View;
2732
import android.view.ViewGroup;
@@ -30,27 +35,25 @@
3035
import android.widget.Toast;
3136
import android.widget.ToggleButton;
3237

33-
import okhttp3.OkHttpClient;
34-
import okhttp3.logging.HttpLoggingInterceptor;
35-
import retrofit2.Call;
36-
import retrofit2.Callback;
37-
import retrofit2.Response;
38-
import retrofit2.Retrofit;
39-
import retrofit2.converter.gson.GsonConverterFactory;
38+
import com.earthstormsoftware.motecontrol.com.earthstormsoftware.motecontrol.moteutil.Mote;
39+
import com.earthstormsoftware.motecontrol.com.earthstormsoftware.motecontrol.moteutil.MoteAPIResponseType;
40+
import com.earthstormsoftware.motecontrol.com.earthstormsoftware.motecontrol.moteutil.MoteMode;
4041

4142
/*
42-
Fragment used to display the main UI. Using fragments is generally considered good practice,
43-
even though the initial UI is quite simple.
43+
* Fragment used to display the main UI. Using fragments is generally considered good practice,
44+
* even though the initial UI is quite simple.
4445
*/
4546

4647
public class MainActivityFragment extends Fragment {
4748

4849
private String moteURI;
4950

51+
private Mote mote;
5052
private ToggleButton tglMoteSwitch;
5153
private Button btnColourPicker;
5254
private int initialPickerColor;
5355

56+
private BroadcastReceiver moteUpdateReceiver;
5457
private ColorPickerDialog colorPickerDialog;
5558

5659
public MainActivityFragment() {
@@ -65,16 +68,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
6568

6669
// Retrieve the URL of the Mote API from device storage
6770
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
68-
moteURI = prefs.getString("mote_uri", "http://127.0.0.1");
71+
moteURI = prefs.getString("mote_uri", null);
72+
73+
// Create the local Mote object and get the current status of the Mote from the host device
74+
if (moteURI != null){
75+
mote = new Mote(moteURI,"1","Mote1",false, MoteMode.COLOUR);
76+
mote.updateMoteStatus();
77+
} else {
78+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
79+
}
6980

7081
// Setup the colour picker dialog that will be called when the user clicks the button
7182
initialPickerColor = Color.WHITE;
7283
colorPickerDialog = new ColorPickerDialog(getActivity(), initialPickerColor, new ColorPickerDialog.OnColorSelectedListener() {
7384

7485
@Override
7586
public void onColorSelected(int color) {
76-
GradientDrawable bgShape = (GradientDrawable)btnColourPicker.getBackground();
77-
bgShape.setColor(color);
87+
Log.i(MoteControl.TAG,"Color selected: " + color);
88+
if (mote != null) {
89+
mote.setColour(color);
90+
}
7891
setMoteColour(color);
7992
}
8093
});
@@ -84,152 +97,126 @@ public void onColorSelected(int color) {
8497
btnColourPicker.setOnClickListener(new View.OnClickListener() {
8598
@Override
8699
public void onClick(View view) {
87-
colorPickerDialog.show();
100+
if (mote != null) {
101+
colorPickerDialog.show();
102+
} else {
103+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
104+
}
105+
88106
}
89107
});
90108

91-
92109
// Setup the toggle button which will turn the Mote on and off
93110
tglMoteSwitch = (ToggleButton) view.findViewById(R.id.tglMoteSwitch);
94111
tglMoteSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
95112
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
96-
if (isChecked) {
97-
toggleMoteStatus(true);
113+
if (mote != null) {
114+
if (isChecked) {
115+
setMoteState(true);
116+
} else {
117+
setMoteState(false);
118+
}
98119
} else {
99-
toggleMoteStatus(false);
120+
if (isChecked) {
121+
tglMoteSwitch.setChecked(false);
122+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
123+
}
100124
}
101125
}
102126
});
103-
104-
// Try and get the current status of the Mote
105-
updateMoteStatus();
106-
107127
return view;
108128
}
109129

110-
// Call the Mote API to get the current state and colour of the Mote
111-
public void updateMoteStatus(){
130+
@Override
131+
public void onResume() {
132+
super.onResume();
133+
134+
/*
135+
* When the Fragment is visible and active, use a BroadcastReceiver to receive notifications
136+
* when an API response is received so the displayed information can be updated.
137+
*/
138+
if (moteUpdateReceiver == null){
139+
moteUpdateReceiver = new BroadcastReceiver() {
140+
@Override
141+
public void onReceive(Context context, Intent intent) {
142+
MoteAPIResponseType mrt = (MoteAPIResponseType) intent.getSerializableExtra("result");
143+
if (mrt == MoteAPIResponseType.OK) {
144+
updateDisplay();
145+
} else {
146+
Toast.makeText(getActivity(), mrt.toString(), Toast.LENGTH_SHORT).show();
147+
}
148+
}
149+
};
150+
Log.i(MoteControl.TAG,"BroadcastReceiver created");
151+
}
152+
IntentFilter intent = new IntentFilter(MoteControl.MOTE_API_RESPONSE);
153+
getActivity().registerReceiver(moteUpdateReceiver, intent);
154+
Log.i(MoteControl.TAG,"BroadcastReceiver registered");
112155

113-
// Uncomment to enable Retrofit logging
114-
//HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
115-
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
116-
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
117-
//httpClient.addInterceptor(logging);
156+
// Update the display whenever the user returns to this fragment
157+
updateDisplay();
158+
}
118159

119-
Retrofit retrofit = new Retrofit.Builder()
120-
.baseUrl(moteURI)
121-
.addConverterFactory(GsonConverterFactory.create())
122-
.client(httpClient.build())
123-
.build();
160+
@Override
161+
public void onPause() {
162+
super.onPause();
124163

125-
MoteAPI moteAPI = retrofit.create(MoteAPI.class);
164+
// If the user navigates away from the fragment, unregister the BroadcastReveiver as
165+
// notifications are not required.
166+
getActivity().unregisterReceiver(moteUpdateReceiver);
167+
Log.i(MoteControl.TAG,"BroadcastReceiver unregistered");
168+
}
126169

127-
// This is where the API actually gets called.
128-
// Note: Using Enqueue means this is an asynchronous call, and not handled on the UI thread.
129-
final Call<MoteStatus> call = moteAPI.getMoteStatus();
130-
call.enqueue(new Callback<MoteStatus>() {
131-
@Override
132-
public void onResponse(Call<MoteStatus> call, Response<MoteStatus> response) {
170+
// Update the UI elements based on the current known state
171+
private void updateDisplay(){
133172

134-
// Change the toggle switch setting depending on the Mote status
135-
if (response.body().getStatus() == 1){
136-
tglMoteSwitch.setChecked(true);
137-
} else {
138-
tglMoteSwitch.setChecked(false);
139-
}
173+
Log.i(MoteControl.TAG,"Updating UI based on current status");
140174

141-
// Change the colour button depending on the Mote colour/
142-
int curColour = Color.parseColor("#" + response.body().getColour());
143-
GradientDrawable bgShape = (GradientDrawable)btnColourPicker.getBackground();
144-
bgShape.setColor(curColour);
175+
if (mote != null) {
176+
// Change the toggle switch setting depending on the Mote status
177+
if (mote.isOn()){
178+
tglMoteSwitch.setChecked(true);
179+
} else {
180+
tglMoteSwitch.setChecked(false);
145181
}
146182

147-
@Override
148-
public void onFailure(Call<MoteStatus> call, Throwable t) {
149-
// If the API call fails for any reason a short toast will be popped up.
150-
Toast.makeText(getActivity(), R.string.txt_mote_api_error, Toast.LENGTH_SHORT).show();
151-
}
152-
});
183+
// Change the colour button depending on the Mote colour/
184+
GradientDrawable bgShape = (GradientDrawable)btnColourPicker.getBackground();
185+
bgShape.setColor(mote.getColour());
186+
}
153187
}
154188

155-
// Call the Mote API to set the current state (on or off) of the Mote
156-
public void toggleMoteStatus(boolean newState){
157-
158-
//HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
159-
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
160-
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
161-
//httpClient.addInterceptor(logging);
162-
163-
Retrofit retrofit = new Retrofit.Builder()
164-
.baseUrl(moteURI)
165-
.addConverterFactory(GsonConverterFactory.create())
166-
.client(httpClient.build())
167-
.build();
168-
169-
MoteAPI moteAPI = retrofit.create(MoteAPI.class);
170-
171-
// Call different API methods for turning on and off
172-
Call<MoteStatus> call;
173-
if (newState == true) {
174-
call = moteAPI.setMoteOn();
189+
// Call the Mote API to get the current status of the Mote from the host device
190+
public void updateMoteStatus(){
191+
if (mote != null){
192+
Log.i(MoteControl.TAG,"Requesting Mote status update");
193+
mote.updateMoteStatus();
194+
updateDisplay();
175195
} else {
176-
call = moteAPI.setMoteOff();
196+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
177197
}
198+
}
178199

179-
call.enqueue(new Callback<MoteStatus>() {
180-
@Override
181-
public void onResponse(Call<MoteStatus> call, Response<MoteStatus> response) {
182-
183-
// If the call worked, the toggle switch will already have been set to the desired
184-
// state, so we just need to update the current colour, which is provided in the
185-
// response
186-
int curColour = Color.parseColor("#" + response.body().getColour());
187-
GradientDrawable bgShape = (GradientDrawable)btnColourPicker.getBackground();
188-
bgShape.setColor(curColour);
189-
}
190-
191-
@Override
192-
public void onFailure(Call<MoteStatus> call, Throwable t) {
193-
Toast.makeText(getActivity(), R.string.txt_mote_api_error, Toast.LENGTH_SHORT).show();
194-
}
195-
});
200+
// Call the Mote API to set the current state (on or off) on the Mote device
201+
public void setMoteState(boolean newState){
202+
if (mote != null){
203+
Log.i(MoteControl.TAG,"Setting new Mote state");
204+
mote.setMoteState(newState);
205+
updateDisplay();
206+
} else {
207+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
208+
}
196209
}
197210

198-
// Call the Mote API to set the desired colour of the Mote
211+
// Call the Mote API to set the desired colour of the Mote on the host device
199212
public void setMoteColour(int newColour){
213+
if (mote != null){
214+
Log.i(MoteControl.TAG,"Setting new Mote colour");
215+
mote.setMoteColour();
216+
updateDisplay();
217+
} else {
218+
Toast.makeText(getActivity(), R.string.configure_api_url, Toast.LENGTH_SHORT).show();
219+
}
200220

201-
//HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
202-
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
203-
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
204-
//httpClient.addInterceptor(logging);
205-
206-
Retrofit retrofit = new Retrofit.Builder()
207-
.baseUrl(moteURI)
208-
.addConverterFactory(GsonConverterFactory.create())
209-
.client(httpClient.build())
210-
.build();
211-
212-
MoteAPI moteAPI = retrofit.create(MoteAPI.class);
213-
214-
// Android stores colour as ints, but the API expects RGB values in the form RRGGBB, so we
215-
// need to convert before calling the API
216-
String strColour = String.format("%06X", (0xFFFFFF & newColour));
217-
final Call<MoteStatus> call = moteAPI.setMoteColour(strColour);
218-
219-
call.enqueue(new Callback<MoteStatus>() {
220-
@Override
221-
public void onResponse(Call<MoteStatus> call, Response<MoteStatus> response) {
222-
223-
// The state (on or off) was not changed, so just update the colour
224-
int curColour = Color.parseColor("#" + response.body().getColour());
225-
GradientDrawable bgShape = (GradientDrawable)btnColourPicker.getBackground();
226-
bgShape.setColor(curColour);
227-
}
228-
229-
@Override
230-
public void onFailure(Call<MoteStatus> call, Throwable t) {
231-
Toast.makeText(getActivity(), R.string.txt_mote_api_error, Toast.LENGTH_SHORT).show();
232-
}
233-
});
234221
}
235222
}

0 commit comments

Comments
 (0)