New Commit of kotlin version - MVVM, retrofit2, Kodein

This commit is contained in:
2020-04-12 19:19:15 +01:00
parent 20298d5028
commit 13da00e96a
49 changed files with 1481 additions and 287 deletions

Binary file not shown.

View File

@@ -1,29 +1,116 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

4
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

3
.idea/gradle.xml generated
View File

@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">

18
.idea/misc.xml generated
View File

@@ -5,22 +5,36 @@
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<list size="12">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<list size="11">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
</list>
</value>
</option>

4
.idea/modules.xml generated
View File

@@ -2,8 +2,8 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Personal_Project---Currency_Converter-master.iml" filepath="$PROJECT_DIR$/Personal_Project---Currency_Converter-master.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/Personal_Project---Currency_Converter-master.iml" filepath="$PROJECT_DIR$/Personal_Project---Currency_Converter-master.iml" group="Personal_Project---Currency_Converter-master" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="Personal_Project---Currency_Converter-master/app" />
</modules>
</component>
</project>

View File

@@ -1,15 +1,17 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdkVersion 29
defaultConfig {
applicationId "com.appttude.h_mal.easycc"
minSdkVersion 16
targetSdkVersion 27
minSdkVersion 23
targetSdkVersion 29
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
@@ -20,15 +22,66 @@ android {
testOptions {
unitTests.returnDefaultValues = true
}
dataBinding {
enabled = true
}
viewBinding.enabled = true
buildTypes.each {
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
def ccApiKey = properties.getProperty("cc_api_key", "")
it.buildConfigField 'String', "CC_API_KEY", ccApiKey
it.resValue 'string', "api_key", ccApiKey
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//Retrofit and GSON
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
//Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
//New Material Design
implementation 'com.google.android.material:material:1.1.0-alpha10'
//Kodein Dependency Injection
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
//Android Room
implementation "androidx.room:room-runtime:2.2.0-rc01"
implementation "androidx.room:room-ktx:2.2.0-rc01"
kapt "androidx.room:room-compiler:2.2.0-rc01"
implementation 'com.xwray:groupie:2.3.0'
implementation 'com.xwray:groupie-kotlin-android-extensions:2.3.0'
implementation 'com.xwray:groupie-databinding:2.3.0'
implementation "androidx.preference:preference-ktx:1.1.0"
}

View File

@@ -1,26 +0,0 @@
package com.appttude.h_mal.easycc;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junit.framework.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.appttude.h_mal.easycc", appContext.getPackageName());
}
}

View File

@@ -1,36 +1,10 @@
package com.appttude.h_mal.easycc;
import android.support.test.rule.ActivityTestRule;
import android.view.View;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.*;
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> activityActivityTestRule = new ActivityTestRule<MainActivity>(MainActivity.class);
private MainActivity mainActivity = null;
@Before
public void setUp() throws Exception {
mainActivity = activityActivityTestRule.getActivity();
}
@Test
public void testViews(){
View view = mainActivity.findViewById(R.id.editText);
assertNotNull(view);
}
@After
public void TearDown() throws Exception{
mainActivity = null;
}
}

View File

@@ -5,22 +5,46 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true" />
<application
android:name=".mvvm.AppClass"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.appttude.h_mal.easycc.MainActivity">
<receiver android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetKotlin">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/currency_kotlin_app_widget_info" />
</receiver>
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".legacy.MainActivityJava"></activity>
<receiver android:name="com.appttude.h_mal.easycc.CurrencyAppWidget">
<receiver android:name=".legacy.CurrencyAppWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
@@ -30,7 +54,7 @@
android:resource="@xml/currency_app_widget_info" />
</receiver>
<activity android:name="com.appttude.h_mal.easycc.CurrencyAppWidgetConfigureActivity">
<activity android:name=".legacy.CurrencyAppWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc;
package com.appttude.h_mal.easycc.legacy;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -10,17 +10,19 @@ 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.CurrencyAppWidgetConfigureActivity.loadTitlePref;
import static com.appttude.h_mal.easycc.PublicMethods.UriBuilder;
import static com.appttude.h_mal.easycc.PublicMethods.createUrl;
import static com.appttude.h_mal.easycc.PublicMethods.makeHttpRequest;
import static com.appttude.h_mal.easycc.PublicMethods.round;
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.
@@ -116,7 +118,7 @@ public class CurrencyAppWidget extends AppWidgetProvider {
int backgroundColor = 0x000000; //background color (here black)
views.setInt( R.id.widget_view, "setBackgroundColor", (int)(opacity * 0xFF) << 24 | backgroundColor);
Intent clickIntentTemplate = new Intent(context, MainActivity.class);
Intent clickIntentTemplate = new Intent(context, MainActivityJava.class);
clickIntentTemplate.setAction(Intent.ACTION_MAIN);
clickIntentTemplate.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -149,7 +151,7 @@ public class CurrencyAppWidget extends AppWidgetProvider {
} catch (JSONException e) {
Log.e("MainActivity", "Problem parsing the JSON results", e);
Log.e("MainActivityJava", "Problem parsing the JSON results", e);
}
return conversionValue;
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc;
package com.appttude.h_mal.easycc.legacy;
import android.app.Activity;
import android.app.Dialog;
@@ -7,7 +7,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
@@ -19,6 +19,8 @@ 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.
*/
@@ -90,7 +92,7 @@ public class CurrencyAppWidgetConfigureActivity extends Activity {
// Read the prefix from the SharedPreferences object for this widget.
// If there is no preference saved, get the default from a resource
static String loadTitlePref(Context context, int appWidgetId, int item) {
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) {

View File

@@ -1,12 +1,12 @@
package com.appttude.h_mal.easycc;
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 android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
@@ -24,6 +24,8 @@ 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;
@@ -32,11 +34,11 @@ import java.net.URL;
import java.text.DecimalFormat;
import static com.appttude.h_mal.easycc.PublicMethods.UriBuilder;
import static com.appttude.h_mal.easycc.PublicMethods.createUrl;
import static com.appttude.h_mal.easycc.PublicMethods.makeHttpRequest;
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 MainActivity extends AppCompatActivity {
public class MainActivityJava extends AppCompatActivity {
EditText currencyOneEditText;
EditText currencyTwoEditText;
@@ -50,7 +52,7 @@ public class MainActivity extends AppCompatActivity {
private String CURRENCY_ONE = "currency_one_pref";
private String CURRENCY_TWO = "currency_two_pref";
private static final String LOG_TAG = MainActivity.class.getSimpleName();
private static final String LOG_TAG = MainActivityJava.class.getSimpleName();
SharedPreferences pref;
@@ -63,8 +65,8 @@ public class MainActivity extends AppCompatActivity {
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.editText);
currencyTwoEditText = (EditText) findViewById(R.id.editText2);
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);
@@ -96,7 +98,7 @@ public class MainActivity extends AppCompatActivity {
currencyOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(MainActivity.this,currencyOne);
CustomDialogClass dialogClass = new CustomDialogClass(MainActivityJava.this,currencyOne);
dialogClass.show();
}
});
@@ -104,7 +106,7 @@ public class MainActivity extends AppCompatActivity {
currencyTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
CustomDialogClass dialogClass = new CustomDialogClass(MainActivity.this,currencyTwo);
CustomDialogClass dialogClass = new CustomDialogClass(MainActivityJava.this,currencyTwo);
dialogClass.show();
}
});
@@ -223,15 +225,15 @@ public class MainActivity extends AppCompatActivity {
} catch (JSONException e) {
Log.e("MainActivity", "Problem parsing the JSON results", e);
Log.e("MainActivityJava", "Problem parsing the JSON results", e);
}
return conversionValue;
}
protected class MyAsyncTask extends AsyncTask<String, Void, Double> {
class MyAsyncTask extends AsyncTask<String, Void, Double> {
@Override
protected Double doInBackground(String... urlString) {
public Double doInBackground(String... urlString) {
String jsonResponse = null;
if (urlString.length < 1 || urlString[0] == null) {
@@ -271,7 +273,7 @@ public class MainActivity extends AppCompatActivity {
wholeView.setAlpha(1.0f);
if (result == null){
Toast.makeText(MainActivity.this, "Failed to retrieve exchange rate", Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivityJava.this, "Failed to retrieve exchange rate", Toast.LENGTH_SHORT).show();
}else{
conversionRateOne = result;
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc;
package com.appttude.h_mal.easycc.legacy;
import android.net.Uri;
import android.util.Log;
@@ -20,7 +20,7 @@ public class PublicMethods {
public PublicMethods() {
}
static String UriBuilder(String s1, String s2){
public static String UriBuilder(String s1, String s2){
s1 = s1.substring(0,3);
s2 = s2.substring(0,3);
@@ -34,7 +34,7 @@ public class PublicMethods {
}
static java.net.URL createUrl(String stringUrl) {
public static java.net.URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
@@ -45,7 +45,7 @@ public class PublicMethods {
}
static String makeHttpRequest(URL url) throws IOException {
public static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
if (url == null) {

View File

@@ -0,0 +1,33 @@
package com.appttude.h_mal.easycc.mvvm
import android.app.Application
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModelFactory
import com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModelFactory
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
class AppClass : Application(), KodeinAware{
override val kodein = Kodein.lazy {
import(androidXModule(this@AppClass))
bind() from singleton { NetworkConnectionInterceptor(instance()) }
bind() from singleton { GetData(instance()) }
bind() from singleton { PreferenceProvider(instance()) }
bind() from singleton { Repository(instance(), instance(), instance()) }
bind() from provider { MainViewModelFactory(instance()) }
bind() from provider { WidgetViewModelFactory(instance()) }
}
}

View File

@@ -0,0 +1,43 @@
package com.appttude.h_mal.easycc.mvvm.data.Repository
import android.content.Context
import com.appttude.h_mal.easycc.BuildConfig
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.mvvm.data.network.SafeApiRequest
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
class Repository (
private val api: GetData,
private val prefs: PreferenceProvider,
context: Context
): SafeApiRequest(){
var ccApiKey = BuildConfig.CC_API_KEY
private val appContext = context.applicationContext
suspend fun getData(s1: String, s2: String): ResponseObject?{
return apiRequest{ api.getCurrencyRate(convertPairsListToString(s1, s2),ccApiKey)}
}
fun getConversionPair(): List<String?> {
return prefs.getConversionPair()
}
fun setConversionPair(s1: String, s2: String){
prefs.saveConversionPair(s1, s2)
}
private fun convertPairsListToString(s1: String, s2: String): String = "${s1.substring(0,3)}_${s2.substring(0,3)}"
fun getArrayList(): Array<String> = appContext.resources.getStringArray(R.array.currency_arrays)
fun getWidgetConversionPairs(id: Int): List<String?> = prefs.getWidgetConversionPair(id)
fun setWidgetConversionPairs(s1: String, s2: String, id: Int) = prefs.saveWidgetConversionPair(s1, s2, id)
fun removeWidgetConversionPairs(id: Int) = prefs.removeWidgetConversion(id)
}

View File

@@ -0,0 +1,39 @@
package com.appttude.h_mal.easycc.mvvm.data.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
class NetworkConnectionInterceptor(
context: Context
) : Interceptor {
private val applicationContext = context.applicationContext
override fun intercept(chain: Interceptor.Chain): Response {
if (!isInternetAvailable()){
throw IOException("Make sure you have an active data connection")
}
return chain.proceed(chain.request())
}
private fun isInternetAvailable(): Boolean {
var result = false
val connectivityManager =
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
connectivityManager?.let {
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
result = when {
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
else -> false
}
}
}
return result
}
}

View File

@@ -0,0 +1,29 @@
package com.appttude.h_mal.easycc.mvvm.data.network
import org.json.JSONException
import org.json.JSONObject
import retrofit2.Response
import java.io.IOException
abstract class SafeApiRequest {
suspend fun<T: Any> apiRequest(call: suspend () -> Response<T>) : T{
val response = call.invoke()
if(response.isSuccessful){
return response.body()!!
}else{
val error = response.errorBody()?.string()
val message = StringBuilder()
error?.let{
try{
message.append(JSONObject(it).getString("error"))
}catch(e: JSONException){ }
message.append("\n")
}
message.append("Error Code: ${response.code()}")
throw IOException(message.toString())
}
}
}

View File

@@ -0,0 +1,36 @@
package com.appttude.h_mal.easycc.mvvm.data.network.api
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
import okhttp3.OkHttpClient
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
interface GetData {
companion object{
operator fun invoke(
networkConnectionInterceptor: NetworkConnectionInterceptor
) : GetData{
val okkHttpclient = OkHttpClient.Builder()
.addNetworkInterceptor(networkConnectionInterceptor)
.build()
return Retrofit.Builder()
.client(okkHttpclient)
.baseUrl("https://free.currencyconverterapi.com/api/v3/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GetData::class.java)
}
}
@GET("convert?")
suspend fun getCurrencyRate(@Query("q") currency: String, @Query("apiKey") api: String): Response<ResponseObject>
}

View File

@@ -0,0 +1,11 @@
package com.appttude.h_mal.easycc.mvvm.data.network.response
import com.appttude.h_mal.easycc.mvvm.models.CurrencyObject
import com.google.gson.annotations.SerializedName
class ResponseObject(
@SerializedName("query")
var query : Any,
@SerializedName("results")
var results : Map<String, CurrencyObject>
)

View File

@@ -0,0 +1,80 @@
package com.appttude.h_mal.easycc.mvvm.data.prefs
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.appttude.h_mal.easycc.R
private const val CONVERSION_ONE = "conversion_one"
private const val CONVERSION_TWO = "conversion_two"
private const val CONVERSION_ONE_WIDGET = "conversion_one_widget"
private const val CONVERSION_TWO_WIDGET = "conversion_two_widget"
class PreferenceProvider(
context: Context
) {
private val appContext = context.applicationContext
private val preference: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(appContext)
private val defaultRate: String = context.resources.getStringArray(R.array.currency_arrays)[0]
fun saveConversionPair(s1: String, s2: String) {
preference.edit().putString(
CONVERSION_ONE,
s1
).putString(
CONVERSION_TWO,
s2
).apply()
}
fun getConversionPair(): List<String?> {
val s1 = getLastConversionOne()
val s2 = getLastConversionTwo()
return listOf(s1,s2)
}
private fun getLastConversionOne(): String? {
return preference.getString(CONVERSION_ONE, defaultRate)
}
private fun getLastConversionTwo(): String? {
return preference.getString(CONVERSION_TWO, defaultRate)
}
fun saveWidgetConversionPair(s1: String, s2: String, id: Int) {
preference.edit().putString(
"${id}_$CONVERSION_ONE",
s1
).putString(
"${id}_$CONVERSION_TWO",
s2
).apply()
}
fun getWidgetConversionPair(id: Int): List<String?> {
val s1 = getWidgetLastConversionOne(id)
val s2 = getWidgetLastConversionTwo(id)
return listOf(s1,s2)
}
private fun getWidgetLastConversionOne(id: Int): String? {
return preference.getString("${id}_$CONVERSION_ONE", defaultRate)
}
private fun getWidgetLastConversionTwo(id: Int): String? {
return preference.getString("${id}_$CONVERSION_TWO", defaultRate)
}
fun removeWidgetConversion(id: Int){
preference.edit().remove("${id}_$CONVERSION_ONE").apply()
preference.edit().remove("${id}_$CONVERSION_TWO").apply()
}
}

View File

@@ -0,0 +1,14 @@
package com.appttude.h_mal.easycc.mvvm.models
import com.google.gson.annotations.SerializedName
data class CurrencyObject (
@SerializedName("id")
var id : String,
@SerializedName("fr")
var fr : String,
@SerializedName("to")
var to : String,
@SerializedName("val")
var value: Double
)

View File

@@ -0,0 +1,50 @@
package com.appttude.h_mal.easycc.mvvm.ui.app
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.WindowManager
import android.widget.ArrayAdapter
import android.widget.TextView
import com.appttude.h_mal.easycc.R
import kotlinx.android.synthetic.main.custom_dialog.*
class CustomDialogClass(
context: Context,
val textView: TextView,
val viewModel: MainViewModel
) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.custom_dialog)
window!!.setBackgroundDrawableResource(android.R.color.transparent)
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val arrayAdapter = ArrayAdapter.createFromResource(context, R.array.currency_arrays, android.R.layout.simple_list_item_1)
list_view.adapter = arrayAdapter
search_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
arrayAdapter.filter.filter(charSequence)
}
override fun afterTextChanged(editable: Editable) {}
})
list_view.setOnItemClickListener{ adapterView, _, i, _ ->
if (textView.tag == "top"){
viewModel.rateIdFrom = adapterView.getItemAtPosition(i).toString()
}else{
viewModel.rateIdTo = adapterView.getItemAtPosition(i).toString()
}
textView.text = adapterView.getItemAtPosition(i).toString()
viewModel.getExchangeRate()
dismiss()
}
}
}

View File

@@ -0,0 +1,127 @@
package com.appttude.h_mal.easycc.mvvm.ui.app
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import android.widget.Toast
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.ActivityMainBinding
import com.appttude.h_mal.easycc.utils.DisplayToast
import com.appttude.h_mal.easycc.utils.clearEditText
import com.appttude.h_mal.easycc.utils.hideView
import kotlinx.android.synthetic.main.activity_main.*
import org.kodein.di.KodeinAware
import org.kodein.di.android.kodein
import org.kodein.di.generic.instance
class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener{
override val kodein by kodein()
private val factory : MainViewModelFactory by instance()
companion object{
lateinit var viewModel: MainViewModel
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
binding.viewmodel = viewModel
binding.lifecycleOwner = this
viewModel.rateListener = this
intent.extras?.apply {
val itemOne = getString("parse_1")
val itemTwo = getString("parse_2")
if (!itemOne.isNullOrEmpty() && !itemTwo.isNullOrEmpty()){
viewModel.rateIdTo = itemOne
viewModel.rateIdFrom = itemTwo
viewModel.getExchangeRate()
}
}
viewModel.start()
topInsertValue.addTextChangedListener(textWatcherClass)
bottomInsertValues.addTextChangedListener(textWatcherClass2)
currency_one.setOnClickListener(this)
currency_two.setOnClickListener(this)
}
private fun showCustomDialog(view: View?){
val dialogClass = CustomDialogClass(this, view as TextView, viewModel)
dialogClass.show()
}
override fun onStarted() {
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?) {
showCustomDialog(view)
}
private val textWatcherClass: TextWatcher = object : TextWatcher {
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
bottomInsertValues.removeTextChangedListener(textWatcherClass2)
if (topInsertValue.text.isNullOrEmpty()) {
bottomInsertValues.setText("")
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) {
try {
viewModel.setBottomValue(s.toString(), bottomInsertValues)
} catch (e: NumberFormatException) {
Log.e(this.javaClass.simpleName, "no numbers inserted")
}
bottomInsertValues.addTextChangedListener(textWatcherClass2)
}
}
private val textWatcherClass2: TextWatcher = object : TextWatcher {
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
topInsertValue.removeTextChangedListener(textWatcherClass)
if (bottomInsertValues.text.isNullOrEmpty()) {
topInsertValue.clearEditText()
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable) {
try {
viewModel.setTopValue(s.toString(), topInsertValue)
} catch (e: NumberFormatException) {
Log.e(this.javaClass.simpleName, "no numbers inserted")
}
topInsertValue.addTextChangedListener(textWatcherClass)
}
}
}

View File

@@ -0,0 +1,78 @@
package com.appttude.h_mal.easycc.mvvm.ui.app
import android.widget.EditText
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import java.text.DecimalFormat
import java.text.NumberFormat
class MainViewModel(
private val repository: Repository
) : ViewModel(){
private val defaultValue by lazy { repository.getArrayList()[0] }
private val conversionPairs by lazy { repository.getConversionPair() }
var rateIdFrom: String? = conversionPairs[0] ?: defaultValue
var rateIdTo: String? = conversionPairs[1] ?: defaultValue
var topVal: String? = null
var bottomVal: String? = null
var rateListener: RateListener? = null
private var conversionRate: Double = 0.00
fun getExchangeRate(){
rateListener?.onStarted()
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){
rateListener?.onFailure("Select currencies")
return
}
CoroutineScope(Dispatchers.Main).launch {
try {
val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!)
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
exchangeResponse?.results?.iterator()?.next()?.value?.let {
rateListener?.onSuccess()
conversionRate = it.value
return@launch
}
rateListener?.onFailure("Failed to retrieve rate")
}catch(e: IOException){
rateListener?.onFailure(e.message!!)
}
}
}
fun setBottomValue(fromValue : String, editText: EditText) {
val fromValDouble = fromValue.toDouble()
val bottomVal1 = (fromValDouble * conversionRate).toTwoDp()
editText.setText(bottomVal1.toBigDecimal().toPlainString())
}
fun setTopValue(toValue : String, editText: EditText) {
val toDoubleVal = toValue.toDouble()
val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp()
editText.setText(newTopVal.toBigDecimal().toPlainString())
}
private fun Double.toTwoDp() = run {
val df = DecimalFormat("#.##")
java.lang.Double.valueOf(df.format(this))
}
fun start(){
if (rateIdFrom != rateIdTo){
getExchangeRate()
}
}
}

View File

@@ -0,0 +1,15 @@
package com.appttude.h_mal.easycc.mvvm.ui.app
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
@Suppress("UNCHECKED_CAST")
class MainViewModelFactory (
private val repository: Repository
): ViewModelProvider.NewInstanceFactory(){
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}

View File

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

View File

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

View File

@@ -0,0 +1,114 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.widget.Toast
import com.appttude.h_mal.easycc.legacy.MainActivityJava
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.KodeinAware
import org.kodein.di.LateInitKodein
import org.kodein.di.generic.instance
import java.io.IOException
/**
* Implementation of App Widget functionality.
* App Widget Configuration implemented in [CurrencyAppWidgetConfigureActivityKotlin]
*/
class CurrencyAppWidgetKotlin : AppWidgetProvider() {
private val kodein = LateInitKodein()
private val repository : Repository by kodein.instance()
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
// When the user deletes the widget, delete the preference associated with it.
for (appWidgetId in appWidgetIds) {
repository.removeWidgetConversionPairs(appWidgetId)
}
}
override fun onEnabled(context: Context) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
// Enter relevant functionality for when the first widget is created
}
override fun onDisabled(context: Context) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
// Enter relevant functionality for when the last widget is disabled
}
fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
//todo: get value from repository
val stringList = repository.getWidgetConversionPairs(appWidgetId)
val s1 = stringList[0]
val s2 = stringList[1]
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
views.setTextViewText(R.id.exchangeName, "Rates")
views.setTextViewText(R.id.exchangeRate, "not set")
//todo: async task to get rate
CoroutineScope(Dispatchers.Main).launch {
try {
val response = repository.getData(s1!!.substring(0,3),s2!!.substring(0,3))
response?.results?.iterator()?.next()?.value?.let {
val titleString = "${it.fr}${it.to}"
views.setTextViewText(R.id.exchangeName, titleString)
views.setTextViewText(R.id.exchangeRate, it.value.toString())
}
}catch (io : IOException){
Toast.makeText(context,io.message, Toast.LENGTH_LONG).show()
}finally {
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
val opacity = 0.3f //opacity = 0: fully transparent, opacity = 1: no transparancy
val backgroundColor = 0x000000 //background color (here black)
views.setInt(R.id.widget_view, "setBackgroundColor", (opacity * 0xFF).toInt() shl 24 or backgroundColor)
val clickIntentTemplate = Intent(context, MainActivityJava::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
putExtra("parse_1", s1)
putExtra("parse_2", s2)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val configPendingIntent = PendingIntent.getActivity(context, 0, clickIntentTemplate, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
}
}
}
fun setupRepository(context: Context): Repository {
val networkInterceptor = NetworkConnectionInterceptor(context)
val getData = GetData(networkInterceptor)
val prefs = PreferenceProvider(context)
return Repository(getData,prefs,context)
}
}

View File

@@ -0,0 +1,48 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.WindowManager
import android.widget.ArrayAdapter
import com.appttude.h_mal.easycc.R
import kotlinx.android.synthetic.main.custom_dialog.*
/*
widget for when submitting the completed selections
*/
class WidgetItemSelectDialog(
context: Context,
val dialogResult: DialogResult
) :Dialog(context){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.custom_dialog)
window!!.setBackgroundDrawableResource(android.R.color.transparent)
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val arrayAdapter = ArrayAdapter.createFromResource(context, R.array.currency_arrays, android.R.layout.simple_list_item_1)
list_view.adapter = arrayAdapter
search_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
arrayAdapter.filter.filter(charSequence)
}
override fun afterTextChanged(editable: Editable) {}
})
list_view.setOnItemClickListener{ adapterView, _, i, _ ->
dialogResult.result(adapterView.getItemAtPosition(i).toString())
dismiss()
}
}
}
interface DialogResult{
fun result(result : String)
}

View File

@@ -0,0 +1,51 @@
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 kotlinx.android.synthetic.main.confirm_dialog.*
/*
widget for when submitting the completed selections
*/
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)
// requestWindowFeature(Window.FEATURE_NO_TITLE)
window!!.setBackgroundDrawableResource(android.R.color.transparent)
setCancelable(false)
//todo: amend widget text
confirm_text.text = StringBuilder().append("Create widget for ")
.append(viewModel.getWidgetStringName())
.append("?").toString()
confirm_yes.setOnClickListener {
viewModel.setWidgetStored()
val intent = Intent(context, CurrencyAppWidgetKotlin::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, IntArray(appWidgetId))
context.sendBroadcast(intent)
// Make sure we pass back the original appWidgetId
val resultValue = Intent()
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
activity.setResult(Activity.RESULT_OK, resultValue)
activity.finish()
}
confirm_no.setOnClickListener { dismiss() }
}
}

View File

@@ -0,0 +1,94 @@
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.Repository
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
class WidgetViewModel(
private val repository: Repository
) : 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)
if (widgetString.isNotEmpty()){
rateIdFrom.value = widgetString[0]
rateIdTo.value = widgetString[1]
}
}
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)
private fun arrayEntry(s: String?): String? {
val strings = repository.getArrayList()
var returnString: String? = strings[0]
for (string in strings) {
if (s == string.substring(0, 3)) {
returnString = string
}
}
return returnString
}
}

View File

@@ -0,0 +1,15 @@
package com.appttude.h_mal.easycc.mvvm.ui.widget
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
@Suppress("UNCHECKED_CAST")
class WidgetViewModelFactory (
private val repository: Repository
): ViewModelProvider.NewInstanceFactory(){
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return WidgetViewModel(repository) as T
}
}

View File

@@ -0,0 +1,18 @@
package com.appttude.h_mal.easycc.utils
import android.content.Context
import android.view.View
import android.widget.EditText
import android.widget.Toast
fun EditText.clearEditText(){
this.setText("")
}
fun View.hideView(vis : Boolean){
visibility = if (vis){ View.GONE } else { View.VISIBLE }
}
fun Context.DisplayToast(message: String){
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -5,5 +5,5 @@
android:startColor="@color/colour_three"
android:endColor="@color/colour_two"
android:type="linear"
android:angle="45"/>
android:angle="45" />
</shape>

View File

@@ -1,111 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="vertical"
android:focusable="false"
android:focusableInTouchMode="true"
tools:context="com.appttude.h_mal.easycc.MainActivity">
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModel" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_margin="12dp">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="false"
android:focusableInTouchMode="true"
tools:context="com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity">
<LinearLayout
android:id="@+id/whole_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:orientation="vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
style="@style/cardview_theme">
<TextView
android:id="@+id/currency_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="Currency One"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:background="@drawable/round_edit_text"
android:ems="10"
android:hint="insert value one"
android:textColorHighlight="#608d91"
android:inputType="numberDecimal"
android:padding="12dp"
android:selectAllOnFocus="true" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
style="@style/cardview_theme">
<TextView
android:id="@+id/currency_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="Currency Two"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_weight="7"
android:background="@drawable/round_edit_text"
android:ems="10"
android:hint="insert value two"
android:textColorHighlight="#608d91"
android:inputType="numberDecimal"
android:padding="12dp"
android:selectAllOnFocus="true" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTint="@color/colour_four"
android:indeterminateTintMode="src_atop" />
android:layout_margin="12dp">
<LinearLayout
android:id="@+id/whole_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:orientation="vertical">
<androidx.cardview.widget.CardView
style="@style/cardview_theme">
<TextView
android:tag="top"
android:id="@+id/currency_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@{viewmodel.rateIdFrom}"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</androidx.cardview.widget.CardView>
<EditText
android:id="@+id/topInsertValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:layout_marginTop="6dp"
android:background="@drawable/round_edit_text"
android:ems="10"
android:text="@={viewmodel.topVal}"
android:hint="insert value one"
android:textColorHighlight="#608d91"
android:inputType="numberDecimal"
android:padding="12dp"
android:tag="from"
android:selectAllOnFocus="true" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
style="@style/cardview_theme">
<TextView
android:id="@+id/currency_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@{viewmodel.rateIdTo}"
android:tag="bottom"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</androidx.cardview.widget.CardView>
<EditText
android:id="@+id/bottomInsertValues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_weight="7"
android:background="@drawable/round_edit_text"
android:ems="10"
android:text="@={viewmodel.bottomVal}"
android:hint="insert value two"
android:textColorHighlight="#608d91"
android:inputType="numberDecimal"
android:padding="12dp"
android:tag="to"
android:selectAllOnFocus="true" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTint="@color/colour_four"
android:indeterminateTintMode="src_atop"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</layout>

View File

@@ -6,7 +6,7 @@
android:orientation="vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardBackgroundColor="@color/colour_three"
@@ -15,6 +15,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:orientation="vertical">
<TextView
@@ -51,6 +52,6 @@
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -1,26 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_margin="10dp"
android:orientation="vertical">
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModel" />
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="false"
android:focusableInTouchMode="true"
tools:context=".mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
<LinearLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/whole_view">
android:layout_centerInParent="true"
android:layout_margin="12dp">
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/whole_view">
<androidx.cardview.widget.CardView
style="@style/cardview_theme"
android:layout_margin="11dp">
@@ -29,14 +40,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="Currency One"
android:onClick="@{viewmodel::selectCurrencyOnClick}"
android:tag="top"
android:text="@={viewmodel.rateIdFrom}"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
android:textSize="18sp"
tools:text="Currency One" />
</androidx.cardview.widget.CardView>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView
style="@style/cardview_theme"
android:layout_margin="11dp">
@@ -45,26 +58,31 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="Currency Two"
android:onClick="@{viewmodel::selectCurrencyOnClick}"
android:tag="bottom"
android:text="@={viewmodel.rateIdTo}"
android:textColor="@color/colour_five"
android:textSize="18sp" />
</android.support.v7.widget.CardView>
android:textSize="18sp"
tools:text="Currency Two" />
</androidx.cardview.widget.CardView>
</LinearLayout>
<TextView
android:layout_marginTop="12dp"
android:layout_marginEnd="22dp"
android:id="@+id/submit_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/whole_view"
android:layout_alignParentEnd="true"
android:textColor="@color/colour_five"
android:onClick="@{viewmodel::submitSelectionOnClick}"
android:text="Submit" />
</RelativeLayout>
</LinearLayout>
<TextView
android:layout_marginTop="12dp"
android:layout_marginRight="22dp"
android:id="@+id/submit_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/whole_view"
android:layout_alignParentRight="true"
android:textColor="@color/colour_five"
android:text="Submit" />
</RelativeLayout>
</RelativeLayout>
</layout>

View File

@@ -0,0 +1,21 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#09C"
android:padding="@dimen/widget_margin">
<TextView
android:id="@+id/appwidget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:background="#09C"
android:contentDescription="@string/appwidget_text"
android:text="@string/appwidget_text"
android:textColor="#ffffff"
android:textSize="24sp"
android:textStyle="bold|italic" />
</RelativeLayout>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/configure" />
<EditText
android:id="@+id/appwidget_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<Button
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/add_widget" />
</LinearLayout>

View File

@@ -6,9 +6,7 @@
android:backgroundTint="@android:color/transparent"
android:orientation="vertical">
<android.support.v7.widget.CardView
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_weight="1"
android:layout_height="0dp"
@@ -23,8 +21,8 @@
android:layout_height="match_parent">
</ListView>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="45dp"
@@ -38,6 +36,6 @@
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:hint="Search Currency" />
</android.support.v7.widget.CardView>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -160,4 +160,5 @@
</string-array>
<string name="configure">Configure</string>
<string name="add_widget">Add widget</string>
<string name="appwidget_text">EXAMPLE</string>
</resources>

View File

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

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"
android:initialKeyguardLayout="@layout/currency_kotlin_app_widget"
android:initialLayout="@layout/currency_kotlin_app_widget"
android:minWidth="110dp"
android:minHeight="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen|keyguard"></appwidget-provider>

View File

@@ -1,18 +1,7 @@
package com.appttude.h_mal.easycc;
import android.content.Context;
import android.content.res.Resources;
import android.os.AsyncTask;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import static com.appttude.h_mal.easycc.PublicMethods.UriBuilder;
import static com.appttude.h_mal.easycc.PublicMethods.createUrl;
import static com.appttude.h_mal.easycc.PublicMethods.makeHttpRequest;
import static org.junit.Assert.*;
/**

View File

@@ -1,16 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
buildscript {
ext.kotlin_version = '1.3.61'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.android.tools.build:gradle:3.6.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }
}
allprojects {

View File

@@ -9,6 +9,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.

View File

@@ -1,6 +1,6 @@
#Sun Nov 04 16:25:28 AEST 2018
#Thu Mar 05 18:27:33 UTC 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip