mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2026-01-31 02:41:47 +00:00
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:
@@ -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 {
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|
||||||
|
|||||||
@@ -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()) }
|
||||||
@@ -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
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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(
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.appttude.h_mal.easycc.mvvm.ui.app
|
|
||||||
|
|
||||||
interface RateListener {
|
|
||||||
fun onStarted()
|
|
||||||
fun onSuccess()
|
|
||||||
fun onFailure(message: String)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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?) {
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()}"
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user