Widget Viewmodel updated to use live data

Widget activity, dialog and viewmodel cleaned up
folder structure changed
removal of legacy java code
This commit is contained in:
2020-05-17 14:15:23 +01:00
parent f1a4f9cd52
commit 2e59ea07f9
40 changed files with 437 additions and 1246 deletions

View File

@@ -1,8 +1,6 @@
package com.appttude.h_mal.easycc; package com.appttude.h_mal.easycc;
import org.junit.Before;
public class MainActivityTest { public class MainActivityTest {

View File

@@ -13,14 +13,14 @@
android:anyDensity="true" /> android:anyDensity="true" />
<application <application
android:name=".mvvm.application.AppClass" android:name="com.appttude.h_mal.easycc.application.AppClass"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<receiver android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetKotlin"> <receiver android:name="com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
@@ -30,12 +30,12 @@
android:resource="@xml/currency_app_widget_info" /> android:resource="@xml/currency_app_widget_info" />
</receiver> </receiver>
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"> <activity android:name="com.appttude.h_mal.easycc.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity"> <activity android:name="com.appttude.h_mal.easycc.ui.main.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -1,13 +1,13 @@
package com.appttude.h_mal.easycc.mvvm.application package com.appttude.h_mal.easycc.application
import android.app.Application import android.app.Application
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
import com.appttude.h_mal.easycc.mvvm.data.network.interceptors.NetworkConnectionInterceptor import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.interceptors.QueryInterceptor import com.appttude.h_mal.easycc.data.network.interceptors.QueryInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
import com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModelFactory import com.appttude.h_mal.easycc.ui.main.MainViewModelFactory
import com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModelFactory import com.appttude.h_mal.easycc.ui.widget.WidgetViewModelFactory
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule import org.kodein.di.android.x.androidXModule
@@ -18,9 +18,11 @@ import org.kodein.di.generic.singleton
class AppClass : Application(), KodeinAware { class AppClass : Application(), KodeinAware {
// Kodein Dependecy Injection created in Application class
override val kodein by Kodein.lazy { override val kodein by Kodein.lazy {
import(androidXModule(this@AppClass)) import(androidXModule(this@AppClass))
// instance() can be context or other binding created
bind() from singleton { NetworkConnectionInterceptor(instance()) } bind() from singleton { NetworkConnectionInterceptor(instance()) }
bind() from singleton { QueryInterceptor() } bind() from singleton { QueryInterceptor() }
bind() from singleton { CurrencyApi(instance(),instance()) } bind() from singleton { CurrencyApi(instance(),instance()) }

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.data.network package com.appttude.h_mal.easycc.data.network
import android.util.Log import android.util.Log
import org.json.JSONException import org.json.JSONException

View File

@@ -1,8 +1,8 @@
package com.appttude.h_mal.easycc.mvvm.data.network.api package com.appttude.h_mal.easycc.data.network.api
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.interceptors.NetworkConnectionInterceptor import com.appttude.h_mal.easycc.data.network.interceptors.QueryInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.interceptors.QueryInterceptor import com.appttude.h_mal.easycc.data.network.response.ResponseObject
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Response import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
@@ -10,20 +10,29 @@ import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
/**
* Retrofit2 Network class to create network requests
*/
interface CurrencyApi { interface CurrencyApi {
// Get rate from server with arguments passed in Repository
@GET("convert?")
suspend fun getCurrencyRate(@Query("q") currency: String): Response<ResponseObject>
// interface invokation to be used in application class
companion object{ companion object{
operator fun invoke( operator fun invoke(
networkConnectionInterceptor: NetworkConnectionInterceptor, networkConnectionInterceptor: NetworkConnectionInterceptor,
queryInterceptor: QueryInterceptor queryInterceptor: QueryInterceptor
) : CurrencyApi{ ) : CurrencyApi{
// okkHttpclient with injected interceptors
val okkHttpclient = OkHttpClient.Builder() val okkHttpclient = OkHttpClient.Builder()
.addInterceptor(queryInterceptor) .addInterceptor(queryInterceptor)
.addNetworkInterceptor(networkConnectionInterceptor) .addNetworkInterceptor(networkConnectionInterceptor)
.build() .build()
// Build retrofit
return Retrofit.Builder() return Retrofit.Builder()
.client(okkHttpclient) .client(okkHttpclient)
.baseUrl("https://free.currencyconverterapi.com/api/v3/") .baseUrl("https://free.currencyconverterapi.com/api/v3/")
@@ -33,7 +42,6 @@ interface CurrencyApi {
} }
} }
@GET("convert?")
suspend fun getCurrencyRate(@Query("q") currency: String): Response<ResponseObject>
} }

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.data.network.interceptors package com.appttude.h_mal.easycc.data.network.interceptors
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager

View File

@@ -1,8 +1,5 @@
package com.appttude.h_mal.easycc.mvvm.data.network.interceptors package com.appttude.h_mal.easycc.data.network.interceptors
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import com.appttude.h_mal.easycc.BuildConfig import com.appttude.h_mal.easycc.BuildConfig
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor

View File

@@ -1,6 +1,6 @@
package com.appttude.h_mal.easycc.mvvm.data.network.response package com.appttude.h_mal.easycc.data.network.response
import com.appttude.h_mal.easycc.mvvm.models.CurrencyObject import com.appttude.h_mal.easycc.models.CurrencyObject
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
class ResponseObject( class ResponseObject(

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.data.prefs package com.appttude.h_mal.easycc.data.prefs
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences

View File

@@ -1,6 +1,6 @@
package com.appttude.h_mal.easycc.mvvm.data.repository package com.appttude.h_mal.easycc.data.repository
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.network.response.ResponseObject
/** /**
* Main entry point for accessing currency data. * Main entry point for accessing currency data.

View File

@@ -1,12 +1,12 @@
package com.appttude.h_mal.easycc.mvvm.data.repository package com.appttude.h_mal.easycc.data.repository
import android.content.Context import android.content.Context
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.R import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.network.SafeApiRequest
import com.appttude.h_mal.easycc.mvvm.data.network.SafeApiRequest import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi import com.appttude.h_mal.easycc.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.mvvm.utils.convertPairsListToString import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.utils.convertPairsListToString
/** /**
* Default implementation of [Repository]. Single entry point for managing currency' data. * Default implementation of [Repository]. Single entry point for managing currency' data.

View File

@@ -1,161 +0,0 @@
package com.appttude.h_mal.easycc.legacy;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import android.widget.RemoteViews;
import com.appttude.h_mal.easycc.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import static com.appttude.h_mal.easycc.legacy.CurrencyAppWidgetConfigureActivity.loadTitlePref;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.UriBuilder;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.createUrl;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.makeHttpRequest;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.round;
/**
* Implementation of App Widget functionality.
* App Widget Configuration implemented in {@link CurrencyAppWidgetConfigureActivity CurrencyAppWidgetConfigureActivity}
*/
public class CurrencyAppWidget extends AppWidgetProvider {
static String LOG_TAG = CurrencyAppWidget.class.getSimpleName();
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
String s1 = loadTitlePref(context,appWidgetId,0);
String s2 = loadTitlePref(context,appWidgetId,1);
String URL = UriBuilder(s1,s2);
WidgetAsyncTask widgetAsyncTask = new WidgetAsyncTask(context,appWidgetId,appWidgetManager,s1.substring(0,3),s2.substring(0,3));
widgetAsyncTask.execute(URL);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// When the user deletes the widget, delete the preference associated with it.
for (int appWidgetId : appWidgetIds) {
CurrencyAppWidgetConfigureActivity.deleteTitlePref(context, appWidgetId);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
private static class WidgetAsyncTask extends AsyncTask<String, Void, Double> {
private Context context;
private int appWidgetId;
private AppWidgetManager appWidgetManager;
private String s1;
private String s2;
public WidgetAsyncTask(Context context, int appWidgetId, AppWidgetManager appWidgetManager, String s1, String s2) {
this.context = context;
this.appWidgetId = appWidgetId;
this.appWidgetManager = appWidgetManager;
this.s1 = s1;
this.s2 = s2;
}
@Override
protected Double doInBackground(String... urlString) {
String jsonResponse = null;
if (urlString.length < 1 || urlString[0] == null) {
return null;
}
try {
URL url = createUrl(urlString[0]);
jsonResponse = makeHttpRequest(url);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), "Problem making the HTTP request.", e);
}
return extractFeatureFromJson(jsonResponse, s1, s2);
}
@Override
protected void onPostExecute(Double result) {
super.onPostExecute(result);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.currency_app_widget);
views.setTextViewText(R.id.exchangeName, s1 + s2);
views.setTextViewText(R.id.exchangeRate, round(result,2)+"");
float opacity = 0.3f; //opacity = 0: fully transparent, opacity = 1: no transparancy
int backgroundColor = 0x000000; //background color (here black)
views.setInt( R.id.widget_view, "setBackgroundColor", (int)(opacity * 0xFF) << 24 | backgroundColor);
Intent clickIntentTemplate = new Intent(context, MainActivityJava.class);
clickIntentTemplate.setAction(Intent.ACTION_MAIN);
clickIntentTemplate.addCategory(Intent.CATEGORY_LAUNCHER);
clickIntentTemplate.putExtra("parse_1",s1);
clickIntentTemplate.putExtra("parse_2",s2);
clickIntentTemplate.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent configPendingIntent = PendingIntent.getActivity(context, 0, clickIntentTemplate, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
private static double extractFeatureFromJson(String newsJSON, String s1, String s2) {
double conversionValue = 0.00;
Log.i(LOG_TAG, "extractFeatureFromJson: " + newsJSON);
Log.i(LOG_TAG, "extractFeatureFromJson: " + s1 + "_" + s2);
if (TextUtils.isEmpty(newsJSON)) {
return 0.00;
}
try {
JSONObject jObject = new JSONObject(newsJSON);
conversionValue = jObject.getDouble(s1 + "_" + s2);
} catch (JSONException e) {
Log.e("MainActivityJava", "Problem parsing the JSON results", e);
}
return conversionValue;
}
}
}

View File

@@ -1,227 +0,0 @@
package com.appttude.h_mal.easycc.legacy;
import android.app.Activity;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import com.appttude.h_mal.easycc.R;
/**
* The configuration screen for the {@link CurrencyAppWidget CurrencyAppWidget} AppWidget.
*/
public class CurrencyAppWidgetConfigureActivity extends Activity {
private static final String PREFS_NAME = "com.example.haimalik.myapplication.CurrencyAppWidget";
private static final String PREF_PREFIX_KEY = "appwidget_";
private static final int PRIMARY = 0;
private static final int SECONDARY = 1;
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
View.OnClickListener mOnClickListener = new View.OnClickListener() {
public void onClick(View v) {
final Context context = CurrencyAppWidgetConfigureActivity.this;
final Dialog dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
dialog.setCancelable(false);
dialog.setContentView(R.layout.confirm_dialog);
TextView text = (TextView) dialog.findViewById(R.id.confirm_text);
text.setText(new StringBuilder().append("Create widget for ")
.append(loadTitlePref(context, mAppWidgetId, PRIMARY))
.append(loadTitlePref(context, mAppWidgetId, SECONDARY))
.append("?").toString());
TextView yes = dialog.findViewById(R.id.confirm_yes);
yes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//do for yes
// It is the responsibility of the configuration activity to update the app widget
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
CurrencyAppWidget.updateAppWidget(context, appWidgetManager, mAppWidgetId);
// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
});
TextView no = dialog.findViewById(R.id.confirm_no);
no.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
dialog.show();
}
};
public CurrencyAppWidgetConfigureActivity() {
super();
}
// Write the prefix to the SharedPreferences object for this widget
static void saveCurrencyPref(Context context, int appWidgetId, String text, int item) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_" + item, text);
prefs.apply();
}
// Read the prefix from the SharedPreferences object for this widget.
// If there is no preference saved, get the default from a resource
public static String loadTitlePref(Context context, int appWidgetId, int item) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
String titleValue = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_" + item, null);
if (titleValue != null) {
return titleValue;
} else {
return context.getResources().getStringArray(R.array.currency_arrays)[0].substring(0,3);
}
}
static void deleteTitlePref(Context context, int appWidgetId) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.remove(PREF_PREFIX_KEY+ appWidgetId + "_0");
prefs.remove(PREF_PREFIX_KEY+ appWidgetId + "_1");
prefs.apply();
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if the user presses the back button.
setResult(RESULT_CANCELED);
setContentView(R.layout.currency_app_widget_configure);
final TextView currencyOne = findViewById(R.id.currency_one);
final TextView currencyTwo = findViewById(R.id.currency_two);
TextView submit = findViewById(R.id.submit_widget);
// Find the widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If this activity was started with an intent without an app widget ID, finish with an error.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
return;
}
currencyOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(CurrencyAppWidgetConfigureActivity.this,currencyOne,PRIMARY);
dialogClass.show();
}
});
currencyTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(CurrencyAppWidgetConfigureActivity.this,currencyTwo,SECONDARY);
dialogClass.show();
}
});
submit.setOnClickListener(mOnClickListener);
}
private class CustomDialogClass extends Dialog implements android.view.View.OnClickListener{
Context context;
ListView listView;
TextView textView;
EditText editText;
int item;
public CustomDialogClass(@NonNull Context context, TextView textView, int item) {
super(context);
this.context = context;
this.textView = textView;
this.item = item;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_dialog);
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
listView = (ListView) findViewById(R.id.list_view);
editText = (EditText) findViewById(R.id.search_text) ;
final ArrayAdapter<CharSequence> arrayAdapter = ArrayAdapter.createFromResource(context,R.array.currency_arrays,android.R.layout.simple_list_item_1);
listView.setAdapter(arrayAdapter);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
arrayAdapter.getFilter().filter(charSequence);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
String text = adapterView.getItemAtPosition(i).toString().substring(0,3);
textView.setText(adapterView.getItemAtPosition(i).toString());
saveCurrencyPref(context,mAppWidgetId,text,item);
dismiss();
}
});
}
@Override
public void onClick(View view) {
}
}
}

View File

@@ -1,378 +0,0 @@
package com.appttude.h_mal.easycc.legacy;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.appttude.h_mal.easycc.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import java.text.DecimalFormat;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.UriBuilder;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.createUrl;
import static com.appttude.h_mal.easycc.legacy.PublicMethods.makeHttpRequest;
public class MainActivityJava extends AppCompatActivity {
EditText currencyOneEditText;
EditText currencyTwoEditText;
TextView currencyOne;
TextView currencyTwo;
double conversionRateOne;
ProgressBar spinner;
LinearLayout wholeView;
private String URL = "https://free.currencyconverterapi.com/api/v3/convert?";
private String CURRENCY_ONE = "currency_one_pref";
private String CURRENCY_TWO = "currency_two_pref";
private static final String LOG_TAG = MainActivityJava.class.getSimpleName();
SharedPreferences pref;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pref = getApplicationContext().getSharedPreferences("MyPref", MODE_PRIVATE);
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
currencyOneEditText = (EditText) findViewById(R.id.topInsertValue);
currencyTwoEditText = (EditText) findViewById(R.id.bottomInsertValues);
currencyOne = (TextView) findViewById(R.id.currency_one);
currencyTwo = (TextView) findViewById(R.id.currency_two);
if (getIntent().getExtras() != null) {
Bundle b = getIntent().getExtras();
currencyOne.setText(arrayEntry(b.getString("parse_1")));
currencyTwo.setText(arrayEntry(b.getString("parse_2")));
}else{
if (pref != null) {
currencyOne.setText(pref.getString(CURRENCY_ONE, String.valueOf(getResources().getTextArray(R.array.currency_arrays)[0])));
currencyTwo.setText(pref.getString(CURRENCY_TWO, String.valueOf(getResources().getTextArray(R.array.currency_arrays)[0])));
}else{
currencyOne.setText(getResources().getTextArray(R.array.currency_arrays)[0]);
currencyTwo.setText(getResources().getTextArray(R.array.currency_arrays)[0]);
}
}
currencyOneEditText.addTextChangedListener(TextWatcherClass);
currencyTwoEditText.addTextChangedListener(TextWatcherClass2);
spinner = (ProgressBar) findViewById(R.id.progressBar);
spinner.setVisibility(View.GONE);
wholeView = findViewById(R.id.whole_view);
// addListenerOnSpinnerItemSelection();
currencyOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(MainActivityJava.this,currencyOne);
dialogClass.show();
}
});
currencyTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(MainActivityJava.this,currencyTwo);
dialogClass.show();
}
});
String stringURL = UriBuilder(currencyOne.getText().toString().substring(0,3),
currencyTwo.getText().toString().substring(0,3));
MyAsyncTask task = new MyAsyncTask();
task.execute(stringURL);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (getIntent().getExtras() != null) {
Bundle b = getIntent().getExtras();
currencyOne.setText(arrayEntry(b.getString("parse_1")));
currencyTwo.setText(arrayEntry(b.getString("parse_2")));
}
}
private TextWatcher TextWatcherClass = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
currencyTwoEditText.removeTextChangedListener(TextWatcherClass2);
if(currencyOneEditText.getText().toString().isEmpty()){
currencyTwoEditText.setText("");
return;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
try{
Double topValue = Double.parseDouble(currencyOneEditText.getText().toString());
Double bottomValue = topValue * conversionRateOne;
DecimalFormat df = new DecimalFormat("#.##");
bottomValue = Double.valueOf(df.format(bottomValue));
currencyTwoEditText.setText(bottomValue.toString());
}
catch (NumberFormatException e){
Log.e(LOG_TAG, "no numbers inserted");
}
currencyTwoEditText.addTextChangedListener(TextWatcherClass2);
}
};
private TextWatcher TextWatcherClass2 = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
currencyOneEditText.removeTextChangedListener(TextWatcherClass);
if(currencyTwoEditText.getText().toString().isEmpty()){
currencyOneEditText.setText("");
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void afterTextChanged(Editable s) {
try{
Double bottomValue = Double.parseDouble(currencyTwoEditText.getText().toString());
Double topValue = bottomValue * (1 / conversionRateOne);
DecimalFormat df = new DecimalFormat("#.##");
topValue = Double.valueOf(df.format(topValue));
currencyOneEditText.setText(topValue.toString());
}
catch (NumberFormatException e){
Log.e(LOG_TAG, "no numbers inserted");
}
currencyOneEditText.addTextChangedListener(TextWatcherClass);
}
};
private double extractFeatureFromJson(String newsJSON) {
double conversionValue = 0.00;
Log.i(LOG_TAG, "extractFeatureFromJson: " + newsJSON);
String currencyOneVal = currencyOne.getText().toString().substring(0,3);
String currencyTwoVal = currencyTwo.getText().toString().substring(0,3);
Log.i(LOG_TAG, "extractFeatureFromJson: " + currencyOneVal + "_" + currencyTwoVal);
if (TextUtils.isEmpty(newsJSON)) {
return 0.00;
}
try {
JSONObject jObject = new JSONObject(newsJSON);
conversionValue = jObject.getDouble(currencyOneVal + "_" + currencyTwoVal);
} catch (JSONException e) {
Log.e("MainActivityJava", "Problem parsing the JSON results", e);
}
return conversionValue;
}
class MyAsyncTask extends AsyncTask<String, Void, Double> {
@Override
public Double doInBackground(String... urlString) {
String jsonResponse = null;
if (urlString.length < 1 || urlString[0] == null) {
return null;
}
try {
URL url = createUrl(urlString[0]);
jsonResponse = makeHttpRequest(url);
} catch (IOException e) {
Log.e(LOG_TAG, "Problem making the HTTP request.", e);
}
if (jsonResponse.equals("")){
return null;
}
return extractFeatureFromJson(jsonResponse);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
spinner.setVisibility(View.VISIBLE);
AlphaAnimation animation1 = new AlphaAnimation(1.0f, 0.2f);
animation1.setDuration(200);
wholeView.startAnimation(animation1);
wholeView.setAlpha(0.2f);
}
@Override
protected void onPostExecute(Double result) {
super.onPostExecute(result);
spinner.setVisibility(View.GONE);
wholeView.setAlpha(1.0f);
if (result == null){
Toast.makeText(MainActivityJava.this, "Failed to retrieve exchange rate", Toast.LENGTH_SHORT).show();
}else{
conversionRateOne = result;
}
}
}
private class CustomDialogClass extends Dialog implements android.view.View.OnClickListener{
Context context;
ListView listView;
TextView textView;
EditText editText;
int selection;
public CustomDialogClass(@NonNull Context context, TextView textView) {
super(context);
this.context = context;
this.textView = textView;
if (textView.getId() == R.id.currency_one){
selection = 1;
}else{
selection = 2;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_dialog);
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
listView = (ListView) findViewById(R.id.list_view);
editText = (EditText) findViewById(R.id.search_text) ;
final ArrayAdapter<CharSequence> arrayAdapter = ArrayAdapter.createFromResource(context,R.array.currency_arrays,android.R.layout.simple_list_item_1);
listView.setAdapter(arrayAdapter);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
arrayAdapter.getFilter().filter(charSequence);
}
@Override
public void afterTextChanged(Editable editable) {
}
});
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
textView.setText(adapterView.getItemAtPosition(i).toString());
SharedPreferences.Editor editor = pref.edit();
if (selection == 1) {
editor.putString(CURRENCY_ONE,adapterView.getItemAtPosition(i).toString());
}else{
editor.putString(CURRENCY_TWO,adapterView.getItemAtPosition(i).toString());
}
editor.apply();
currencyOneEditText.setText("");
currencyTwoEditText.setText("");
String stringURL = UriBuilder(currencyOne.getText().toString().substring(0,3),
currencyTwo.getText().toString().substring(0,3));
MyAsyncTask task = new MyAsyncTask();
task.execute(stringURL);
dismiss();
}
});
}
@Override
public void onClick(View view) {
}
}
private String arrayEntry (String s){
String[] strings = getResources().getStringArray(R.array.currency_arrays);
String returnString = strings[0];
for (String string : strings) {
if (s.equals(string.substring(0, 3))) {
returnString = string;
}
}
return returnString;
}
}

View File

@@ -1,108 +0,0 @@
package com.appttude.h_mal.easycc.legacy;
import android.net.Uri;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
public class PublicMethods {
private static String TAG = PublicMethods.class.getSimpleName();
private static String URL = "https://free.currencyconverterapi.com/api/v3/convert?";
public PublicMethods() {
}
public static String UriBuilder(String s1, String s2){
s1 = s1.substring(0,3);
s2 = s2.substring(0,3);
Uri baseUri = Uri.parse(URL);
Uri.Builder builder = baseUri.buildUpon();
builder.appendQueryParameter("q", s1 + "_" + s2)
.appendQueryParameter("compact", "ultra")
.appendQueryParameter("apiKey", "a4f93cc2ff05dd772321");
return builder.build().toString();
}
public static java.net.URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
} catch (MalformedURLException e) {
Log.e(TAG, "Error with creating URL ", e);
}
return url;
}
public static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
if (url == null) {
return jsonResponse;
}
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(30000);
urlConnection.setConnectTimeout(30000);
urlConnection.setRequestMethod("GET");
urlConnection.connect();
if (urlConnection.getResponseCode() == 200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
} else {
Log.e(TAG, "Error response code: " + urlConnection.getResponseCode());
}
} catch (IOException e) {
Log.e(TAG, "Problem retrieving the JSON results.", e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (inputStream != null) {
inputStream.close();
}
}
return jsonResponse;
}
private static String readFromStream(InputStream inputStream) throws IOException {
StringBuilder output = new StringBuilder();
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(inputStreamReader);
String line = "";
while (line != null) {
output.append(line);
line = reader.readLine();
}
}
Log.d(TAG, output.toString());
return output.toString();
}
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
}
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.models package com.appttude.h_mal.easycc.models
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@@ -1,7 +0,0 @@
package com.appttude.h_mal.easycc.mvvm.ui.app
interface RateListener {
fun onStarted()
fun onSuccess()
fun onFailure(message: String)
}

View File

@@ -1,70 +0,0 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import android.appwidget.AppWidgetManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
import com.appttude.h_mal.easycc.mvvm.utils.DisplayToast
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
/**
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
*/
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAware, RateListener {
override val kodein by kodein()
private val factory : WidgetViewModelFactory by instance()
var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
companion object{
lateinit var viewModel: WidgetViewModel
}
public override fun onCreate(icicle: Bundle?) {
super.onCreate(icicle)
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if the user presses the back button.
setResult(RESULT_CANCELED)
// Find the widget id from the intent.
val extras = intent.extras
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
}
// If this activity was started with an intent without an app widget ID, finish with an error.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish()
return
}
viewModel = ViewModelProviders.of(this, factory).get(WidgetViewModel::class.java)
val binding: CurrencyAppWidgetConfigureBinding = DataBindingUtil.setContentView(this, R.layout.currency_app_widget_configure)
binding.viewmodel = viewModel
binding.lifecycleOwner = this
viewModel.initialise(mAppWidgetId)
viewModel.rateListener = this
}
override fun onStarted() {}
override fun onSuccess() {
WidgetSubmitDialog(this,mAppWidgetId, viewModel).show()
}
override fun onFailure(message: String) {
DisplayToast(message)
}
}

View File

@@ -1,59 +0,0 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import android.app.Activity
import android.app.Dialog
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.mvvm.utils.transformIntToArray
import kotlinx.android.synthetic.main.confirm_dialog.*
/**
* Dialog created when submitting the completed selections
* in [CurrencyAppWidgetConfigureActivityKotlin]
*/
class WidgetSubmitDialog(
private val activity: Activity,
private val appWidgetId: Int,
private val viewModel: WidgetViewModel
) :Dialog(activity){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.confirm_dialog)
// layer behind dialog to be transparent
window!!.setBackgroundDrawableResource(android.R.color.transparent)
// Dialog cannot be cancelled by clicking away
setCancelable(false)
confirm_text.text = StringBuilder().append("Create widget for ")
.append(viewModel.getWidgetStringName())
.append("?").toString()
confirm_yes.setOnClickListener {
// It is the responsibility of the configuration activity to update the app widget
// Send update broadcast to widget app class
Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
null,
context,
CurrencyAppWidgetKotlin::class.java).apply {
// Save current widget pairs
viewModel.setWidgetStored()
// Put current app widget ID into extras and send broadcast
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, transformIntToArray(appWidgetId) )
activity.sendBroadcast(this)
}
// Make sure we pass back the original appWidgetId
val resultValue = activity.intent
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
activity.setResult(Activity.RESULT_OK, resultValue)
activity.finish()
}
confirm_no.setOnClickListener { dismiss() }
}
}

View File

@@ -1,83 +0,0 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import android.view.View
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
class WidgetViewModel(
private val repository: RepositoryImpl
) : ViewModel(){
var rateListener: RateListener? = null
var appWidgetId: Int? = null
var rateIdFrom = MutableLiveData<String>()
var rateIdTo = MutableLiveData<String>()
fun initialise(appId: Int){
appWidgetId = appId
val widgetString = getWidgetStored(appId)
rateIdFrom.value = widgetString.first
rateIdTo.value = widgetString.second
}
fun selectCurrencyOnClick(view: View){
if (appWidgetId == null){
Toast.makeText(view.context, "No App Widget ID", Toast.LENGTH_LONG).show()
return
}
WidgetItemSelectDialog(view.context, object : DialogResult {
override fun result(result: String) {
if (view.tag.toString() == "top"){
rateIdFrom.value = result
}else{
rateIdTo.value = result
}
Toast.makeText(view.context, result, Toast.LENGTH_LONG).show()
}
}).show()
}
fun submitSelectionOnClick(view: View){
if (appWidgetId == null){
rateListener?.onFailure("No App Widget ID")
return
}
if (rateIdFrom.value == rateIdTo.value){
rateListener?.onFailure("Selected rates cannot be the same")
return
}
rateListener?.onSuccess()
}
fun getWidgetStored(id: Int) = repository.getWidgetConversionPairs(id)
fun setWidgetStored() {
if (rateIdTo.value == null && rateIdFrom.value == null){
rateListener?.onFailure("Selections incomplete")
return
}
if (rateIdFrom.value == rateIdTo.value){
rateListener?.onFailure("Selected rates cannot be the same")
return
}
repository.setWidgetConversionPairs(rateIdFrom.value!!,rateIdTo.value!!,appWidgetId!!)
}
fun getWidgetStringName() = "${rateIdFrom.value!!.trimToThree()}${rateIdTo.value!!.trimToThree()}"
private fun String.trimToThree() = this.substring(0,3)
}

View File

@@ -1,22 +0,0 @@
package com.appttude.h_mal.easycc.mvvm.utils
import java.text.DecimalFormat
fun transformIntToArray(int: Int): IntArray{
return intArrayOf(int)
}
fun String.trimToThree(): String{
if (this.length > 3){
return this.substring(0, 3)
}
return this
}
fun convertPairsListToString(s1: String, s2: String): String =
"${s1.trimToThree()}_${s2.trimToThree()}"
fun Double.toTwoDp() = run {
val df = DecimalFormat("#.##")
java.lang.Double.valueOf(df.format(this))
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.ui.app package com.appttude.h_mal.easycc.ui.main
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
@@ -10,6 +10,9 @@ import android.widget.ArrayAdapter
import com.appttude.h_mal.easycc.R import com.appttude.h_mal.easycc.R
import kotlinx.android.synthetic.main.custom_dialog.* import kotlinx.android.synthetic.main.custom_dialog.*
/**
* Custom dialog when selecting currencies from list with filter
*/
class CustomDialogClass( class CustomDialogClass(
context: Context, context: Context,
private val clickListener: ClickListener private val clickListener: ClickListener
@@ -19,9 +22,12 @@ class CustomDialogClass(
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.custom_dialog) setContentView(R.layout.custom_dialog)
// Transparent background
window!!.setBackgroundDrawableResource(android.R.color.transparent) window!!.setBackgroundDrawableResource(android.R.color.transparent)
// Keyboard not to overlap dialog
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
// array adapter for list of currencies in R.Strings
val arrayAdapter = val arrayAdapter =
ArrayAdapter.createFromResource( ArrayAdapter.createFromResource(
context, R.array.currency_arrays, context, R.array.currency_arrays,
@@ -29,6 +35,7 @@ class CustomDialogClass(
list_view.adapter = arrayAdapter list_view.adapter = arrayAdapter
// Edit text to filter @arrayAdapter
search_text.addTextChangedListener(object : TextWatcher { search_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
@@ -37,6 +44,7 @@ class CustomDialogClass(
override fun afterTextChanged(editable: Editable) {} override fun afterTextChanged(editable: Editable) {}
}) })
// interface selection back to calling activity
list_view.setOnItemClickListener{ adapterView, _, i, _ -> list_view.setOnItemClickListener{ adapterView, _, i, _ ->
clickListener.onText(adapterView.getItemAtPosition(i).toString()) clickListener.onText(adapterView.getItemAtPosition(i).toString())
dismiss() dismiss()
@@ -44,6 +52,7 @@ class CustomDialogClass(
} }
} }
// Interface to handle selection within dialog
interface ClickListener{ interface ClickListener{
fun onText(currencyName: String) fun onText(currencyName: String)
} }

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.ui.app package com.appttude.h_mal.easycc.ui.main
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
@@ -12,15 +12,15 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.easycc.R import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
import com.appttude.h_mal.easycc.mvvm.utils.DisplayToast import com.appttude.h_mal.easycc.utils.clearEditText
import com.appttude.h_mal.easycc.mvvm.utils.clearEditText import com.appttude.h_mal.easycc.utils.displayToast
import com.appttude.h_mal.easycc.mvvm.utils.hideView import com.appttude.h_mal.easycc.utils.hideView
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein import org.kodein.di.android.kodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener { class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener {
override val kodein by kodein() override val kodein by kodein()
// Retrieve MainViewModelFactory via dependency injection // Retrieve MainViewModelFactory via dependency injection
@@ -56,15 +56,19 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
private fun setUpObservers() { private fun setUpObservers() {
viewModel.operationStartedListener.observe(this, Observer { viewModel.operationStartedListener.observe(this, Observer {
// Show progress bar
progressBar.hideView(false) progressBar.hideView(false)
}) })
viewModel.operationFinishedListener.observe(this, Observer { pair -> viewModel.operationFinishedListener.observe(this, Observer { pair ->
// hide progress bar
progressBar.hideView(true) progressBar.hideView(true)
if (pair.first){ if (pair.first){
// Operation was successful remove text in EditTexts
bottomInsertValues.clearEditText() bottomInsertValues.clearEditText()
topInsertValue.clearEditText() topInsertValue.clearEditText()
}else{ }else{
pair.second?.let { DisplayToast(it) } // Display Toast with error message returned from Viewmodel
pair.second?.let { displayToast(it) }
} }
}) })
} }
@@ -78,38 +82,25 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
} }
private fun showCustomDialog(view: View?) { private fun showCustomDialog(view: View?) {
CustomDialogClass(this, object : ClickListener {
val dialogClass = CustomDialogClass(this, object : ClickListener {
override fun onText(currencyName: String) { override fun onText(currencyName: String) {
(view as TextView).text = currencyName (view as TextView).text = currencyName
viewModel.setCurrencyName(view.tag, currencyName) viewModel.setCurrencyName(view.tag, currencyName)
} }
})
dialogClass.show()
}
override fun onStarted() { }).show()
progressBar.hideView(false)
}
override fun onSuccess() {
progressBar.hideView(true)
bottomInsertValues.clearEditText()
topInsertValue.clearEditText()
}
override fun onFailure(message: String) {
progressBar.hideView(true)
DisplayToast(message)
} }
override fun onClick(view: View?) { override fun onClick(view: View?) {
showCustomDialog(view) showCustomDialog(view)
} }
// Text watcher applied to EditText @topInsertValue
private val textWatcherClass: TextWatcher = object : TextWatcher { private val textWatcherClass: TextWatcher = object : TextWatcher {
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) { override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
// Remove text watcher on other text watcher to prevent infinite loop
bottomInsertValues.removeTextChangedListener(textWatcherClass2) bottomInsertValues.removeTextChangedListener(textWatcherClass2)
// Clear any values if current EditText is empty
if (topInsertValue.text.isNullOrEmpty()) if (topInsertValue.text.isNullOrEmpty())
bottomInsertValues.setText("") bottomInsertValues.setText("")
} }
@@ -117,12 +108,14 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
bottomInsertValues.setText(viewModel.getConversion(s.toString())) bottomInsertValues.setText(viewModel.getConversion(s.toString()))
// add Text watcher back as it is safe to do so
bottomInsertValues.addTextChangedListener(textWatcherClass2) bottomInsertValues.addTextChangedListener(textWatcherClass2)
} }
} }
private val textWatcherClass2: TextWatcher = object : TextWatcher { private val textWatcherClass2: TextWatcher = object : TextWatcher {
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) { override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
topInsertValue.removeTextChangedListener(textWatcherClass) topInsertValue.removeTextChangedListener(textWatcherClass)
if (bottomInsertValues.text.isNullOrEmpty()) if (bottomInsertValues.text.isNullOrEmpty())
topInsertValue.clearEditText() topInsertValue.clearEditText()

View File

@@ -1,26 +1,28 @@
package com.appttude.h_mal.easycc.mvvm.ui.app package com.appttude.h_mal.easycc.ui.main
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.EditText
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.mvvm.utils.toTwoDp import com.appttude.h_mal.easycc.utils.toTwoDpString
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import java.text.DecimalFormat
/**
* ViewModel for the task Main Activity Screen
*/
private const val TAG = "MainViewModel" private const val TAG = "MainViewModel"
class MainViewModel( class MainViewModel(
// Repository injected via Viewmodel factory
private val repository: Repository private val repository: Repository
) : ViewModel(){ ) : ViewModel(){
private val conversionPairs by lazy { repository.getConversionPair() } private val conversionPairs by lazy { repository.getConversionPair() }
// Viewbinding to textviews in @activity_main.xml
var rateIdFrom: String? = null var rateIdFrom: String? = null
var rateIdTo: String? = null var rateIdTo: String? = null
@@ -30,36 +32,42 @@ class MainViewModel(
private var conversionRate: Double = 0.00 private var conversionRate: Double = 0.00
fun getExchangeRate(){ private fun getExchangeRate(){
operationStartedListener.postValue(true)
operationStartedListener.postValue(false)
// Null check on currency values
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){ if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){
operationFinishedListener.postValue(Pair(false, "Select currencies")) operationFinishedListener.postValue(Pair(false, "Select currencies"))
return return
} }
// No need to call api as it will return exchange rate as 1
if (rateIdFrom == rateIdTo){ if (rateIdFrom == rateIdTo){
conversionRate = 1.00 conversionRate = 1.00
operationFinishedListener.postValue(Pair(true, null)) operationFinishedListener.postValue(Pair(true, null))
return return
} }
// Open Coroutine on IO thread to carry out async task
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
// Non-null assertion (!!) as values have been null checked and have not changed
val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!) val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!)
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
exchangeResponse.results?.iterator()?.next()?.value?.let { exchangeResponse.results?.iterator()?.next()?.value?.let {
// Response Successful and contains @param CurrencyObject
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
operationFinishedListener.postValue(Pair(true, null)) operationFinishedListener.postValue(Pair(true, null))
conversionRate = it.value conversionRate = it.value
return@launch return@launch
} }
}catch(e: IOException){ }catch(e: IOException){
operationFinishedListener.postValue(Pair(false, e.message ?: "Currency Retrieval failed")) e.message?.let {
return@launch operationFinishedListener.postValue(Pair(false, it))
return@launch
}
} }
operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate")) operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate"))
} }
} }
@@ -67,8 +75,8 @@ class MainViewModel(
fun getConversion(fromValue: String): String? { fun getConversion(fromValue: String): String? {
return try { return try {
val fromValDouble = fromValue.toDouble() val fromValDouble = fromValue.toDouble()
val bottomVal1 = (fromValDouble * conversionRate).toTwoDp() val bottomVal1 = (fromValDouble * conversionRate)
bottomVal1.toBigDecimal().toPlainString() bottomVal1.toTwoDpString()
}catch (e: NumberFormatException) { }catch (e: NumberFormatException) {
Log.e(TAG, "no numbers inserted") Log.e(TAG, "no numbers inserted")
null null
@@ -78,23 +86,26 @@ class MainViewModel(
fun getReciprocalConversion(toValue: String): String? { fun getReciprocalConversion(toValue: String): String? {
return try { return try {
val toDoubleVal = toValue.toDouble() val toDoubleVal = toValue.toDouble()
val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp() val newTopVal = toDoubleVal.times((1/conversionRate))
newTopVal.toBigDecimal().toPlainString() newTopVal.toTwoDpString()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, "no numbers inserted") Log.e(TAG, "no numbers inserted")
null null
} }
} }
// Start operation based on dialog selection
fun setCurrencyName(tag: Any?, currencyName: String){ fun setCurrencyName(tag: Any?, currencyName: String){
if (tag.toString() == "top"){ when(tag.toString()){
rateIdFrom = currencyName "top" -> rateIdFrom = currencyName
}else{ "bottom" -> rateIdTo = currencyName
rateIdTo = currencyName else -> { return }
} }
getExchangeRate() getExchangeRate()
} }
// Start operation based on possible values stored in bundle or retrieve from repository
fun initiate(extras: Bundle?) { fun initiate(extras: Bundle?) {
rateIdFrom = extras?.getString("parse_1") ?: conversionPairs.first rateIdFrom = extras?.getString("parse_1") ?: conversionPairs.first
rateIdTo = extras?.getString("parse_2") ?: conversionPairs.second rateIdTo = extras?.getString("parse_2") ?: conversionPairs.second

View File

@@ -1,9 +1,13 @@
package com.appttude.h_mal.easycc.mvvm.ui.app package com.appttude.h_mal.easycc.ui.main
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
/**
* Viewmodel factory for [MainViewModel]
* inject repository into viewmodel
*/
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MainViewModelFactory ( class MainViewModelFactory (
private val repository: RepositoryImpl private val repository: RepositoryImpl

View File

@@ -0,0 +1,151 @@
package com.appttude.h_mal.easycc.ui.widget
import android.app.Activity
import android.appwidget.AppWidgetManager
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
import com.appttude.h_mal.easycc.ui.main.ClickListener
import com.appttude.h_mal.easycc.ui.main.CustomDialogClass
import com.appttude.h_mal.easycc.utils.displayToast
import com.appttude.h_mal.easycc.utils.transformIntToArray
import com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin
import kotlinx.android.synthetic.main.currency_app_widget_configure.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
/**
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
*/
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAware, View.OnClickListener {
override val kodein by kodein()
private val factory: WidgetViewModelFactory by instance()
var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
companion object {
lateinit var viewModel: WidgetViewModel
}
public override fun onCreate(icicle: Bundle?) {
super.onCreate(icicle)
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if the user presses the back button.
setResult(RESULT_CANCELED)
// Find the widget id from the intent.
val extras = intent.extras
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
}
// If this activity was started with an intent without an app widget ID, finish with an error.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish()
return
}
// ViewModel setup
viewModel = ViewModelProviders.of(this, factory).get(WidgetViewModel::class.java)
viewModel.initiate(mAppWidgetId)
setupDataBinding()
setupObserver()
setupClickListener()
}
private fun setupClickListener() {
submit_widget.setOnClickListener(this)
currency_one.setOnClickListener(this)
currency_two.setOnClickListener(this)
}
private fun setupObserver() {
viewModel.operationFinishedListener.observe(this, Observer {
// it.first is a the success of the operation
if (it.first){
displaySubmitDialog()
}else{
// failed operation - display toast with message from it.second
it.second?.let { message -> displayToast(message) }
}
})
}
private fun setupDataBinding() {
// data binding to @R.layout.currency_app_widget_configure
DataBindingUtil.setContentView<CurrencyAppWidgetConfigureBinding>(
this,
R.layout.currency_app_widget_configure
).apply {
viewmodel = viewModel
lifecycleOwner = this@CurrencyAppWidgetConfigureActivityKotlin
}
}
override fun onClick(view: View?) {
when (view?.tag.toString()) {
"top", "bottom" -> showCustomDialog(view)
"submit" -> viewModel.submitSelectionOnClick()
else -> {
return
}
}
}
private fun displaySubmitDialog() {
val message = viewModel.getSubmitDialogMessage()
WidgetSubmitDialog(this, message, object : DialogSubmit {
override fun onSubmit() {
sendUpdateIntent()
finishCurrencyWidgetActivity()
}
}).show()
}
private fun showCustomDialog(view: View?) {
CustomDialogClass(this, object : ClickListener {
override fun onText(currencyName: String) {
(view as TextView).text = currencyName
viewModel.setCurrencyName(view.tag, currencyName)
}
}).show()
}
fun finishCurrencyWidgetActivity(){
// Make sure we pass back the original appWidgetId
val resultValue = intent
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId)
setResult(Activity.RESULT_OK, resultValue)
finish()
}
fun sendUpdateIntent() {
// It is the responsibility of the configuration activity to update the app widget
// Send update broadcast to widget app class
Intent(this@CurrencyAppWidgetConfigureActivityKotlin,
CurrencyAppWidgetKotlin::class.java
).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
viewModel.setWidgetStored()
// Put current app widget ID into extras and send broadcast
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, transformIntToArray(mAppWidgetId))
sendBroadcast(this)
}
}
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget package com.appttude.h_mal.easycc.ui.widget
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
@@ -15,7 +15,7 @@ widget for when submitting the completed selections
*/ */
class WidgetItemSelectDialog( class WidgetItemSelectDialog(
context: Context, context: Context,
val dialogResult: DialogResult private val dialogResult: DialogResult
) :Dialog(context){ ) :Dialog(context){
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -0,0 +1,38 @@
package com.appttude.h_mal.easycc.ui.widget
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import com.appttude.h_mal.easycc.R
import kotlinx.android.synthetic.main.confirm_dialog.*
/**
* Dialog created when submitting the completed selections
* in [CurrencyAppWidgetConfigureActivityKotlin]
*/
class WidgetSubmitDialog(
context: Context,
private val messageString: String,
private val dialogInterface: DialogSubmit
) :Dialog(context){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.confirm_dialog)
// layer behind dialog to be transparent
window!!.setBackgroundDrawableResource(android.R.color.transparent)
// Dialog cannot be cancelled by clicking away
setCancelable(false)
confirm_text.text = messageString
// handle dialog buttons
confirm_yes.setOnClickListener { dialogInterface.onSubmit() }
confirm_no.setOnClickListener { dismiss() }
}
}
interface DialogSubmit{
fun onSubmit()
}

View File

@@ -0,0 +1,70 @@
package com.appttude.h_mal.easycc.ui.widget
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.utils.trimToThree
class WidgetViewModel(
private val repository: Repository
) : ViewModel(){
private val defaultCurrency: String by lazy { repository.getArrayList()[0] }
var appWidgetId: Int? = null
// data binding to @R.layout.currency_app_widget_configure
var rateIdFrom: String? = null
var rateIdTo: String? = null
// Live data to feedback to @CurrencyAppWidgetConfigureActivityKotlin
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
// Setup viewmodel app widget ID
// Set default values for text views
fun initiate(appId: Int){
appWidgetId = appId
val widgetString
= repository.getWidgetConversionPairs(appId)
rateIdFrom = widgetString.first ?: defaultCurrency
rateIdTo = widgetString.second ?: defaultCurrency
}
// Retrieve name for submit dialog (eg. AUDGBP)
fun getSubmitDialogMessage(): String {
val widgetName = getWidgetStringName()
return StringBuilder().append("Create widget for ")
.append(widgetName)
.append("?").toString()
}
fun submitSelectionOnClick(){
if (rateIdTo == null || rateIdFrom == null){
operationFinishedListener.value = Pair(false, "Selections incomplete")
return
}
if (rateIdFrom == rateIdTo){
operationFinishedListener.value =
Pair(false, "Selected rates cannot be the same ${rateIdFrom}${rateIdTo}")
return
}
operationFinishedListener.value = Pair(true, null)
}
fun setWidgetStored() {
repository.setWidgetConversionPairs(rateIdFrom!!,rateIdTo!!,appWidgetId!!)
}
// Start operation based on dialog selection
fun setCurrencyName(tag: Any?, currencyName: String){
when(tag.toString()){
"top" -> rateIdFrom = currencyName
"bottom" -> rateIdTo = currencyName
else -> { return }
}
}
private fun getWidgetStringName() = "${rateIdFrom!!.trimToThree()}${rateIdTo!!.trimToThree()}"
}

View File

@@ -1,8 +1,8 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget package com.appttude.h_mal.easycc.ui.widget
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class WidgetViewModelFactory ( class WidgetViewModelFactory (

View File

@@ -0,0 +1,36 @@
package com.appttude.h_mal.easycc.utils
import java.lang.Double.valueOf
import java.text.DecimalFormat
import java.util.*
fun transformIntToArray(int: Int): IntArray{
return intArrayOf(int)
}
fun String.trimToThree(): String{
val size = length
return when {
size > 3 -> substring(0, 3)
else -> this
}
}
fun convertPairsListToString(s1: String, s2: String): String =
"${s1.trimToThree()}_${s2.trimToThree()}"
fun Double.toTwoDp() = run {
try {
val df = DecimalFormat("0.00")
df.currency = Currency.getInstance(Locale.getDefault())
valueOf(df.format(this))
}catch (e: NumberFormatException){
e.printStackTrace()
this
}
}
fun Double.toTwoDpString(): String{
return this.toTwoDp().toBigDecimal().toPlainString()
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.utils package com.appttude.h_mal.easycc.utils
import android.content.Context import android.content.Context
import android.view.View import android.view.View
@@ -13,6 +13,6 @@ fun View.hideView(vis : Boolean){
visibility = if (vis){ View.GONE } else { View.VISIBLE } visibility = if (vis){ View.GONE } else { View.VISIBLE }
} }
fun Context.DisplayToast(message: String){ fun Context.displayToast(message: String){
Toast.makeText(this, message, Toast.LENGTH_LONG).show() Toast.makeText(this, message, Toast.LENGTH_LONG).show()
} }

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget package com.appttude.h_mal.easycc.widget
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
@@ -10,9 +10,9 @@ import android.util.Log
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.Toast import android.widget.Toast
import com.appttude.h_mal.easycc.R import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
import com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity import com.appttude.h_mal.easycc.ui.main.MainActivity
import com.appttude.h_mal.easycc.mvvm.utils.transformIntToArray import com.appttude.h_mal.easycc.utils.transformIntToArray
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<variable <variable
name="viewmodel" name="viewmodel"
type="com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModel" /> type="com.appttude.h_mal.easycc.ui.main.MainViewModel" />
</data> </data>
<RelativeLayout <RelativeLayout
@@ -16,7 +14,7 @@
android:orientation="vertical" android:orientation="vertical"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
tools:context="com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity"> tools:context=".ui.main.MainActivity">
<RelativeLayout <RelativeLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -44,7 +42,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="12dp"
android:text="@{viewmodel.rateIdFrom}" android:text="@={viewmodel.rateIdFrom}"
android:textColor="@color/colour_five" android:textColor="@color/colour_five"
android:textSize="18sp" /> android:textSize="18sp" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
@@ -72,15 +70,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <androidx.cardview.widget.CardView style="@style/cardview_theme">
style="@style/cardview_theme">
<TextView <TextView
android:id="@+id/currency_two" android:id="@+id/currency_two"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="12dp"
android:text="@{viewmodel.rateIdTo}" android:text="@={viewmodel.rateIdTo}"
android:tag="bottom" android:tag="bottom"
android:textColor="@color/colour_five" android:textColor="@color/colour_five"
android:textSize="18sp" /> android:textSize="18sp" />

View File

@@ -1,14 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<data> <data>
<variable <variable
name="viewmodel" name="viewmodel"
type="com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModel" /> type="com.appttude.h_mal.easycc.ui.widget.WidgetViewModel" />
</data> </data>
<RelativeLayout <RelativeLayout
@@ -17,7 +14,7 @@
android:orientation="vertical" android:orientation="vertical"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
tools:context=".mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"> tools:context=".ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -40,7 +37,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="12dp"
android:onClick="@{viewmodel::selectCurrencyOnClick}"
android:tag="top" android:tag="top"
android:text="@={viewmodel.rateIdFrom}" android:text="@={viewmodel.rateIdFrom}"
android:textColor="@color/colour_five" android:textColor="@color/colour_five"
@@ -58,7 +54,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="12dp"
android:onClick="@{viewmodel::selectCurrencyOnClick}"
android:tag="bottom" android:tag="bottom"
android:text="@={viewmodel.rateIdTo}" android:text="@={viewmodel.rateIdTo}"
android:textColor="@color/colour_five" android:textColor="@color/colour_five"
@@ -69,15 +64,15 @@
</LinearLayout> </LinearLayout>
<TextView <TextView
android:layout_marginTop="12dp"
android:layout_marginEnd="22dp" android:layout_marginEnd="22dp"
android:id="@+id/submit_widget" android:id="@+id/submit_widget"
android:tag="submit"
android:padding="12dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/whole_view" android:layout_below="@id/whole_view"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:textColor="@color/colour_five" android:textColor="@color/colour_five"
android:onClick="@{viewmodel::submitSelectionOnClick}"
android:text="Submit" /> android:text="Submit" />
</RelativeLayout> </RelativeLayout>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin" android:configure="com.appttude.h_mal.easycc.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"
android:initialKeyguardLayout="@layout/currency_app_widget" android:initialKeyguardLayout="@layout/currency_app_widget"
android:initialLayout="@layout/currency_app_widget" android:initialLayout="@layout/currency_app_widget"
android:minHeight="40dp" android:minHeight="40dp"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin" android:configure="com.appttude.h_mal.easycc.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"
android:initialKeyguardLayout="@layout/currency_kotlin_app_widget" android:initialKeyguardLayout="@layout/currency_kotlin_app_widget"
android:initialLayout="@layout/currency_kotlin_app_widget" android:initialLayout="@layout/currency_kotlin_app_widget"
android:minWidth="110dp" android:minWidth="110dp"

View File

@@ -2,7 +2,7 @@ package com.appttude.h_mal.easycc;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).

View File

@@ -1,16 +1,14 @@
package com.appttude.h_mal.easycc.repository package com.appttude.h_mal.easycc.repository
import android.content.Context import android.content.Context
import com.appttude.h_mal.easycc.BuildConfig import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi import com.appttude.h_mal.easycc.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.utils.convertPairsListToString
import com.appttude.h_mal.easycc.mvvm.utils.convertPairsListToString
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Before import org.junit.Before
@@ -20,7 +18,6 @@ import org.mockito.Mockito
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import retrofit2.Response import retrofit2.Response
import java.io.IOException import java.io.IOException
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith

View File

@@ -1,10 +1,10 @@
package com.appttude.h_mal.easycc.repository package com.appttude.h_mal.easycc.repository
import android.content.Context import android.content.Context
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock