From 9a2cdf7e7ac089d512be1b6dc49df6e40fc65873 Mon Sep 17 00:00:00 2001 From: Kasim Rangwala Date: Wed, 30 Jan 2019 03:09:36 +0530 Subject: [PATCH] Migrated to AndroidX Added experimental Spanish Translation Various bug fixes --- .idea/caches/build_file_checksums.ser | Bin 546 -> 546 bytes .idea/codeStyles/Project.xml | 6 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/misc.xml | 28 +- README.md | 4 +- app/build.gradle | 47 +- app/proguard-rules.pro | 1 + .../ExampleInstrumentedTest.kt | 8 +- app/src/main/AndroidManifest.xml | 114 ++-- .../io/gripxtech/odoojsonrpcclient/App.kt | 24 +- .../odoojsonrpcclient/MainActivity.kt | 89 +-- .../io/gripxtech/odoojsonrpcclient/Utils.kt | 184 ++++- .../gripxtech/odoojsonrpcclient/core/Odoo.kt | 628 ++++++++++-------- .../odoojsonrpcclient/core/OdooUser.kt | 49 +- .../authenticator/AccountAuthenticator.kt | 5 +- .../core/authenticator/LoginActivity.kt | 378 ++++++++--- .../authenticator/ManageAccountActivity.kt | 33 +- .../authenticator/ManageAccountAdapter.kt | 16 +- .../authenticator/ManageAccountViewHolder.kt | 2 +- .../core/authenticator/ProfileActivity.kt | 20 +- .../core/authenticator/SplashActivity.kt | 127 +++- .../core/entities/Many2One.kt | 29 +- .../core/entities/route/RouteReqBody.kt | 2 +- .../authenticate/AuthenticateResult.kt | 1 + .../core/preferences/SettingsActivity.kt | 22 +- .../core/preferences/SettingsFragment.kt | 86 ++- .../core/utils/ClonedCookie.kt | 8 +- .../core/utils/CookiePrefs.kt | 11 +- .../core/utils/LetterTileProvider.kt | 4 +- .../core/utils/LocaleHelper.kt | 31 + .../core/utils/LocalePrefs.kt | 17 + .../core/utils/NavHeaderViewHolder.kt | 41 +- .../core/utils/OdooFileProvider.kt | 5 + .../odoojsonrpcclient/core/utils/Prefs.kt | 6 +- .../core/utils/Retrofit2Helper.kt | 74 ++- .../core/utils/android/ktx/AndroidKtx.kt | 26 +- .../android/ktx/CompletableObserverEx.kt | 45 ++ .../utils/android/ktx/FlowableSubscriberEx.kt | 57 ++ .../core/utils/android/ktx/MaybeObserverEx.kt | 57 ++ .../utils/android/ktx/SingleObserverEx.kt | 45 ++ .../utils/recycler/RecyclerBaseAdapter.kt | 16 +- .../recycler/decorators/GridItemDecorator.kt | 77 ++- .../HorizontalLinearItemDecorator.kt | 22 +- .../decorators/VerticalLinearItemDecorator.kt | 20 +- .../core/utils/recycler/entities/EmptyItem.kt | 2 +- .../recycler/entities/EmptyViewHolder.kt | 2 +- .../recycler/entities/ErrorViewHolder.kt | 2 +- .../utils/recycler/entities/LessViewHolder.kt | 2 +- .../utils/recycler/entities/MoreViewHolder.kt | 2 +- .../helpers/ItemTouchHelperAdapter.kt | 2 +- .../recycler/helpers/OnStartDragListener.kt | 2 +- .../helpers/SimpleItemTouchHelperCallback.kt | 31 +- .../core/utils/tabs/PagerAdapter.kt | 10 +- .../core/utils/tabs/PagerItem.kt | 2 +- .../customer/CustomerAdapter.kt | 6 +- .../customer/CustomerFragment.kt | 79 ++- .../customer/CustomerViewHolder.kt | 2 +- .../customer/entities/Customer.kt | 110 +-- .../baseline_feedback_black_18.png | Bin 0 -> 184 bytes .../baseline_feedback_black_18.png | Bin 0 -> 152 bytes .../baseline_feedback_black_18.png | Bin 0 -> 175 bytes .../baseline_feedback_black_18.png | Bin 0 -> 244 bytes .../baseline_feedback_black_18.png | Bin 0 -> 256 bytes .../res/drawable/ic_assignment_black_24dp.xml | 9 + .../res/drawable/ic_dashboard_white_24dp.xml | 6 + .../main/res/drawable/ic_home_black_24dp.xml | 9 + .../res/drawable/ic_launcher_background.xml | 74 +++ .../res/drawable/ic_person_add_black_24dp.xml | 9 + app/src/main/res/layout/activity_login.xml | 535 ++++++++------- app/src/main/res/layout/activity_main.xml | 20 +- .../res/layout/activity_main_nav_header.xml | 97 +-- .../res/layout/activity_manage_account.xml | 12 +- app/src/main/res/layout/activity_profile.xml | 18 +- app/src/main/res/layout/activity_settings.xml | 10 +- app/src/main/res/layout/fragment_customer.xml | 16 +- .../main/res/layout/item_view_customer.xml | 102 +-- .../res/layout/item_view_manage_account.xml | 172 ++--- .../res/layout/item_view_recycler_empty.xml | 4 +- .../res/layout/item_view_recycler_error.xml | 4 +- app/src/main/res/menu/menu_main_nav.xml | 57 +- app/src/main/res/values-es/strings.xml | 88 +++ app/src/main/res/values/arrays.xml | 3 + app/src/main/res/values/colors.xml | 16 +- app/src/main/res/values/configs.xml | 28 +- app/src/main/res/values/strings.xml | 36 +- app/src/main/res/values/styles.xml | 20 + app/src/main/res/xml/file_paths.xml | 5 +- app/src/main/res/xml/preferences.xml | 52 +- build.gradle | 13 +- gradle.properties | 4 + gradle/wrapper/gradle-wrapper.properties | 2 +- 91 files changed, 2639 insertions(+), 1506 deletions(-) create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocaleHelper.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocalePrefs.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/OdooFileProvider.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/CompletableObserverEx.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/FlowableSubscriberEx.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/MaybeObserverEx.kt create mode 100644 app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/SingleObserverEx.kt create mode 100644 app/src/main/res/drawable-hdpi/baseline_feedback_black_18.png create mode 100644 app/src/main/res/drawable-mdpi/baseline_feedback_black_18.png create mode 100644 app/src/main/res/drawable-xhdpi/baseline_feedback_black_18.png create mode 100644 app/src/main/res/drawable-xxhdpi/baseline_feedback_black_18.png create mode 100644 app/src/main/res/drawable-xxxhdpi/baseline_feedback_black_18.png create mode 100644 app/src/main/res/drawable/ic_assignment_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_dashboard_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_home_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_person_add_black_24dp.xml create mode 100644 app/src/main/res/values-es/strings.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index e554c5e016853d9297708bb60066812a65bf6aac..f169266b4ac364f2280960ef62c506f6339d5bed 100644 GIT binary patch delta 140 zcmV;70CWGM1fm3xmj!5>fLCgfoOe75;jT8CYhsphF0rN~XJLzU01tCzbaZKMXLBxR za$#g_Wp!}?egFml000oD{u;xPX|?4jT3%CTFC`n3PXQSah(F1&biK%0?I%rhJC-qH ulXw9g5EPP38sMejlsPa%isEcbg_ETLY!L{A_Y-{wyx+J1}%6iS2yDT delta 140 zcmV;70CWGM1fm3xmjz`m4D(izoOe8+;%zCV-1R2POh4cO(AJ1_01tCzbaZKMXLBxR za$#g_Wp!}?egFml000o4*ZxpO@yA2a}}%Y!UN~1FF1WrB1RvW;5o^5Mp@g-8%08 diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 30aa626..34dc27c 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 99202cc..af0bbdd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,29 +1,9 @@ - - - + + + + diff --git a/README.md b/README.md index b096ac1..27eb6ec 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Update [`tokenKey`](https://github.com/kasim1011/OdooJsonRpcClient/blob/5edf9e5e Do not hesitate to report [issues](https://github.com/kasim1011/OdooJsonRpcClient/issues) you may find. -Get the **sample APK** from [release](https://github.com/kasim1011/OdooJsonRpcClient/releases) saction. +Get the **sample APK** from [release](https://github.com/kasim1011/OdooJsonRpcClient/releases) section. Next Milestone: - **Synchronization** and **Persistence** using [Room Persistence Library](https://developer.android.com/topic/libraries/architecture/room) @@ -288,7 +288,7 @@ Odoo.searchRead(model = "res.partner", fields = listOf( val searchRead = response.body()!! if (searchRead.isSuccessful) { val result = searchRead.result - // use gson to convert records (jsonArray) to list of pojo + // use gson to convert records (jsonArray) to list of POJO // ... } else { // Odoo specific error diff --git a/app/build.gradle b/app/build.gradle index b20fddd..e2ea9f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-android-extensions' -apply plugin: 'io.mironov.smuggler' // Create a variable called keystorePropertiesFile, and initialize it to your // keystore.properties file, in the rootProject folder. @@ -24,15 +23,17 @@ android { } } - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { applicationId "io.gripxtech.odoojsonrpcclient" minSdkVersion 17 - targetSdkVersion 27 - versionCode 3 - versionName "1.02" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + targetSdkVersion 28 + versionCode 4 + versionName "1.03" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + + multiDexEnabled true } buildTypes { release { @@ -45,6 +46,9 @@ android { signingConfig signingConfigs.config } } + androidExtensions { + experimental = true + } dataBinding { enabled true } @@ -56,30 +60,29 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "com.android.support:appcompat-v7:$support_library_version" - implementation "com.android.support:design:$support_library_version" - implementation "com.android.support:recyclerview-v7:$support_library_version" - implementation "com.android.support:cardview-v7:$support_library_version" - implementation "com.android.support:preference-v14:$support_library_version" - implementation 'com.android.support.constraint:constraint-layout:1.1.2' - implementation 'com.android.support:support-v4:27.1.1' - kapt "com.android.databinding:compiler:$gradle_version" + implementation 'androidx.appcompat:appcompat:1.1.0-alpha01' + implementation 'com.google.android.material:material:1.1.0-alpha02' + implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha01' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.preference:preference-ktx:1.1.0-alpha02' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' + implementation 'androidx.multidex:multidex:2.0.1' implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" implementation "com.github.bumptech.glide:glide:$glide_version" kapt "com.github.bumptech.glide:compiler:$glide_version" implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version" - implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'com.google.code.gson:gson:2.8.5' implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" - implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' - implementation 'io.reactivex.rxjava2:rxjava:2.1.16' - implementation 'com.jakewharton.timber:timber:4.7.0' - implementation 'com.intuit.sdp:sdp-android:1.0.5' - implementation 'com.intuit.ssp:ssp-android:1.0.5' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.6' + implementation 'de.hdodenhof:circleimageview:3.0.0' + implementation 'com.jakewharton.timber:timber:4.7.1' + implementation 'com.intuit.sdp:sdp-android:1.0.6' + implementation 'com.intuit.ssp:ssp-android:1.0.6' 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 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 54bce4c..03b5295 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -59,6 +59,7 @@ # Application classes that will be serialized/deserialized over Gson -keep class io.gripxtech.odoojsonrpcclient.core.entities.** { *; } -keep interface io.gripxtech.odoojsonrpcclient.core.web.** { *; } +-keep class io.gripxtech.odoojsonrpcclient.customer.entities.** { *; } # Prevent proguard from stripping interface information from TypeAdapterFactory, # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) diff --git a/app/src/androidTest/java/io/gripxtech/odoojsonrpcclient/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/gripxtech/odoojsonrpcclient/ExampleInstrumentedTest.kt index 4488469..8d9452b 100644 --- a/app/src/androidTest/java/io/gripxtech/odoojsonrpcclient/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/io/gripxtech/odoojsonrpcclient/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package io.gripxtech.odoojsonrpcclient -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 - +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8903039..0f65bda 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,76 +1,90 @@ + xmlns:tools="http://schemas.android.com/tools" + package="io.gripxtech.odoojsonrpcclient"> - - - + + + - - + android:name="android.permission.GET_ACCOUNTS" + android:maxSdkVersion="22"/> + + + - + android:largeHeap="true" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme" + android:usesCleartextTraffic="true" + tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"> + android:name=".MainActivity" + android:label="@string/app_name" + android:screenOrientation="portrait" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".core.authenticator.LoginActivity" + android:label="@string/app_name" + android:screenOrientation="portrait" + android:theme="@style/AppTheme.NoActionBar"/> + - + - + + android:name=".core.authenticator.ProfileActivity" + android:label="@string/action_profile" + android:screenOrientation="portrait" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".core.authenticator.ManageAccountActivity" + android:label="@string/action_manage_account" + android:screenOrientation="portrait" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".core.preferences.SettingsActivity" + android:label="@string/action_settings" + android:screenOrientation="portrait" + android:theme="@style/AppTheme.NoActionBar"/> + android:name=".core.authenticator.AuthenticatorService" + android:enabled="true" + android:exported="true" + tools:ignore="ExportedService"> - + + android:name="android.accounts.AccountAuthenticator" + android:resource="@xml/authenticator"/> + + + + + \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/App.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/App.kt index fea248e..7be02e3 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/App.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/App.kt @@ -1,13 +1,16 @@ package io.gripxtech.odoojsonrpcclient -import android.app.Application +import android.content.Context +import android.content.res.Configuration +import androidx.multidex.MultiDexApplication import io.gripxtech.odoojsonrpcclient.core.Odoo import io.gripxtech.odoojsonrpcclient.core.utils.CookiePrefs import io.gripxtech.odoojsonrpcclient.core.utils.LetterTileProvider +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.core.utils.Retrofit2Helper import timber.log.Timber -class App : Application() { +class App : MultiDexApplication() { companion object { const val KEY_ACCOUNT_TYPE = "${BuildConfig.APPLICATION_ID}.auth" @@ -21,10 +24,23 @@ class App : Application() { CookiePrefs(this) } + override fun attachBaseContext(base: Context?) { + if (base != null) { + super.attachBaseContext(LocaleHelper.setLocale(base)) + } else { + super.attachBaseContext(base) + } + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } + override fun onCreate() { super.onCreate() - Odoo.app = this Retrofit2Helper.app = this + Odoo.app = this if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) @@ -32,5 +48,5 @@ class App : Application() { } fun getLetterTile(displayName: String): ByteArray = - letterTileProvider.getLetterTile(displayName) + letterTileProvider.getLetterTile(displayName) } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/MainActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/MainActivity.kt index b672142..1e2926d 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/MainActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/MainActivity.kt @@ -1,17 +1,16 @@ package io.gripxtech.odoojsonrpcclient +import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.databinding.DataBindingUtil import android.os.Bundle -import android.support.v4.view.GravityCompat -import android.support.v7.app.ActionBarDrawerToggle -import android.support.v7.app.AppCompatActivity -import android.support.v7.app.AppCompatDelegate -import io.gripxtech.odoojsonrpcclient.core.authenticator.LoginActivity -import io.gripxtech.odoojsonrpcclient.core.authenticator.ManageAccountActivity -import io.gripxtech.odoojsonrpcclient.core.authenticator.ProfileActivity +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.view.GravityCompat +import androidx.databinding.DataBindingUtil import io.gripxtech.odoojsonrpcclient.core.preferences.SettingsActivity +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.core.utils.NavHeaderViewHolder import io.gripxtech.odoojsonrpcclient.core.utils.android.ktx.postEx import io.gripxtech.odoojsonrpcclient.customer.CustomerFragment @@ -35,7 +34,6 @@ class MainActivity : AppCompatActivity() { private lateinit var navHeader: NavHeaderViewHolder private var currentDrawerItemID: Int = 0 - private var drawerClickStatus: Boolean = false private val customerFragment: CustomerFragment by lazy { CustomerFragment.newInstance(CustomerFragment.Companion.CustomerType.Customer) @@ -49,6 +47,14 @@ class MainActivity : AppCompatActivity() { CustomerFragment.newInstance(CustomerFragment.Companion.CustomerType.Company) } + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) app = application as App @@ -70,8 +76,8 @@ class MainActivity : AppCompatActivity() { setTitle(R.string.app_name) drawerToggle = ActionBarDrawerToggle( - this, binding.dl, binding.tb, - R.string.navigation_drawer_open, R.string.navigation_drawer_close + this, binding.dl, binding.tb, + R.string.navigation_drawer_open, R.string.navigation_drawer_close ) binding.dl.addDrawerListener(drawerToggle) drawerToggle.syncState() @@ -81,30 +87,10 @@ class MainActivity : AppCompatActivity() { navHeader = NavHeaderViewHolder(view) val user = getActiveOdooUser() if (user != null) { - navHeader.setUser(user) + navHeader.setUser(user, GlideApp.with(this@MainActivity)) } } - drawerClickStatus = false - - navHeader.menuToggle.setOnClickListener { - val menu = binding.nv.menu - if (drawerClickStatus) { - menu.setGroupVisible(R.id.nav_menu_1, true) - menu.setGroupVisible(R.id.nav_menu_2, true) - menu.setGroupVisible(R.id.nav_menu_3, true) - menu.setGroupVisible(R.id.nav_menu_4, false) - navHeader.menuToggleImage.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp) - } else { - menu.setGroupVisible(R.id.nav_menu_1, false) - menu.setGroupVisible(R.id.nav_menu_2, false) - menu.setGroupVisible(R.id.nav_menu_3, false) - menu.setGroupVisible(R.id.nav_menu_4, true) - navHeader.menuToggleImage.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp) - } - drawerClickStatus = !drawerClickStatus - } - binding.nv.setNavigationItemSelectedListener { item -> binding.dl.postEx { closeDrawer(GravityCompat.START) } when (item.itemId) { @@ -126,28 +112,10 @@ class MainActivity : AppCompatActivity() { } true } - R.id.nav_profile -> { - if (getActiveOdooUser() != null) { - startActivity(Intent(this, ProfileActivity::class.java)) - } else { - showMessage(message = getString(R.string.error_active_user)) - } - true - } R.id.nav_settings -> { startActivity(Intent(this, SettingsActivity::class.java)) true } - R.id.nav_add_account -> { - val intent = Intent(this, LoginActivity::class.java) - intent.putExtra(LoginActivity.FROM_APP_SETTINGS, true) - startActivity(intent) - true - } - R.id.nav_manage_account -> { - startActivity(Intent(this, ManageAccountActivity::class.java)) - true - } else -> { true } @@ -159,9 +127,10 @@ class MainActivity : AppCompatActivity() { } } - override fun onConfigurationChanged(newConfig: Configuration?) { + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) drawerToggle.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) } private fun loadFragment(currentDrawerItemID: Int) { @@ -170,21 +139,21 @@ class MainActivity : AppCompatActivity() { when (currentDrawerItemID) { ACTION_CUSTOMER -> { supportFragmentManager - .beginTransaction() - .replace(R.id.clMain, customerFragment, getString(R.string.action_customer)) - .commit() + .beginTransaction() + .replace(R.id.clMain, customerFragment, getString(R.string.action_customer)) + .commit() } ACTION_SUPPLIER -> { supportFragmentManager - .beginTransaction() - .replace(R.id.clMain, supplierFragment, getString(R.string.action_supplier)) - .commit() + .beginTransaction() + .replace(R.id.clMain, supplierFragment, getString(R.string.action_supplier)) + .commit() } ACTION_COMPANY -> { supportFragmentManager - .beginTransaction() - .replace(R.id.clMain, companyFragment, getString(R.string.action_company)) - .commit() + .beginTransaction() + .replace(R.id.clMain, companyFragment, getString(R.string.action_company)) + .commit() } } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/Utils.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/Utils.kt index 94f8424..8b4a561 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/Utils.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/Utils.kt @@ -5,24 +5,34 @@ import android.accounts.AccountManager import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.ConnectivityManager +import android.net.Uri import android.os.Build import android.os.Handler -import android.support.v4.app.ActivityCompat -import android.support.v4.app.TaskStackBuilder -import android.support.v7.app.AlertDialog -import android.support.v7.app.AppCompatActivity import android.text.Html import android.text.Spanned +import android.util.Base64 import android.view.inputmethod.InputMethodManager +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.app.TaskStackBuilder import com.google.gson.* import io.gripxtech.odoojsonrpcclient.core.Odoo import io.gripxtech.odoojsonrpcclient.core.OdooUser import io.gripxtech.odoojsonrpcclient.core.authenticator.SplashActivity import io.gripxtech.odoojsonrpcclient.core.entities.Many2One +import io.gripxtech.odoojsonrpcclient.core.entities.odooError.OdooError import io.gripxtech.odoojsonrpcclient.core.entities.session.authenticate.AuthenticateResult +import io.gripxtech.odoojsonrpcclient.core.utils.decryptAES import io.gripxtech.odoojsonrpcclient.core.utils.encryptAES import retrofit2.Response +import java.io.ByteArrayOutputStream +import java.text.SimpleDateFormat +import java.util.* + const val RECORD_LIMIT = 10 @@ -87,19 +97,58 @@ fun Context.logoutOdooUser(odooUser: OdooUser) { accountManager.setUserData(odooUser.account, "active", "false") } +fun Context.getCookies(odooUser: OdooUser): String { + val accountManager = AccountManager.get(this) + return accountManager.getUserData(odooUser.account, "cookies")?.decryptAES() ?: "" +} + +fun Context.setCookies(odooUser: OdooUser, cookiesStr: String) { + val accountManager = AccountManager.get(this) + accountManager.setUserData(odooUser.account, "cookies", cookiesStr.encryptAES()) +} + fun Context.deleteOdooUser(odooUser: OdooUser): Boolean { val accountManager = AccountManager.get(this) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { accountManager.removeAccountExplicitly(odooUser.account) } else { @Suppress("DEPRECATION") - val result = accountManager.removeAccount(odooUser.account, { _ -> + val result = accountManager.removeAccount(odooUser.account, { }, Handler(this.mainLooper)) result != null && result.result != null && result.result!! } } +fun String.toDate(dateFormat: String = "yyyy-MM-dd HH:mm:ss"): Date { + val parser = SimpleDateFormat(dateFormat, Locale.US) + return parser.parse(this) +} + +fun Date.formatTo(dateFormat: String): String { + val formatter = SimpleDateFormat(dateFormat, Locale.US) + return formatter.format(this) +} + +fun String.toDate(dateFormat: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.getTimeZone("UTC")): Date { + val parser = SimpleDateFormat(dateFormat, Locale.getDefault()) + parser.timeZone = timeZone + return parser.parse(this) +} + +fun Date.formatTo(dateFormat: String, timeZone: TimeZone = TimeZone.getDefault()): String { + val formatter = SimpleDateFormat(dateFormat, Locale.getDefault()) + formatter.timeZone = timeZone + return formatter.format(this) +} + +fun Bitmap.toBase64(): String { + val byteArrayOutputStream = ByteArrayOutputStream() + compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + val byteArray = byteArrayOutputStream.toByteArray() + return Base64.encodeToString(byteArray, Base64.DEFAULT) +} + val JsonElement.isManyToOne: Boolean get() = isJsonArray && asJsonArray.size() == 2 val JsonElement.asManyToOne: Many2One @@ -109,6 +158,16 @@ val JsonElement.asManyToOne: Many2One Many2One(JsonArray().apply { add(0); add("") }) } +fun Many2One.toStringList(): ArrayList = ArrayList().apply { + add(id.toString()) + add(name) +} + +fun ArrayList.toJsonElement(): JsonElement = JsonArray().apply { + add(this[0].asInt) + add(this[1]) +} + val JsonArray.asIntList: List get() = this.map { it.asInt @@ -126,6 +185,8 @@ fun AppCompatActivity.hideSoftKeyboard() { if (view != null) { val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(view.windowToken, 0) + + view.clearFocus() } } @@ -138,34 +199,95 @@ fun AppCompatActivity.restartApp() { var alertDialog: AlertDialog? = null fun AppCompatActivity.showMessage( - title: CharSequence? = null, - message: CharSequence?, - cancelable: Boolean = false, - positiveButton: CharSequence = getString(R.string.ok), - positiveButtonListener: DialogInterface.OnClickListener = DialogInterface.OnClickListener { _, _ -> }, - showNegativeButton: Boolean = false, - negativeButton: CharSequence = getString(R.string.cancel), - negativeButtonListener: DialogInterface.OnClickListener = DialogInterface.OnClickListener { _, _ -> } + title: CharSequence? = null, + message: CharSequence?, + cancelable: Boolean = false, + icon: Drawable? = null, + positiveButton: CharSequence = getString(R.string.ok), + positiveButtonListener: DialogInterface.OnClickListener? = DialogInterface.OnClickListener { _, _ -> } +): AlertDialog = showMessage( + title, message, cancelable, icon, positiveButton, positiveButtonListener, + false, getString(R.string.cancel), DialogInterface.OnClickListener { _, _ -> } +) + +fun AppCompatActivity.showMessage( + title: CharSequence? = null, + message: CharSequence?, + cancelable: Boolean = false, + icon: Drawable? = null, + positiveButton: CharSequence = getString(R.string.ok), + positiveButtonListener: DialogInterface.OnClickListener? = DialogInterface.OnClickListener { _, _ -> }, + showNegativeButton: Boolean = false, + negativeButton: CharSequence = getString(R.string.cancel), + negativeButtonListener: DialogInterface.OnClickListener? = DialogInterface.OnClickListener { _, _ -> }, + showNeutralButton: Boolean = false, + neutralButton: CharSequence = getString(R.string.cancel), + neutralButtonListener: DialogInterface.OnClickListener? = DialogInterface.OnClickListener { _, _ -> } ): AlertDialog { alertDialog?.dismiss() alertDialog = AlertDialog.Builder(this, R.style.AppAlertDialogTheme) - .setTitle(title) - .setMessage(if (message?.isNotEmpty() == true) { - message - } else { - getString(R.string.generic_error) - }) - .setCancelable(cancelable) - .setPositiveButton(positiveButton, positiveButtonListener) - .apply { - if (showNegativeButton) { - setNegativeButton(negativeButton, negativeButtonListener) - } + .setTitle(title) + .setMessage(if (message?.isNotEmpty() == true) { + message + } else { + getString(R.string.generic_error) + }) + .setCancelable(cancelable) + .setIcon(icon) + .setPositiveButton(positiveButton, positiveButtonListener) + .apply { + if (showNegativeButton) { + setNegativeButton(negativeButton, negativeButtonListener) + } + if (showNeutralButton) { + setNeutralButton(neutralButton, neutralButtonListener) } - .show() + } + .show() return alertDialog!! } +fun AppCompatActivity.promptReport(odooError: OdooError) { + showMessage( + message = odooError.data.message, + showNeutralButton = true, + neutralButton = getString(R.string.error_report), + neutralButtonListener = DialogInterface.OnClickListener { _, _ -> + val intent = emailIntent( + address = arrayOf(getString(R.string.preference_contact_summary)), + cc = arrayOf(), + subject = "${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) " + + getString(R.string.report_feedback), + body = "Name: ${odooError.data.name}\n\n" + + "Message: ${odooError.data.message}\n\n" + + "Exception Type: ${odooError.data.exceptionType}\n\n" + + "Arguments: ${odooError.data.arguments}\n\n" + + "Debug: ${odooError.data.debug}\n\n" + ) + try { + startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + showMessage(message = getString(R.string.preference_error_email_intent)) + } + } + ) +} + +fun AppCompatActivity.emailIntent( + address: Array, + cc: Array, + subject: String, + body: String +): Intent { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("mailto:")) + intent.putExtra(Intent.EXTRA_EMAIL, address) + intent.putExtra(Intent.EXTRA_CC, cc) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.putExtra(Intent.EXTRA_TEXT, body) + return Intent.createChooser(intent, getString(R.string.preference_prompt_email_intent)) +} + @Suppress("DEPRECATION") fun AppCompatActivity.showServerErrorMessage( response: Response<*>, @@ -181,7 +303,13 @@ fun AppCompatActivity.showServerErrorMessage( ) fun AppCompatActivity.closeApp(message: String = getString(R.string.generic_error)): AlertDialog = - showMessage(getString(R.string.fatal_error), message, false, getString(R.string.exit), DialogInterface.OnClickListener { _, _ -> + showMessage( + getString(R.string.fatal_error), + message, + false, + null, + getString(R.string.exit), + DialogInterface.OnClickListener { _, _ -> ActivityCompat.finishAffinity(this) }) @@ -208,7 +336,7 @@ fun AppCompatActivity.isDeviceOnline(): Boolean { var isConnected = false val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val nInfo = manager.activeNetworkInfo - if (nInfo != null && nInfo.isConnectedOrConnecting) { + if (nInfo != null && nInfo.isConnected) { isConnected = true } return isConnected diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/Odoo.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/Odoo.kt index e9ce99e..c1fb9cd 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/Odoo.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/Odoo.kt @@ -95,27 +95,28 @@ object Odoo { @Suppress("PlatformExtensionReceiverOfInline") fun fromAccount(manager: AccountManager, account: Account) = OdooUser( - Retrofit2Helper.Companion.Protocol.valueOf( - manager.getUserData(account, "protocol") - ), - manager.getUserData(account, "host"), - manager.getUserData(account, "login"), - manager.getUserData(account, "password").decryptAES(), - manager.getUserData(account, "database"), - manager.getUserData(account, "serverVersion"), - manager.getUserData(account, "isAdmin").toBoolean(), - manager.getUserData(account, "id").toInt(), - manager.getUserData(account, "name"), - manager.getUserData(account, "imageSmall"), - manager.getUserData(account, "partnerId").toInt(), - manager.getUserData(account, "context").toJsonObject(), - manager.getUserData(account, "active").toBoolean(), - account + Retrofit2Helper.Companion.Protocol.valueOf( + manager.getUserData(account, "protocol") + ), + manager.getUserData(account, "host"), + manager.getUserData(account, "login"), + manager.getUserData(account, "password").decryptAES(), + manager.getUserData(account, "database"), + manager.getUserData(account, "serverVersion"), + manager.getUserData(account, "isAdmin").toBoolean(), + manager.getUserData(account, "isSuperuser").toBoolean(), + manager.getUserData(account, "id").toInt(), + manager.getUserData(account, "name"), + manager.getUserData(account, "imageSmall"), + manager.getUserData(account, "partnerId").toInt(), + manager.getUserData(account, "context").toJsonObject(), + manager.getUserData(account, "active").toBoolean(), + account ) private val retrofit2Helper = Retrofit2Helper( - protocol, - host + protocol, + host ) private val retrofit get() = retrofit2Helper.retrofit @@ -136,52 +137,56 @@ object Odoo { val requestBody = VersionInfoReqBody(id = jsonRpcId) val observable = request.versionInfo(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun listDb(serverVersion: String, callback: ResponseObserver.() -> Unit) { val requestBody = ListDbReqBody(id = jsonRpcId) val observable = - when { - serverVersion.startsWith("8.") -> { - val request = retrofit.create(ListDbV8Request::class.java) - request.listDb(requestBody) - } - serverVersion.startsWith("9.") -> { - val request = retrofit.create(ListDbV9Request::class.java) - request.listDb(requestBody) - } - else -> { - val request = retrofit.create(ListDbRequest::class.java) - request.listDb(requestBody) - } + when { + serverVersion.startsWith("8.") -> { + val request = retrofit.create(ListDbV8Request::class.java) + request.listDb(requestBody) + } + serverVersion.startsWith("9.") -> { + val request = retrofit.create(ListDbV9Request::class.java) + request.listDb(requestBody) } + else -> { + val request = retrofit.create(ListDbRequest::class.java) + request.listDb(requestBody) + } + } observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } private val pendingAuthenticateCallbacks: ArrayList.() -> Unit> = arrayListOf() val pendingAuthenticateCookies: ArrayList = arrayListOf() @Synchronized - fun authenticate(login: String, password: String, database: String, - callback: ResponseObserver.() -> Unit) { + fun authenticate( + login: String, password: String, database: String, + callback: ResponseObserver.() -> Unit + ) { pendingAuthenticateCallbacks += callback if (pendingAuthenticateCallbacks.size == 1) { val request = retrofit.create(AuthenticateRequest::class.java) - val requestBody = AuthenticateReqBody(id = jsonRpcId, params = AuthenticateParams( + val requestBody = AuthenticateReqBody( + id = jsonRpcId, params = AuthenticateParams( host, login, password, database - )) + ) + ) val observable = request.authenticate(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply { - (pendingAuthenticateCallbacks.size - 1 downTo 0).map { - pendingAuthenticateCallbacks.removeAt(it) - }.forEach { it() } - }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply { + (pendingAuthenticateCallbacks.size - 1 downTo 0).map { + pendingAuthenticateCallbacks.removeAt(it) + }.forEach { it() } + }) } } @@ -190,8 +195,8 @@ object Odoo { val requestBody = CheckReqBody(id = jsonRpcId) val observable = request.check(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun destroy(callback: ResponseObserver.() -> Unit) { @@ -199,8 +204,8 @@ object Odoo { val requestBody = DestroyReqBody(id = jsonRpcId) val observable = request.destroy(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun modules(callback: ResponseObserver.() -> Unit) { @@ -208,8 +213,8 @@ object Odoo { val requestBody = ModulesReqBody(id = jsonRpcId) val observable = request.modules(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun getSessionInfo(callback: ResponseObserver.() -> Unit) { @@ -217,133 +222,141 @@ object Odoo { val requestBody = GetSessionInfoReqBody(id = jsonRpcId) val observable = request.getSessionInfo(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun searchRead( - model: String, - fields: List = listOf(), - domain: List = listOf(), - offset: Int = 0, - limit: Int = 0, - sort: String = "", - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + fields: List = listOf(), + domain: List = listOf(), + offset: Int = 0, + limit: Int = 0, + sort: String = "", + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(SearchReadRequest::class.java) - val requestBody = SearchReadReqBody(id = jsonRpcId, params = SearchReadParams( + val requestBody = SearchReadReqBody( + id = jsonRpcId, params = SearchReadParams( model, fields, domain, offset, limit, sort, context - )) + ) + ) val observable = request.searchRead(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun load( - id: Int, - model: String, - fields: List = listOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + id: Int, + model: String, + fields: List = listOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(LoadRequest::class.java) - val requestBody = LoadReqBody(id = jsonRpcId, params = LoadParams( + val requestBody = LoadReqBody( + id = jsonRpcId, params = LoadParams( id, model, fields, context - )) + ) + ) val observable = request.load(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun callKw( - model: String, - method: String, - args: List, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + method: String, + args: List, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(CallKwRequest::class.java) - val requestBody = CallKwReqBody(id = jsonRpcId, params = CallKwParams( + val requestBody = CallKwReqBody( + id = jsonRpcId, params = CallKwParams( model, method, args, kwArgs, context - )) + ) + ) val observable = request.callKw(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun execWorkflow( - model: String, - id: Int, - signal: String, - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + id: Int, + signal: String, + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(ExecWorkflowRequest::class.java) - val requestBody = ExecWorkflowReqBody(id = jsonRpcId, params = ExecWorkflowParams( + val requestBody = ExecWorkflowReqBody( + id = jsonRpcId, params = ExecWorkflowParams( model, id, signal, context - )) + ) + ) val observable = request.execWorkflow(requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun route( - path1: String, - path2: String, - args: Map, - callback: ResponseObserver.() -> Unit + path1: String, + path2: String, + args: Any = mapOf(), + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(RouteRequest::class.java) val requestBody = RouteReqBody(id = jsonRpcId, params = args) val observable = request.route(path1, path2, requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun route3Path( - path1: String, - path2: String, - path3: String, - args: Map, - callback: ResponseObserver.() -> Unit + path1: String, + path2: String, + path3: String, + args: Any = mapOf(), + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(Route3PathRequest::class.java) val requestBody = RouteReqBody(id = jsonRpcId, params = args) val observable = request.route(path1, path2, path3, requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun route4Path( - path1: String, - path2: String, - path3: String, - path4: String, - args: Map, - callback: ResponseObserver.() -> Unit + path1: String, + path2: String, + path3: String, + path4: String, + args: Any = mapOf(), + callback: ResponseObserver.() -> Unit ) { val request = retrofit.create(Route4PathRequest::class.java) val requestBody = RouteReqBody(id = jsonRpcId, params = args) val observable = request.route(path1, path2, path3, path4, requestBody) observable.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(ResponseObserver().apply(callback)) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(ResponseObserver().apply(callback)) } fun create( - model: String, - values: Map, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + values: Map, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -354,15 +367,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(Create( - if (response.body()!!.isSuccessful) - response.body()!!.result.asLong - else - 0L - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + Create( + if (response.body()!!.isSuccessful) + response.body()!!.result.asLong + else + 0L + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -376,12 +393,12 @@ object Odoo { } fun read( - model: String, - ids: List, - fields: List, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + ids: List, + fields: List, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -392,15 +409,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(Read( - if (response.body()!!.isSuccessful) - response.body()!!.result - else - JsonArray() - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + Read( + if (response.body()!!.isSuccessful) + response.body()!!.result + else + JsonArray() + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -414,12 +435,12 @@ object Odoo { } fun write( - model: String, - ids: List, - values: Map, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + ids: List, + values: Map, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -430,15 +451,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(Write( - if (response.body()!!.isSuccessful) - response.body()!!.result.asBoolean - else - false - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + Write( + if (response.body()!!.isSuccessful) + response.body()!!.result.asBoolean + else + false + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -452,11 +477,11 @@ object Odoo { } fun unlink( - model: String, - ids: List, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + ids: List, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -467,15 +492,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(Unlink( - if (response.body()!!.isSuccessful) - response.body()!!.result.asBoolean - else - false - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + Unlink( + if (response.body()!!.isSuccessful) + response.body()!!.result.asBoolean + else + false + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -489,11 +518,11 @@ object Odoo { } fun nameGet( - model: String, - ids: List, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + ids: List, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -504,15 +533,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(NameGet( - if (response.body()!!.isSuccessful) - response.body()!!.result.asJsonArray - else - JsonArray() - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + NameGet( + if (response.body()!!.isSuccessful) + response.body()!!.result.asJsonArray + else + JsonArray() + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -526,11 +559,11 @@ object Odoo { } fun nameCreate( - model: String, - name: String, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + name: String, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -541,15 +574,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(NameCreate( - if (response.body()!!.isSuccessful) - response.body()!!.result.asJsonArray - else - JsonArray() - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + NameCreate( + if (response.body()!!.isSuccessful) + response.body()!!.result.asJsonArray + else + JsonArray() + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -563,37 +600,43 @@ object Odoo { } fun nameSearch( - model: String, - name: String = "", - args: List = listOf(), - operator: String = "ilike", - limit: Int = 0, - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + name: String = "", + args: List = listOf(), + operator: String = "ilike", + limit: Int = 0, + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() - callKw(model, "name_search", listOf(), mapOf( + callKw( + model, "name_search", listOf(), mapOf( "name" to name, "args" to args, "operator" to operator, "limit" to limit - ), context) { + ), context + ) { onSubscribe { disposable -> callbackEx.onSubscribe(disposable) } onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(NameSearch( - if (response.body()!!.isSuccessful) - response.body()!!.result.asJsonArray - else - JsonArray() - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + NameSearch( + if (response.body()!!.isSuccessful) + response.body()!!.result.asJsonArray + else + JsonArray() + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -607,15 +650,15 @@ object Odoo { } fun search( - model: String, - domain: List = listOf(), - offset: Int = 0, - limit: Int = 0, - sort: String = "", - count: Boolean = false, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + domain: List = listOf(), + offset: Int = 0, + limit: Int = 0, + sort: String = "", + count: Boolean = false, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -626,15 +669,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(Search( - if (response.body()!!.isSuccessful) - response.body()!!.result.asJsonArray.asIntList - else - listOf() - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + Search( + if (response.body()!!.isSuccessful) + response.body()!!.result.asJsonArray.asIntList + else + listOf() + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -648,11 +695,11 @@ object Odoo { } fun searchCount( - model: String, - args: List = listOf(), - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + args: List = listOf(), + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -663,15 +710,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(SearchCount( - if (response.body()!!.isSuccessful) - response.body()!!.result.asInt - else - 0 - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + SearchCount( + if (response.body()!!.isSuccessful) + response.body()!!.result.asInt + else + 0 + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -685,12 +736,12 @@ object Odoo { } fun checkAccessRights( - model: String, - operation: String, - raiseException: Boolean = false, - kwArgs: Map = mapOf(), - context: JsonObject = user.context, - callback: ResponseObserver.() -> Unit + model: String, + operation: String, + raiseException: Boolean = false, + kwArgs: Map = mapOf(), + context: JsonObject = user.context, + callback: ResponseObserver.() -> Unit ) { val callbackEx = ResponseObserver() callbackEx.callback() @@ -701,15 +752,19 @@ object Odoo { onNext { response -> callbackEx.onNext( - if (response.isSuccessful) - Response.success(CheckAccessRights( - if (response.body()!!.isSuccessful) - response.body()!!.result.asBoolean - else - false - , response.body()!!.odooError)) - else - Response.error(response.code(), response.errorBody()!!)) + if (response.isSuccessful) + Response.success( + CheckAccessRights( + if (response.body()!!.isSuccessful) + response.body()!!.result.asBoolean + else + false + , response.body()!!.odooError + ) + ) + else + Response.error(response.code(), response.errorBody()!!) + ) } onError { error -> @@ -723,33 +778,36 @@ object Odoo { } fun fieldsGet( - model: String = "", - fields: List = listOf(), - callback: ResponseObserver.() -> Unit + model: String = "", + fields: List = listOf(), + callback: ResponseObserver.() -> Unit ) = - searchRead("ir.model.fields", fields, - if (model.isNotEmpty()) listOf(listOf("model_id", "=", model)) else listOf(), - callback = callback - ) + searchRead( + "ir.model.fields", fields, + if (model.isNotEmpty()) listOf(listOf("model_id", "=", model)) else listOf(), + callback = callback + ) fun accessGet( - model: String = "", - fields: List = listOf(), - callback: ResponseObserver.() -> Unit + model: String = "", + fields: List = listOf(), + callback: ResponseObserver.() -> Unit ) = - searchRead("ir.model.access", fields, - if (model.isNotEmpty()) listOf(listOf("model_id", "=", model)) else listOf(), - callback = callback - ) + searchRead( + "ir.model.access", fields, + if (model.isNotEmpty()) listOf(listOf("model_id", "=", model)) else listOf(), + callback = callback + ) fun groupsGet( - fields: List = listOf(), - callback: ResponseObserver.() -> Unit + fields: List = listOf(), + callback: ResponseObserver.() -> Unit ) = - searchRead("res.groups", fields, - listOf(listOf("users", "in", listOf(user.id))), - callback = callback - ) + searchRead( + "res.groups", fields, + listOf(listOf("users", "in", listOf(user.id))), + callback = callback + ) } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/OdooUser.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/OdooUser.kt index 63b577b..3c58b4e 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/OdooUser.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/OdooUser.kt @@ -1,29 +1,30 @@ package io.gripxtech.odoojsonrpcclient.core import android.accounts.Account -import android.databinding.BindingAdapter import android.util.Base64 import android.widget.ImageView +import androidx.databinding.BindingAdapter import com.google.gson.JsonObject import io.gripxtech.odoojsonrpcclient.App import io.gripxtech.odoojsonrpcclient.GlideApp import io.gripxtech.odoojsonrpcclient.core.utils.Retrofit2Helper data class OdooUser( - val protocol: Retrofit2Helper.Companion.Protocol = Retrofit2Helper.Companion.Protocol.HTTP, - val host: String = "", - val login: String = "", - val password: String = "", - val database: String = "", - val serverVersion: String = "", - val isAdmin: Boolean = false, - val id: Int = 0, - val name: String = "", - val imageSmall: String = "", - val partnerId: Int = 0, - val context: JsonObject = JsonObject(), - val isActive: Boolean = false, - val account: Account = Account("false", App.KEY_ACCOUNT_TYPE) + val protocol: Retrofit2Helper.Companion.Protocol = Retrofit2Helper.Companion.Protocol.HTTP, + val host: String = "", + val login: String = "", + val password: String = "", + val database: String = "", + val serverVersion: String = "", + val isAdmin: Boolean = false, + val isSuperUser: Boolean = false, + val id: Int = 0, + val name: String = "", + val imageSmall: String = "", + val partnerId: Int = 0, + val context: JsonObject = JsonObject(), + val isActive: Boolean = false, + val account: Account = Account("false", App.KEY_ACCOUNT_TYPE) ) { val androidName: String get() = "$login[$database]" @@ -36,14 +37,16 @@ data class OdooUser( @BindingAdapter("image_small", "name") fun loadImage(view: ImageView, imageSmall: String, name: String) { GlideApp.with(view.context) - .asBitmap() - .load( - if (imageSmall.isNotEmpty()) - Base64.decode(imageSmall, Base64.DEFAULT) - else - (view.context.applicationContext as App) - .getLetterTile(if (name.isNotEmpty()) name else "X")) - .into(view) + .asBitmap() + .load( + if (imageSmall.isNotEmpty()) + Base64.decode(imageSmall, Base64.DEFAULT) + else + (view.context.applicationContext as App) + .getLetterTile(if (name.isNotEmpty()) name else "X") + ) + .circleCrop() + .into(view) } } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/AccountAuthenticator.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/AccountAuthenticator.kt index 3d40a09..6d13b9c 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/AccountAuthenticator.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/AccountAuthenticator.kt @@ -21,8 +21,9 @@ class AccountAuthenticator( requiredFeatures: Array?, options: Bundle? ): Bundle { - val intent = Intent(context, LoginActivity::class.java) - intent.putExtra(LoginActivity.FROM_ANDROID_ACCOUNTS, true) +// val intent = Intent(context, LoginActivity::class.java) +// intent.putExtra(LoginActivity.FROM_ANDROID_ACCOUNTS, true) + val intent = Intent(context, SplashActivity::class.java) val bundle = Bundle() bundle.putParcelable(AccountManager.KEY_INTENT, intent) diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/LoginActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/LoginActivity.kt index 7a20288..32c9543 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/LoginActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/LoginActivity.kt @@ -1,17 +1,20 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator import android.app.Activity +import android.content.Context import android.content.Intent -import android.databinding.DataBindingUtil +import android.content.res.Configuration import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.support.v7.app.AppCompatDelegate import android.view.View import android.widget.ArrayAdapter +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.databinding.DataBindingUtil import io.gripxtech.odoojsonrpcclient.* import io.gripxtech.odoojsonrpcclient.core.Odoo import io.gripxtech.odoojsonrpcclient.core.entities.session.authenticate.AuthenticateResult import io.gripxtech.odoojsonrpcclient.core.entities.webclient.versionInfo.VersionInfo +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.core.utils.Retrofit2Helper import io.gripxtech.odoojsonrpcclient.core.utils.android.ktx.addTextChangedListenerEx import io.gripxtech.odoojsonrpcclient.core.utils.android.ktx.postEx @@ -39,22 +42,42 @@ class LoginActivity : AppCompatActivity() { private lateinit var app: App private lateinit var binding: ActivityLoginBinding - private lateinit var compositeDisposable: CompositeDisposable + private var compositeDisposable: CompositeDisposable? = null private var selfHostedUrl: Boolean = false + private var preConfigDatabase: Boolean = false + private var preConfigDatabaseName: String = "" + + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) app = application as App binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + compositeDisposable?.dispose() compositeDisposable = CompositeDisposable() selfHostedUrl = resources.getBoolean(R.bool.self_hosted_url) + preConfigDatabase = resources.getBoolean(R.bool.pre_config_database) + preConfigDatabaseName = getString(R.string.pre_config_database_name) } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) if (selfHostedUrl) { - binding.grpCheckVersion.visibility = View.VISIBLE + binding.grpCheckVersion.postEx { + visibility = View.VISIBLE + } binding.spProtocol.setOnItemSelectedListenerEx { onItemSelected { _, _, _, _ -> resetLoginLayout(resetCheckVersion = true) @@ -64,28 +87,52 @@ class LoginActivity : AppCompatActivity() { binding.tlHost.isErrorEnabled = false binding.etHost.addTextChangedListenerEx { - afterTextChanged { _ -> + afterTextChanged { binding.tlHost.isErrorEnabled = false resetLoginLayout(resetCheckVersion = true) } } binding.bnCheckVersion.setOnClickListener { - if (binding.etHost.text.toString().isBlank()) { + val host = binding.etHost.text.toString().trim() + if (host.isBlank()) { binding.tlHost.error = getString(R.string.login_host_error) return@setOnClickListener } + if (host.startsWith("http")) { + binding.tlHost.error = getString(R.string.login_host_error1) + return@setOnClickListener + } + hideSoftKeyboard() binding.spProtocol.isEnabled = false binding.tlHost.isEnabled = false binding.bnCheckVersion.isEnabled = false resetLoginLayout(resetCheckVersion = false) prepareUiForCheckVersion() + Odoo.protocol = when (binding.spProtocol.selectedItemPosition) { + 0 -> { + Retrofit2Helper.Companion.Protocol.HTTP + } + else -> { + Retrofit2Helper.Companion.Protocol.HTTPS + } + } + Odoo.host = binding.etHost.text.toString() checkVersion() } } else { prepareUiForCheckVersion() + Odoo.protocol = when (resources.getInteger(R.integer.protocol)) { + 0 -> { + Retrofit2Helper.Companion.Protocol.HTTP + } + else -> { + Retrofit2Helper.Companion.Protocol.HTTPS + } + } + Odoo.host = getString(R.string.host_url) checkVersion() } @@ -103,7 +150,7 @@ class LoginActivity : AppCompatActivity() { } val database = binding.spDatabase.selectedItem - if (database != null && database.toString().isBlank()) { + if (database == null || database.toString().isBlank()) { showMessage(message = getString(R.string.login_database_error)) return@setOnClickListener } @@ -115,7 +162,9 @@ class LoginActivity : AppCompatActivity() { val users = getOdooUsers() if (users.isNotEmpty()) { - binding.bnOtherAccount.visibility = View.VISIBLE + binding.bnOtherAccount.postEx { + visibility = View.VISIBLE + } binding.bnOtherAccount.setOnClickListener { startActivity(Intent(this@LoginActivity, ManageAccountActivity::class.java)) } @@ -123,25 +172,24 @@ class LoginActivity : AppCompatActivity() { } private fun prepareUiForCheckVersion() { - binding.llCheckingVersion.visibility = View.VISIBLE - binding.llCheckVersionResult.visibility = View.GONE - binding.ivCheckVersionResultSuccess.visibility = View.GONE - binding.ivCheckVersionResultFail.visibility = View.GONE - Odoo.protocol = when (resources.getInteger(R.integer.protocol)) { - 0 -> { - Retrofit2Helper.Companion.Protocol.HTTP - } - else -> { - Retrofit2Helper.Companion.Protocol.HTTPS - } + binding.llCheckingVersion.postEx { + visibility = View.VISIBLE + } + binding.llCheckVersionResult.postEx { + visibility = View.GONE + } + binding.ivCheckVersionResultSuccess.postEx { + visibility = View.GONE + } + binding.ivCheckVersionResultFail.postEx { + visibility = View.GONE } - Odoo.host = getString(R.string.host_url) } private fun checkVersion() { Odoo.versionInfo { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> @@ -151,22 +199,29 @@ class LoginActivity : AppCompatActivity() { if (versionInfo.result.serverVersionIsSupported) { getDbList(versionInfo) } else { - toggleCheckVersionWidgets(isSuccess = false, resultMessage = getString( + toggleCheckVersionWidgets( + isSuccess = false, resultMessage = getString( R.string.login_server_error, versionInfo.result.serverVersion - )) + ) + ) } } else { toggleCheckVersionWidgets(isSuccess = false, resultMessage = versionInfo.errorMessage) } } else { - toggleCheckVersionWidgets(isSuccess = false, resultMessage = "${response.code()}: ${response.message()}") + toggleCheckVersionWidgets( + isSuccess = false, + resultMessage = "${response.code()}: ${response.message()}" + ) } } onError { error -> - toggleCheckVersionWidgets(isSuccess = false, resultMessage = error.message - ?: getString(R.string.generic_error)) + toggleCheckVersionWidgets( + isSuccess = false, resultMessage = error.message + ?: getString(R.string.generic_error) + ) } } } @@ -174,36 +229,52 @@ class LoginActivity : AppCompatActivity() { private fun getDbList(versionInfo: VersionInfo) { Odoo.listDb(versionInfo.result.serverVersion) { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> if (response.isSuccessful) { val listDb = response.body()!! if (listDb.isSuccessful) { - toggleCheckVersionWidgets(isSuccess = true, resultMessage = getString( + toggleCheckVersionWidgets( + isSuccess = true, resultMessage = getString( R.string.login_server_success, versionInfo.result.serverVersion - )) - - binding.spDatabase.adapter = ArrayAdapter( + ) + ) + if (preConfigDatabase && listDb.result.contains(preConfigDatabaseName)) { + binding.spDatabase.adapter = ArrayAdapter( + this@LoginActivity, + R.layout.support_simple_spinner_dropdown_item, + listOf(preConfigDatabaseName) + ) + changeGroupLoginVisibility(View.VISIBLE) + changeDbSpinnerVisibility(View.GONE) + } else { + binding.spDatabase.adapter = ArrayAdapter( this@LoginActivity, R.layout.support_simple_spinner_dropdown_item, listDb.result - ) - binding.groupLogin.visibility = View.VISIBLE - changeDbSpinnerVisibility(if (listDb.result.size == 1) View.GONE else View.VISIBLE) + ) + changeGroupLoginVisibility(View.VISIBLE) + changeDbSpinnerVisibility(if (listDb.result.size == 1) View.GONE else View.VISIBLE) + } } else { toggleCheckVersionWidgets(isSuccess = false, resultMessage = listDb.errorMessage) } } else { - toggleCheckVersionWidgets(isSuccess = false, resultMessage = "${response.code()}: ${response.message()}") + toggleCheckVersionWidgets( + isSuccess = false, + resultMessage = "${response.code()}: ${response.message()}" + ) } } onError { error -> - toggleCheckVersionWidgets(isSuccess = false, resultMessage = error.message - ?: getString(R.string.generic_error)) + toggleCheckVersionWidgets( + isSuccess = false, resultMessage = error.message + ?: getString(R.string.generic_error) + ) } } } @@ -212,25 +283,45 @@ class LoginActivity : AppCompatActivity() { binding.spProtocol.isEnabled = true binding.tlHost.isEnabled = true binding.bnCheckVersion.isEnabled = true - binding.llCheckingVersion.visibility = View.GONE - binding.llCheckVersionResult.visibility = View.VISIBLE + binding.llCheckingVersion.postEx { + visibility = View.GONE + } + binding.llCheckVersionResult.postEx { + visibility = View.VISIBLE + } if (isSuccess) { - binding.ivCheckVersionResultSuccess.visibility = View.VISIBLE - binding.ivCheckVersionResultFail.visibility = View.GONE + binding.ivCheckVersionResultSuccess.postEx { + visibility = View.VISIBLE + } + binding.ivCheckVersionResultFail.postEx { + visibility = View.GONE + } } else { - binding.ivCheckVersionResultSuccess.visibility = View.GONE - binding.ivCheckVersionResultFail.visibility = View.VISIBLE + binding.ivCheckVersionResultSuccess.postEx { + visibility = View.GONE + } + binding.ivCheckVersionResultFail.postEx { + visibility = View.VISIBLE + } } binding.tvCheckVersionResultMessage.text = resultMessage } private fun resetLoginLayout(resetCheckVersion: Boolean = false) { if (resetCheckVersion) { - binding.llCheckingVersion.visibility = View.GONE - binding.llCheckVersionResult.visibility = View.GONE - binding.ivCheckVersionResultSuccess.visibility = View.GONE - binding.ivCheckVersionResultFail.visibility = View.GONE + binding.llCheckingVersion.postEx { + visibility = View.GONE + } + binding.llCheckVersionResult.postEx { + visibility = View.GONE + } + binding.ivCheckVersionResultSuccess.postEx { + visibility = View.GONE + } + binding.ivCheckVersionResultFail.postEx { + visibility = View.GONE + } } // changeDbSpinnerVisibility(View.GONE) binding.spDatabase.adapter.run { @@ -240,22 +331,78 @@ class LoginActivity : AppCompatActivity() { } } } - binding.groupLogin.visibility = View.GONE - binding.llLoginProgress.visibility = View.GONE - binding.llLoginError.visibility = View.GONE + binding.spcLoginTop.postEx { + visibility = View.GONE + } + binding.tlLogin.postEx { + visibility = View.GONE + } + binding.tlPassword.postEx { + visibility = View.GONE + } + binding.lblDatabase.postEx { + visibility = View.GONE + } + binding.spcDatabaseTop.postEx { + visibility = View.GONE + } + binding.spDatabase.postEx { + visibility = View.GONE + } + binding.spcDatabaseBottom.postEx { + visibility = View.GONE + } + binding.bn.postEx { + visibility = View.GONE + } + binding.llLoginProgress.postEx { + visibility = View.GONE + } + binding.llLoginError.postEx { + visibility = View.GONE + } } /** * Set the visibility state of this view. * - * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * @param flag One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. * @attr ref android.R.styleable#View_visibility */ - private fun changeDbSpinnerVisibility(visibility: Int) { - binding.lblDatabase.visibility = visibility - binding.spcDatabaseTop.visibility = visibility - binding.spDatabase.visibility = visibility - binding.spcDatabaseBottom.visibility = visibility + private fun changeGroupLoginVisibility(flag: Int) { + binding.spcLoginTop.postEx { + visibility = flag + } + binding.tlLogin.postEx { + visibility = flag + } + binding.tlPassword.postEx { + visibility = flag + } + binding.bn.postEx { + visibility = flag + } + } + + /** + * Set the visibility state of this view. + * + * @param flag One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * @attr ref android.R.styleable#View_visibility + */ + private fun changeDbSpinnerVisibility(flag: Int) { + binding.lblDatabase.postEx { + visibility = flag + } + binding.spcDatabaseTop.postEx { + visibility = flag + } + binding.spDatabase.postEx { + visibility = flag + } + binding.spcDatabaseBottom.postEx { + visibility = flag + } } private fun prepareUiForAuthenticate() { @@ -269,14 +416,18 @@ class LoginActivity : AppCompatActivity() { binding.spDatabase.isEnabled = false binding.bn.isEnabled = false - binding.llLoginProgress.visibility = View.VISIBLE - binding.llLoginError.visibility = View.GONE + binding.llLoginProgress.postEx { + visibility = View.VISIBLE + } + binding.llLoginError.postEx { + visibility = View.GONE + } } private fun authenticate(login: String, password: String, database: String) { Odoo.authenticate(login = login, password = password, database = database) { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> @@ -287,7 +438,19 @@ class LoginActivity : AppCompatActivity() { authenticateResult.password = password searchReadUserInfo(authenticateResult = authenticateResult) } else { - toggleLoginWidgets(showErrorBody = true, errorMessage = authenticate.errorMessage) + val errorMessage = authenticate.errorMessage + toggleLoginWidgets( + showErrorBody = true, + errorMessage = if (errorMessage.contains( + "Expected singleton: res.users()", + ignoreCase = true + ) + ) { + getString(R.string.login_credential_error) + } else { + authenticate.errorMessage + } + ) } } else { toggleLoginWidgets(showErrorBody = false) @@ -296,30 +459,43 @@ class LoginActivity : AppCompatActivity() { } onError { error -> - toggleLoginWidgets(showErrorBody = true, errorMessage = error.message - ?: getString(R.string.generic_error)) + val pattern1 = "an int but was BOOLEAN" + val pattern2 = "result.uid" + val message = error.message ?: getString(R.string.generic_error) + toggleLoginWidgets( + showErrorBody = true, errorMessage = if (message.contains(pattern1) && message.contains(pattern2)) + getString(R.string.login_credential_error) + else + message + ) } } } private fun searchReadUserInfo(authenticateResult: AuthenticateResult) { Odoo.searchRead( - model = "res.users", - fields = listOf("name", "image"), - domain = listOf(listOf("id", "=", authenticateResult.uid)), - offset = 0, limit = 0, sort = "id DESC", context = authenticateResult.userContext + model = "res.users", + fields = listOf("name", "image"), + domain = listOf(listOf("id", "=", authenticateResult.uid)), + offset = 0, limit = 0, sort = "id DESC", context = authenticateResult.userContext ) { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> if (response.isSuccessful) { val searchRead = response.body()!! if (searchRead.isSuccessful) { - val row = searchRead.result.records[0].asJsonObject - authenticateResult.imageSmall = row.get("image").asString - authenticateResult.name = row.get("name").asString.trimFalse() + if (searchRead.result.records.size() > 0) { + val row = searchRead.result.records[0].asJsonObject + row?.get("image")?.asString?.let { + authenticateResult.imageSmall = it + } + row?.get("name")?.asString?.trimFalse()?.let { + authenticateResult.name = it + } + } binding.tvLoginProgress.text = getString(R.string.login_success) createAccount(authenticateResult = authenticateResult) } else { @@ -332,8 +508,10 @@ class LoginActivity : AppCompatActivity() { } onError { error -> - toggleLoginWidgets(showErrorBody = true, errorMessage = error.message - ?: getString(R.string.generic_error)) + toggleLoginWidgets( + showErrorBody = true, errorMessage = error.message + ?: getString(R.string.generic_error) + ) } } } @@ -349,10 +527,14 @@ class LoginActivity : AppCompatActivity() { binding.spDatabase.isEnabled = true binding.bn.isEnabled = true - binding.llLoginProgress.visibility = View.GONE + binding.llLoginProgress.postEx { + visibility = View.GONE + } if (showErrorBody) { - binding.llLoginError.visibility = View.VISIBLE + binding.llLoginError.postEx { + visibility = View.VISIBLE + } binding.tvLoginError.text = errorMessage } } @@ -372,33 +554,33 @@ class LoginActivity : AppCompatActivity() { false } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeEx { - onSubscribe { _ -> - // Must be complete, not dispose in between - // compositeDisposable.add(d) - } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeEx { + onSubscribe { + // Must be complete, not dispose in between + // compositeDisposable.add(d) + } - onNext { t -> - resultCallback(t) - } + onNext { t -> + resultCallback(t) + } - onError { error -> - error.printStackTrace() - if (!isFinishing && !isDestroyed) { - binding.llLoginProgress.postEx { - visibility = View.GONE - } - binding.llLoginError.postEx { - visibility = View.VISIBLE - } - binding.tvLoginError.postEx { - text = getString(R.string.login_create_account_error) - } + onError { error -> + error.printStackTrace() + if (!isFinishing && !isDestroyed) { + binding.llLoginProgress.postEx { + visibility = View.GONE + } + binding.llLoginError.postEx { + visibility = View.VISIBLE + } + binding.tvLoginError.postEx { + text = getString(R.string.login_create_account_error) } } } + } } private fun resultCallback(result: Boolean) { @@ -424,7 +606,7 @@ class LoginActivity : AppCompatActivity() { } override fun onDestroy() { - compositeDisposable.dispose() + compositeDisposable?.dispose() super.onDestroy() } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountActivity.kt index 88d6d8c..5ef3a0e 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountActivity.kt @@ -1,12 +1,16 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator -import android.databinding.DataBindingUtil -import android.support.v7.app.AppCompatActivity +import android.content.Context +import android.content.res.Configuration import android.os.Bundle -import android.support.v7.app.AppCompatDelegate -import android.support.v7.widget.LinearLayoutManager +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.App import io.gripxtech.odoojsonrpcclient.R +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.core.utils.recycler.decorators.VerticalLinearItemDecorator import io.gripxtech.odoojsonrpcclient.databinding.ActivityManageAccountBinding import io.gripxtech.odoojsonrpcclient.getOdooUsers @@ -21,13 +25,28 @@ class ManageAccountActivity : AppCompatActivity() { } lateinit var app: App private set - lateinit var compositeDisposable: CompositeDisposable private set + var compositeDisposable: CompositeDisposable? = null + private set lateinit var binding: ActivityManageAccountBinding private set lateinit var adapter: ManageAccountAdapter private set + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) app = application as App + compositeDisposable?.dispose() compositeDisposable = CompositeDisposable() binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_account) @@ -40,7 +59,7 @@ class ManageAccountActivity : AppCompatActivity() { val users = getOdooUsers() val layoutManager = LinearLayoutManager( - this, LinearLayoutManager.VERTICAL, false + this, RecyclerView.VERTICAL, false ) binding.rv.layoutManager = layoutManager binding.rv.addItemDecoration(VerticalLinearItemDecorator( @@ -52,7 +71,7 @@ class ManageAccountActivity : AppCompatActivity() { } override fun onDestroy() { - compositeDisposable.dispose() + compositeDisposable?.dispose() super.onDestroy() } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountAdapter.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountAdapter.kt index 9548530..648e918 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountAdapter.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountAdapter.kt @@ -1,10 +1,10 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator -import android.support.v4.content.ContextCompat -import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.* import io.gripxtech.odoojsonrpcclient.core.Odoo import io.gripxtech.odoojsonrpcclient.core.OdooUser @@ -139,7 +139,7 @@ class ManageAccountAdapter( Odoo.authenticate(user.login, user.password, user.database) { onSubscribe { disposable -> - activity.compositeDisposable.add(disposable) + activity.compositeDisposable?.add(disposable) } onNext { response -> @@ -184,12 +184,12 @@ class ManageAccountAdapter( .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeEx { - onSubscribe { _ -> + onSubscribe { // Must be complete, not dispose in between // compositeDisposable.add(d) } - onNext { _ -> + onNext { activity.restartApp() } @@ -211,12 +211,12 @@ class ManageAccountAdapter( .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeEx { - onSubscribe { _ -> + onSubscribe { // Must be complete, not dispose in between // compositeDisposable.add(d) } - onNext { _ -> + onNext { activity.restartApp() } @@ -238,7 +238,7 @@ class ManageAccountAdapter( .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeEx { - onSubscribe { _ -> + onSubscribe { // Must be complete, not dispose in between // compositeDisposable.add(d) } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountViewHolder.kt index f784531..1324ff7 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ManageAccountViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewManageAccountBinding class ManageAccountViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ProfileActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ProfileActivity.kt index 644d96d..ec86afe 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ProfileActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/ProfileActivity.kt @@ -1,10 +1,13 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator -import android.databinding.DataBindingUtil -import android.support.v7.app.AppCompatActivity +import android.content.Context +import android.content.res.Configuration import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil import io.gripxtech.odoojsonrpcclient.App import io.gripxtech.odoojsonrpcclient.R +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.databinding.ActivityProfileBinding import io.gripxtech.odoojsonrpcclient.getActiveOdooUser @@ -13,6 +16,19 @@ class ProfileActivity : AppCompatActivity() { private lateinit var app: App private lateinit var binding: ActivityProfileBinding + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } + override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) app = application as App diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/SplashActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/SplashActivity.kt index e5183dd..0506ed5 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/SplashActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/authenticator/SplashActivity.kt @@ -1,31 +1,58 @@ package io.gripxtech.odoojsonrpcclient.core.authenticator +import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.content.res.Configuration import android.os.Build import android.os.Bundle -import android.support.v4.app.ActivityCompat -import android.support.v7.app.AppCompatActivity import android.text.Html +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat import io.gripxtech.odoojsonrpcclient.* import io.gripxtech.odoojsonrpcclient.core.Odoo import io.gripxtech.odoojsonrpcclient.core.OdooUser import io.gripxtech.odoojsonrpcclient.core.entities.session.authenticate.AuthenticateResult +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.core.utils.android.ktx.subscribeEx import io.reactivex.Observable +import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers class SplashActivity : AppCompatActivity() { private lateinit var app: App - private lateinit var compositeDisposable: CompositeDisposable + private var compositeDisposable: CompositeDisposable? = null + + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + if (!isTaskRoot + && intent.hasCategory(Intent.CATEGORY_LAUNCHER) + && intent.action != null + && intent.action == Intent.ACTION_MAIN + ) { + finish() + return + } + app = application as App + compositeDisposable?.dispose() compositeDisposable = CompositeDisposable() } @@ -40,29 +67,65 @@ class SplashActivity : AppCompatActivity() { Odoo.user = user Odoo.check { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> - if (response.isSuccessful && response.body()!!.isSuccessful) { - startMainActivity() + if (response.isSuccessful) { + val check = response.body()!! + if (check.isSuccessful) { + app.cookiePrefs.setCookies(Odoo.pendingAuthenticateCookies) + startMainActivity() + } else { + val odooError = check.odooError + showMessage( + title = getString(R.string.server_request_error, response.code(), response.message()), + message = check.errorMessage, + positiveButton = getString(R.string.login_again), + positiveButtonListener = DialogInterface.OnClickListener { _, _ -> + authenticate(user) + }, + showNegativeButton = true, + negativeButton = getString(R.string.report_feedback), + negativeButtonListener = DialogInterface.OnClickListener { _, _ -> + val intent = emailIntent( + address = arrayOf(getString(R.string.preference_contact_summary)), + cc = arrayOf(), + subject = "${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) " + + getString(R.string.report_feedback), + body = "Name: ${odooError.data.name}\n\n" + + "Message: ${odooError.data.message}\n\n" + + "Exception Type: ${odooError.data.exceptionType}\n\n" + + "Arguments: ${odooError.data.arguments}\n\n" + + "Debug: ${odooError.data.debug}\n\n" + ) + try { + startActivity(intent) + } catch (e: Exception) { + e.printStackTrace() + showMessage(message = getString(R.string.preference_error_email_intent)) + } + }, + showNeutralButton = true, + neutralButton = getString(R.string.preference_logout_title), + neutralButtonListener = DialogInterface.OnClickListener { _, _ -> + logoutApp() + } + ) + } } else { val errorBody = response.errorBody()?.string() ?: getString(R.string.generic_error) @Suppress("DEPRECATION") - val message: CharSequence = if (errorBody.contains("jsonrpc", ignoreCase = true)) { - response.body()?.errorMessage ?: getString(R.string.generic_error) - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(errorBody, Html.FROM_HTML_MODE_COMPACT) - else - Html.fromHtml(errorBody) - } + val message: CharSequence = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + Html.fromHtml(errorBody, Html.FROM_HTML_MODE_COMPACT) + else + Html.fromHtml(errorBody) showMessage( title = getString(R.string.server_request_error, response.code(), response.message()), message = message, - positiveButton = getString(R.string.try_again), + positiveButton = getString(R.string.login_again), positiveButtonListener = DialogInterface.OnClickListener { _, _ -> authenticate(user) }, @@ -70,7 +133,12 @@ class SplashActivity : AppCompatActivity() { negativeButton = getString(R.string.quit), negativeButtonListener = DialogInterface.OnClickListener { _, _ -> ActivityCompat.finishAffinity(this@SplashActivity) - } + }, + showNeutralButton = true, + neutralButton = getString(R.string.preference_logout_title), + neutralButtonListener = DialogInterface.OnClickListener { _, _ -> + logoutApp() + } ) } } @@ -88,10 +156,26 @@ class SplashActivity : AppCompatActivity() { } } + private fun logoutApp() { + Single.fromCallable { + for (odooUser in getOdooUsers()) { + deleteOdooUser(odooUser) + } + }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeEx { + onSuccess { + restartApp() + } + + onError { error -> + error.printStackTrace() + } + } + } + private fun authenticate(user: OdooUser) { Odoo.authenticate(login = user.login, password = user.password, database = user.database) { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> @@ -101,8 +185,7 @@ class SplashActivity : AppCompatActivity() { createAccount(authenticateResult = authenticate.result, user = user) } else { // logoutOdooUser(user) - deleteOdooUser(user) - restartApp() + logoutApp() } } else { showServerErrorMessage(response, positiveButtonListener = DialogInterface.OnClickListener { _, _ -> @@ -140,7 +223,7 @@ class SplashActivity : AppCompatActivity() { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeEx { - onSubscribe { _: Disposable -> + onSubscribe { // Must be complete, not dispose in between // compositeDisposable.add(d) } @@ -171,7 +254,7 @@ class SplashActivity : AppCompatActivity() { } override fun onDestroy() { - compositeDisposable.dispose() + compositeDisposable?.dispose() super.onDestroy() } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/Many2One.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/Many2One.kt index c8e52aa..3e815df 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/Many2One.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/Many2One.kt @@ -1,11 +1,14 @@ package io.gripxtech.odoojsonrpcclient.core.entities +import android.os.Parcel +import android.os.Parcelable import com.google.gson.JsonElement import io.gripxtech.odoojsonrpcclient.toJsonElement +import io.gripxtech.odoojsonrpcclient.toStringList data class Many2One( private val jsonElement: JsonElement -) { +) : Parcelable { val isManyToOne: Boolean get() = jsonElement.isJsonArray && jsonElement.asJsonArray.size() == 2 @@ -20,4 +23,28 @@ data class Many2One( set(value) { if (isManyToOne) jsonElement.asJsonArray.set(1, value.toJsonElement()) } + + constructor(parcel: Parcel) : this( + arrayListOf().apply { + parcel.readStringList(this) + }.toJsonElement() + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeStringList(toStringList()) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Many2One { + return Many2One(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/route/RouteReqBody.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/route/RouteReqBody.kt index 63bac68..704a17e 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/route/RouteReqBody.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/route/RouteReqBody.kt @@ -19,5 +19,5 @@ data class RouteReqBody( @field:Expose @field:SerializedName("params") - val params: Map = mapOf() + val params: Any = mapOf() ) \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/session/authenticate/AuthenticateResult.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/session/authenticate/AuthenticateResult.kt index fc8513a..290bdb5 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/session/authenticate/AuthenticateResult.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/entities/session/authenticate/AuthenticateResult.kt @@ -90,6 +90,7 @@ data class AuthenticateResult( putString("database", db) putString("serverVersion", serverVersion) putString("isAdmin", admin.toString()) + putString("isSuperuser", superuser.toString()) putString("id", uid.toString()) putString("partnerId", partnerId.toString()) putString("name", name) diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsActivity.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsActivity.kt index 20d2401..b76acc3 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsActivity.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsActivity.kt @@ -1,11 +1,14 @@ package io.gripxtech.odoojsonrpcclient.core.preferences -import android.databinding.DataBindingUtil +import android.content.Context +import android.content.res.Configuration import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.support.v7.app.AppCompatDelegate +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.databinding.DataBindingUtil import io.gripxtech.odoojsonrpcclient.App import io.gripxtech.odoojsonrpcclient.R +import io.gripxtech.odoojsonrpcclient.core.utils.LocaleHelper import io.gripxtech.odoojsonrpcclient.databinding.ActivitySettingsBinding class SettingsActivity : AppCompatActivity() { @@ -19,6 +22,19 @@ class SettingsActivity : AppCompatActivity() { private lateinit var app: App lateinit var binding: ActivitySettingsBinding private set + override fun attachBaseContext(newBase: Context?) { + if (newBase != null) { + super.attachBaseContext(LocaleHelper.setLocale(newBase)) + } else { + super.attachBaseContext(newBase) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + LocaleHelper.setLocale(this) + } + override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) app = application as App diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsFragment.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsFragment.kt index 14dc43a..2861470 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsFragment.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/preferences/SettingsFragment.kt @@ -4,12 +4,18 @@ import android.content.Intent import android.net.MailTo import android.net.Uri import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v7.preference.Preference -import android.support.v7.preference.PreferenceFragmentCompat -import io.gripxtech.odoojsonrpcclient.BuildConfig -import io.gripxtech.odoojsonrpcclient.R -import io.gripxtech.odoojsonrpcclient.getActiveOdooUser +import androidx.core.app.TaskStackBuilder +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.google.android.material.snackbar.Snackbar +import io.gripxtech.odoojsonrpcclient.* +import io.gripxtech.odoojsonrpcclient.core.authenticator.SplashActivity +import io.gripxtech.odoojsonrpcclient.core.utils.LocalePrefs +import io.gripxtech.odoojsonrpcclient.core.utils.android.ktx.subscribeEx +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers class SettingsFragment : PreferenceFragmentCompat() { @@ -20,38 +26,67 @@ class SettingsFragment : PreferenceFragmentCompat() { private lateinit var activity: SettingsActivity private lateinit var build: Preference + private lateinit var language: ListPreference private lateinit var organization: Preference private lateinit var privacy: Preference private lateinit var contact: Preference + private lateinit var logout: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { activity = getActivity() as SettingsActivity addPreferencesFromResource(R.xml.preferences) build = findPreference(getString(R.string.preference_build_key)) + language = findPreference(getString(R.string.preference_language_key)) as ListPreference organization = findPreference(getString(R.string.preference_organization_key)) privacy = findPreference(getString(R.string.preference_privacy_policy_key)) contact = findPreference(getString(R.string.preference_contact_key)) + logout = findPreference(getString(R.string.preference_logout_key)) build.summary = getString(R.string.preference_build_summary, BuildConfig.VERSION_NAME) - organization.setOnPreferenceClickListener { _: Preference -> - startActivity(Intent( + language.setOnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + val localePrefs = LocalePrefs(activity) + when (newValue) { + getString(R.string.language_code_spanish) -> { + localePrefs.language = getString(R.string.language_code_spanish) + } + getString(R.string.language_code_english) -> { + localePrefs.language = getString(R.string.language_code_english) + } + else -> { + localePrefs.clear() + } + } + TaskStackBuilder.create(activity) + .addNextIntent(Intent(activity, SplashActivity::class.java)) + .startActivities() + } + true + } + + organization.setOnPreferenceClickListener { + startActivity( + Intent( Intent.ACTION_VIEW, Uri.parse(getString(R.string.preference_organization_website)) - )) + ) + ) true } - privacy.setOnPreferenceClickListener { _: Preference -> - startActivity(Intent( + privacy.setOnPreferenceClickListener { + startActivity( + Intent( Intent.ACTION_VIEW, Uri.parse(getString(R.string.preference_privacy_policy_url)) - )) + ) + ) true } - contact.setOnPreferenceClickListener { _: Preference -> + contact.setOnPreferenceClickListener { val lclContext = context val url = ("mailto:" + getString(R.string.preference_contact_summary) + "?subject=Contact by " + getString(R.string.app_name) + " user " + @@ -67,14 +102,33 @@ class SettingsFragment : PreferenceFragmentCompat() { } catch (e: Exception) { e.printStackTrace() Snackbar.make( - activity.binding.root, - R.string.preference_error_email_intent, - Snackbar.LENGTH_LONG + activity.binding.root, + R.string.preference_error_email_intent, + Snackbar.LENGTH_LONG ).show() } true } + + logout.setOnPreferenceClickListener { + Single.fromCallable { + for (odooUser in activity.getOdooUsers()) { + activity.deleteOdooUser(odooUser) + } + }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribeEx { + onSuccess { + TaskStackBuilder.create(activity) + .addNextIntent(Intent(activity, SplashActivity::class.java)) + .startActivities() + } + + onError { error -> + error.printStackTrace() + } + } + true + } } private fun emailIntent(address: Array, cc: Array, subject: String, body: String): Intent { diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/ClonedCookie.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/ClonedCookie.kt index 588b57b..ccf4d4f 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/ClonedCookie.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/ClonedCookie.kt @@ -1,12 +1,12 @@ package io.gripxtech.odoojsonrpcclient.core.utils -import android.annotation.SuppressLint +import android.os.Parcelable import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName -import io.mironov.smuggler.AutoParcelable +import kotlinx.android.parcel.Parcelize import okhttp3.Cookie -@SuppressLint("ParcelCreator") +@Parcelize data class ClonedCookie( @field:Expose @@ -45,7 +45,7 @@ data class ClonedCookie( @field:SerializedName("hostOnly") private val hostOnly: Boolean -) : AutoParcelable { +) : Parcelable { companion object { fun fromCookie(cookie: Cookie) = ClonedCookie( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/CookiePrefs.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/CookiePrefs.kt index a0a641c..8529339 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/CookiePrefs.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/CookiePrefs.kt @@ -3,8 +3,11 @@ package io.gripxtech.odoojsonrpcclient.core.utils import android.content.Context import com.google.gson.reflect.TypeToken import io.gripxtech.odoojsonrpcclient.getActiveOdooUser +import io.gripxtech.odoojsonrpcclient.getCookies import io.gripxtech.odoojsonrpcclient.gson +import io.gripxtech.odoojsonrpcclient.setCookies import okhttp3.Cookie +import timber.log.Timber class CookiePrefs(context: Context) : Prefs(CookiePrefs.TAG, context) { @@ -17,13 +20,15 @@ class CookiePrefs(context: Context) : Prefs(CookiePrefs.TAG, context) { fun getCookies(): ArrayList { val activeUser = context.getActiveOdooUser() if (activeUser != null) { - val cookiesStr = getString(activeUser.androidName) + // val cookiesStr = getString(activeUser.androidName) + val cookiesStr = context.getCookies(activeUser) if (cookiesStr.isNotEmpty()) { val clonedCookies: ArrayList = gson.fromJson(cookiesStr, type) val cookies = arrayListOf() for (clonedCookie in clonedCookies) { cookies += clonedCookie.toCookie() } + Timber.i("getCookies() returned $cookies") return cookies } } @@ -31,6 +36,7 @@ class CookiePrefs(context: Context) : Prefs(CookiePrefs.TAG, context) { } fun setCookies(cookies: ArrayList) { + Timber.i("setCookies() called with $cookies") val clonedCookies = arrayListOf() for (cookie in cookies) { clonedCookies += ClonedCookie.fromCookie(cookie) @@ -38,7 +44,8 @@ class CookiePrefs(context: Context) : Prefs(CookiePrefs.TAG, context) { val cookiesStr = gson.toJson(clonedCookies, type) val activeUser = context.getActiveOdooUser() if (activeUser != null) { - putString(activeUser.androidName, cookiesStr) + context.setCookies(activeUser, cookiesStr) + // putString(activeUser.androidName, cookiesStr) } } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LetterTileProvider.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LetterTileProvider.kt index 1fbdc4d..5099f7c 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LetterTileProvider.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LetterTileProvider.kt @@ -4,9 +4,9 @@ import android.content.Context import android.content.res.Resources import android.graphics.* import android.graphics.drawable.BitmapDrawable -import android.support.annotation.DrawableRes -import android.support.v4.content.ContextCompat import android.text.TextPaint +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import io.gripxtech.odoojsonrpcclient.R import java.io.ByteArrayOutputStream diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocaleHelper.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocaleHelper.kt new file mode 100644 index 0000000..7394f28 --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocaleHelper.kt @@ -0,0 +1,31 @@ +package io.gripxtech.odoojsonrpcclient.core.utils + +import android.content.Context +import android.content.res.Configuration +import java.util.* + +object LocaleHelper { + + fun setLocale(context: Context): Context = setNewLocale(context, getLanguage(context)) + + private fun setNewLocale(context: Context, language: String): Context { + persistLanguage(context, language) + return updateResources(context, language) + } + + private fun getLanguage(context: Context): String = LocalePrefs(context).language + + private fun persistLanguage(context: Context, language: String) { + LocalePrefs(context).language = language + } + + private fun updateResources(context: Context, language: String): Context { + val locale = Locale(language) + Locale.setDefault(locale) + + val res = context.resources + val config = Configuration(res.configuration) + config.setLocale(locale) + return context.createConfigurationContext(config) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocalePrefs.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocalePrefs.kt new file mode 100644 index 0000000..eb3954a --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/LocalePrefs.kt @@ -0,0 +1,17 @@ +package io.gripxtech.odoojsonrpcclient.core.utils + +import android.content.Context +import androidx.core.os.ConfigurationCompat + +class LocalePrefs(context: Context) : Prefs(LocalePrefs.TAG, context) { + + companion object { + const val TAG = "LocalePrefs" + + private const val Language = "Language" + } + + var language: String + get() = getString(Language, ConfigurationCompat.getLocales(context.resources.configuration)[0].language) + set(value) = putString(Language, value) +} diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/NavHeaderViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/NavHeaderViewHolder.kt index 31e8003..e693b94 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/NavHeaderViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/NavHeaderViewHolder.kt @@ -1,39 +1,44 @@ package io.gripxtech.odoojsonrpcclient.core.utils -import android.support.constraint.ConstraintLayout import android.util.Base64 import android.view.View import android.widget.ImageView import android.widget.TextView -import de.hdodenhof.circleimageview.CircleImageView +import androidx.constraintlayout.widget.ConstraintLayout import io.gripxtech.odoojsonrpcclient.App -import io.gripxtech.odoojsonrpcclient.GlideApp +import io.gripxtech.odoojsonrpcclient.GlideRequests import io.gripxtech.odoojsonrpcclient.R import io.gripxtech.odoojsonrpcclient.core.OdooUser import io.gripxtech.odoojsonrpcclient.trimFalse -class NavHeaderViewHolder(view: View) { - val pic: CircleImageView = view.findViewById(R.id.userImage) - val name: TextView = view.findViewById(R.id.header_name) - val email: TextView = view.findViewById(R.id.header_details) - val menuToggle: ConstraintLayout = view.findViewById(R.id.menuToggle) - val menuToggleImage: ImageView = view.findViewById(R.id.ivDropdown) +class NavHeaderViewHolder( + view: View +) { + private val pic: ImageView = view.findViewById(R.id.userImage) + private val name: TextView = view.findViewById(R.id.header_name) + private val email: TextView = view.findViewById(R.id.header_details) + private val menuToggle: ConstraintLayout = view.findViewById(R.id.menuToggle) + private val menuToggleImage: ImageView = view.findViewById(R.id.ivDropdown) - fun setUser(user: OdooUser) { + fun setUser(user: OdooUser, glideRequests: GlideRequests) { name.text = user.name email.text = user.login if (user.imageSmall.trimFalse().isNotEmpty()) { val byteArray = Base64.decode(user.imageSmall, Base64.DEFAULT) - GlideApp.with(pic.context) - .asBitmap() - .load(byteArray) - .into(pic) + glideRequests + .asBitmap() + .load(byteArray) + .dontAnimate() + .circleCrop() + .into(pic) } else { - GlideApp.with(pic.context) - .asBitmap() - .load((pic.context.applicationContext as App).getLetterTile(user.name)) - .into(pic) + glideRequests + .asBitmap() + .load((pic.context.applicationContext as App).getLetterTile(user.name)) + .dontAnimate() + .circleCrop() + .into(pic) } } } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/OdooFileProvider.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/OdooFileProvider.kt new file mode 100644 index 0000000..6ee5728 --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/OdooFileProvider.kt @@ -0,0 +1,5 @@ +package io.gripxtech.odoojsonrpcclient.core.utils + +import androidx.core.content.FileProvider + +class OdooFileProvider : FileProvider() \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Prefs.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Prefs.kt index 5ae2f83..0276fce 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Prefs.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Prefs.kt @@ -23,11 +23,13 @@ abstract class Prefs(val name: String, val context: Context) { protected fun putLong(key: String, value: Long) = preferences.edit().putLong(key, value).apply() - protected fun getString(key: String, defValue: String = ""): String = preferences.getString(key, defValue) + protected fun getString(key: String, defValue: String = ""): String = + preferences.getString(key, defValue) ?: defValue protected fun putString(key: String, value: String) = preferences.edit().putString(key, value).apply() - protected fun getStringSet(key: String, defValue: MutableSet = mutableSetOf()): MutableSet = preferences.getStringSet(key, defValue) + protected fun getStringSet(key: String, defValue: MutableSet = mutableSetOf()): MutableSet = + preferences.getStringSet(key, defValue) ?: defValue protected fun putStringSet(key: String, value: MutableSet) = preferences.edit().putStringSet(key, value).apply() diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Retrofit2Helper.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Retrofit2Helper.kt index e419e82..ff815ad 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Retrofit2Helper.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/Retrofit2Helper.kt @@ -12,8 +12,8 @@ import timber.log.Timber class Retrofit2Helper( - _protocol: Retrofit2Helper.Companion.Protocol, - _host: String + _protocol: Retrofit2Helper.Companion.Protocol, + _host: String ) { companion object { const val TAG = "Retrofit2Helper" @@ -43,56 +43,58 @@ class Retrofit2Helper( get() { if (_retrofit == null) { _retrofit = Retrofit.Builder() - .baseUrl(when (protocol) { + .baseUrl( + when (protocol) { Retrofit2Helper.Companion.Protocol.HTTP -> { "http://" } Retrofit2Helper.Companion.Protocol.HTTPS -> { "https://" } - } + host) - .client(client) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .addConverterFactory(GsonConverterFactory.create(gson)) - .build() + } + host + ) + .client(client) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build() } return _retrofit!! } - val client: OkHttpClient + private val client: OkHttpClient get() = OkHttpClient() - .newBuilder() - .cookieJar(object : CookieJar { + .newBuilder() + .cookieJar(object : CookieJar { - private var cookies: MutableList? = Retrofit2Helper.app.cookiePrefs.getCookies() + private var cookies: MutableList? = Retrofit2Helper.app.cookiePrefs.getCookies() - override fun saveFromResponse(url: HttpUrl?, cookies: MutableList?) { - if (url.toString().contains("/web/session/authenticate")) { - this.cookies = cookies - if (cookies != null) { - Odoo.pendingAuthenticateCookies.clear() - Odoo.pendingAuthenticateCookies.addAll(cookies) - } + override fun saveFromResponse(url: HttpUrl?, cookies: MutableList?) { + if (url.toString().contains("/web/session/authenticate") || url.toString().contains("web/session/check")) { + this.cookies = cookies + if (cookies != null) { + Odoo.pendingAuthenticateCookies.clear() + Odoo.pendingAuthenticateCookies.addAll(cookies) } } + } - override fun loadForRequest(url: HttpUrl?): MutableList? = - cookies - }) - .addInterceptor { chain: Interceptor.Chain? -> - val original = chain!!.request() + override fun loadForRequest(url: HttpUrl?): MutableList? = + cookies + }) + .addInterceptor { chain: Interceptor.Chain? -> + val original = chain!!.request() - val request = original.newBuilder() - .header("User-Agent", android.os.Build.MODEL) - .method(original.method(), original.body()) - .build() + val request = original.newBuilder() + .header("User-Agent", android.os.Build.MODEL) + .method(original.method(), original.body()) + .build() - chain.proceed(request) - } - .addInterceptor(HttpLoggingInterceptor { - Timber.tag("OkHttp").d(it) - }.apply { - level = HttpLoggingInterceptor.Level.BODY - }) - .build() + chain.proceed(request) + } + .addInterceptor(HttpLoggingInterceptor { + Timber.tag("OkHttp").d(it) + }.apply { + level = HttpLoggingInterceptor.Level.BODY + }) + .build() } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/AndroidKtx.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/AndroidKtx.kt index a26eeea..380f83f 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/AndroidKtx.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/AndroidKtx.kt @@ -3,7 +3,7 @@ package io.gripxtech.odoojsonrpcclient.core.utils.android.ktx import android.view.View import android.widget.AdapterView import android.widget.TextView -import io.reactivex.Observable +import io.reactivex.* inline fun AdapterView<*>.setOnItemSelectedListenerEx(listener: OnItemSelectedListenerEx.() -> Unit) { val listenerEx = OnItemSelectedListenerEx() @@ -26,3 +26,27 @@ inline fun Observable.subscribeEx(crossinline observer: ObserverEx.() observerEx.observer() subscribe(observerEx) } + +inline fun Flowable.subscribeEx(crossinline subscriber: FlowableSubscriberEx.() -> Unit) { + val subscriberEx = FlowableSubscriberEx() + subscriberEx.subscriber() + subscribe(subscriberEx) +} + +inline fun Single.subscribeEx(crossinline observer: SingleObserverEx.() -> Unit) { + val observerEx = SingleObserverEx() + observerEx.observer() + subscribe(observerEx) +} + +inline fun Maybe.subscribeEx(crossinline observer: MaybeObserverEx.() -> Unit) { + val observerEx = MaybeObserverEx() + observerEx.observer() + subscribe(observerEx) +} + +inline fun Completable.subscribeEx(crossinline observer: CompletableObserverEx.() -> Unit) { + val observerEx = CompletableObserverEx() + observerEx.observer() + subscribe(observerEx) +} diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/CompletableObserverEx.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/CompletableObserverEx.kt new file mode 100644 index 0000000..2e2e15c --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/CompletableObserverEx.kt @@ -0,0 +1,45 @@ +package io.gripxtech.odoojsonrpcclient.core.utils.android.ktx + +import io.reactivex.CompletableObserver +import io.reactivex.disposables.Disposable +import timber.log.Timber + +class CompletableObserverEx : CompletableObserver { + + private var subscribe: ((disposable: Disposable) -> Unit) = { + Timber.d("onSubscribe() called") + } + + fun onSubscribe(subscribe: (disposable: Disposable) -> Unit) { + this.subscribe = subscribe + } + + override fun onSubscribe(disposable: Disposable) { + this.subscribe.invoke(disposable) + } + + private var error: ((error: Throwable) -> Unit) = { + Timber.e("onError() called: ${it::class.java.simpleName}: ${it.message}") + it.printStackTrace() + } + + fun onError(error: (error: Throwable) -> Unit) { + this.error = error + } + + override fun onError(error: Throwable) { + this.error.invoke(error) + } + + private var complete: (() -> Unit) = { + Timber.d("onComplete() called") + } + + fun onComplete(complete: () -> Unit) { + this.complete = complete + } + + override fun onComplete() { + this.complete.invoke() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/FlowableSubscriberEx.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/FlowableSubscriberEx.kt new file mode 100644 index 0000000..d2eb816 --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/FlowableSubscriberEx.kt @@ -0,0 +1,57 @@ +package io.gripxtech.odoojsonrpcclient.core.utils.android.ktx + +import io.reactivex.FlowableSubscriber +import org.reactivestreams.Subscription +import timber.log.Timber + +class FlowableSubscriberEx : FlowableSubscriber { + + private var subscribe: ((subscription: Subscription) -> Unit) = { + Timber.d("onSubscribe() called") + } + + fun onSubscribe(subscribe: (subscription: Subscription) -> Unit) { + this.subscribe = subscribe + } + + override fun onSubscribe(subscription: Subscription) { + this.subscribe.invoke(subscription) + } + + private var next: ((response: T) -> Unit) = { + Timber.d("onNext() called: response is $it") + } + + fun onNext(next: (response: T) -> Unit) { + this.next = next + } + + override fun onNext(response: T) { + this.next.invoke(response) + } + + private var error: ((error: Throwable) -> Unit) = { + Timber.e("onError() called: ${it::class.java.simpleName}: ${it.message}") + it.printStackTrace() + } + + fun onError(error: (error: Throwable) -> Unit) { + this.error = error + } + + override fun onError(error: Throwable) { + this.error.invoke(error) + } + + private var complete: (() -> Unit) = { + Timber.d("onComplete() called") + } + + fun onComplete(complete: () -> Unit) { + this.complete = complete + } + + override fun onComplete() { + this.complete.invoke() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/MaybeObserverEx.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/MaybeObserverEx.kt new file mode 100644 index 0000000..7839c15 --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/MaybeObserverEx.kt @@ -0,0 +1,57 @@ +package io.gripxtech.odoojsonrpcclient.core.utils.android.ktx + +import io.reactivex.MaybeObserver +import io.reactivex.disposables.Disposable +import timber.log.Timber + +class MaybeObserverEx : MaybeObserver { + + private var subscribe: ((disposable: Disposable) -> Unit) = { + Timber.d("onSubscribe() called") + } + + fun onSubscribe(subscribe: (disposable: Disposable) -> Unit) { + this.subscribe = subscribe + } + + override fun onSubscribe(disposable: Disposable) { + this.subscribe.invoke(disposable) + } + + private var success: ((response: T) -> Unit) = { + Timber.d("onNext() called: response is $it") + } + + fun onSuccess(success: (response: T) -> Unit) { + this.success = success + } + + override fun onSuccess(response: T) { + this.success.invoke(response) + } + + private var error: ((error: Throwable) -> Unit) = { + Timber.e("onError() called: ${it::class.java.simpleName}: ${it.message}") + it.printStackTrace() + } + + fun onError(error: (error: Throwable) -> Unit) { + this.error = error + } + + override fun onError(error: Throwable) { + this.error.invoke(error) + } + + private var complete: (() -> Unit) = { + Timber.d("onComplete() called") + } + + fun onComplete(complete: () -> Unit) { + this.complete = complete + } + + override fun onComplete() { + this.complete.invoke() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/SingleObserverEx.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/SingleObserverEx.kt new file mode 100644 index 0000000..e082fcd --- /dev/null +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/android/ktx/SingleObserverEx.kt @@ -0,0 +1,45 @@ +package io.gripxtech.odoojsonrpcclient.core.utils.android.ktx + +import io.reactivex.SingleObserver +import io.reactivex.disposables.Disposable +import timber.log.Timber + +class SingleObserverEx : SingleObserver { + + private var subscribe: ((disposable: Disposable) -> Unit) = { + Timber.d("onSubscribe() called") + } + + fun onSubscribe(subscribe: (disposable: Disposable) -> Unit) { + this.subscribe = subscribe + } + + override fun onSubscribe(disposable: Disposable) { + this.subscribe.invoke(disposable) + } + + private var success: ((response: T) -> Unit) = { + Timber.d("onNext() called: response is $it") + } + + fun onSuccess(success: (response: T) -> Unit) { + this.success = success + } + + override fun onSuccess(response: T) { + this.success.invoke(response) + } + + private var error: ((error: Throwable) -> Unit) = { + Timber.e("onError() called: ${it::class.java.simpleName}: ${it.message}") + it.printStackTrace() + } + + fun onError(error: (error: Throwable) -> Unit) { + this.error = error + } + + override fun onError(error: Throwable) { + this.error.invoke(error) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/RecyclerBaseAdapter.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/RecyclerBaseAdapter.kt index 337a37e..7ef6cc5 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/RecyclerBaseAdapter.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/RecyclerBaseAdapter.kt @@ -1,13 +1,13 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler -import android.databinding.DataBindingUtil -import android.support.annotation.DrawableRes -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup import android.widget.Filter import android.widget.Filterable +import androidx.annotation.DrawableRes +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.R import io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities.* import io.gripxtech.odoojsonrpcclient.databinding.ItemViewRecyclerEmptyBinding @@ -188,9 +188,9 @@ abstract class RecyclerBaseAdapter( val layoutManager = recyclerView.layoutManager if (layoutManager != null && layoutManager is LinearLayoutManager) { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(lclRecyclerView: RecyclerView?, dx: Int, dy: Int) { + override fun onScrolled(lclRecyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) - if (lclRecyclerView != null && pvtMoreListener != null || pvtLessListener != null) { + if (pvtMoreListener != null || pvtLessListener != null) { val totalItemCount = layoutManager.itemCount val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val firstVisibleItem = layoutManager.findFirstVisibleItemPosition() @@ -199,7 +199,7 @@ abstract class RecyclerBaseAdapter( if (pvtMoreListener != null) { synchronized(this) { isMoreLoading = true - lclRecyclerView?.post { + lclRecyclerView.post { hideMore() showMore() val lclPvtMoreListener = pvtMoreListener @@ -211,7 +211,7 @@ abstract class RecyclerBaseAdapter( if (pvtLessListener != null) { synchronized(this) { isMoreLoading = true - lclRecyclerView?.post { + lclRecyclerView.post { hideLess() showLess() val lclPvtLessListener = pvtLessListener diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/GridItemDecorator.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/GridItemDecorator.kt index 3242a5c..bc33d44 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/GridItemDecorator.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/GridItemDecorator.kt @@ -1,52 +1,49 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.decorators import android.graphics.Rect -import android.support.v7.widget.RecyclerView import android.view.View +import androidx.recyclerview.widget.RecyclerView class GridItemDecorator( - private val sizeGridSpacingPx: Int, - private val gridSize: Int + private val sizeGridSpacingPx: Int, + private val gridSize: Int ) : RecyclerView.ItemDecoration() { private var needLeftSpacing = false - override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) = - if (outRect != null && view != null && parent != null) { - val frameWidth: Int = ((parent.width - sizeGridSpacingPx.toFloat() * (gridSize - 1)) / gridSize).toInt() - val padding: Int = parent.width / gridSize - frameWidth - val itemPosition: Int = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition - if (itemPosition < gridSize) { - outRect.top = 0 - } else { - outRect.top = sizeGridSpacingPx - } - if (itemPosition % gridSize == 0) { - outRect.left = 0 - outRect.right = padding - needLeftSpacing = true - } else if ((itemPosition + 1) % gridSize == 0) { - needLeftSpacing = false - outRect.right = 0 - outRect.left = padding - } else if (needLeftSpacing) { - needLeftSpacing = false - outRect.left = sizeGridSpacingPx - padding - if ((itemPosition + 2) % gridSize == 0) { - outRect.right = sizeGridSpacingPx - padding - } else { - outRect.right = sizeGridSpacingPx / 2 - } - } else if ((itemPosition + 2) % gridSize == 0) { - needLeftSpacing = false - outRect.left = sizeGridSpacingPx / 2 - outRect.right = sizeGridSpacingPx - padding - } else { - needLeftSpacing = false - outRect.left = sizeGridSpacingPx / 2 - outRect.right = sizeGridSpacingPx / 2 - } - outRect.bottom = 0 + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + val frameWidth: Int = ((parent.width - sizeGridSpacingPx.toFloat() * (gridSize - 1)) / gridSize).toInt() + val padding: Int = parent.width / gridSize - frameWidth + val itemPosition: Int = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition + if (itemPosition < gridSize) { + outRect.top = 0 + } else { + outRect.top = sizeGridSpacingPx + } + if (itemPosition % gridSize == 0) { + outRect.left = 0 + outRect.right = padding + needLeftSpacing = true + } else if ((itemPosition + 1) % gridSize == 0) { + needLeftSpacing = false + outRect.right = 0 + outRect.left = padding + } else if (needLeftSpacing) { + needLeftSpacing = false + outRect.left = sizeGridSpacingPx - padding + if ((itemPosition + 2) % gridSize == 0) { + outRect.right = sizeGridSpacingPx - padding } else { - super.getItemOffsets(outRect, view, parent, state) + outRect.right = sizeGridSpacingPx / 2 } + } else if ((itemPosition + 2) % gridSize == 0) { + needLeftSpacing = false + outRect.left = sizeGridSpacingPx / 2 + outRect.right = sizeGridSpacingPx - padding + } else { + needLeftSpacing = false + outRect.left = sizeGridSpacingPx / 2 + outRect.right = sizeGridSpacingPx / 2 + } + outRect.bottom = 0 + } } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/HorizontalLinearItemDecorator.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/HorizontalLinearItemDecorator.kt index 829cb27..5a7dfb6 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/HorizontalLinearItemDecorator.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/HorizontalLinearItemDecorator.kt @@ -1,21 +1,19 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.decorators import android.graphics.Rect -import android.support.v7.widget.RecyclerView import android.view.View +import androidx.recyclerview.widget.RecyclerView class HorizontalLinearItemDecorator( - private val horizontalSpaceHeight: Int + private val horizontalSpaceHeight: Int ) : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) = - if (outRect != null && view != null && parent != null) { - if (parent.getChildAdapterPosition(view) - != parent.adapter.itemCount - 1) { - outRect.right = horizontalSpaceHeight - } else { - } - } else { - super.getItemOffsets(outRect, view, parent, state) - } + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + val viewHolder = parent.adapter + if (viewHolder != null && parent.getChildAdapterPosition(view) != viewHolder.itemCount - 1) { + outRect.right = horizontalSpaceHeight + } else { + super.getItemOffsets(outRect, view, parent, state) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/VerticalLinearItemDecorator.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/VerticalLinearItemDecorator.kt index ca5b14a..da44a3a 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/VerticalLinearItemDecorator.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/decorators/VerticalLinearItemDecorator.kt @@ -1,21 +1,19 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.decorators import android.graphics.Rect -import android.support.v7.widget.RecyclerView import android.view.View +import androidx.recyclerview.widget.RecyclerView class VerticalLinearItemDecorator( private val verticalSpaceHeight: Int ) : RecyclerView.ItemDecoration() { - override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) = - if (outRect != null && view != null && parent != null) { - if (parent.getChildAdapterPosition(view) - != parent.adapter.itemCount - 1) { - outRect.bottom = verticalSpaceHeight - } else { - } - } else { - super.getItemOffsets(outRect, view, parent, state) - } + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + val viewHolder = parent.adapter + if (viewHolder != null && parent.getChildAdapterPosition(view) != viewHolder.itemCount - 1) { + outRect.bottom = verticalSpaceHeight + } else { + super.getItemOffsets(outRect, view, parent, state) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyItem.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyItem.kt index b05c92d..2403c73 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyItem.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyItem.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities -import android.support.annotation.DrawableRes +import androidx.annotation.DrawableRes data class EmptyItem( val message: CharSequence, diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyViewHolder.kt index 53d24a7..574f0a4 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/EmptyViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewRecyclerEmptyBinding class EmptyViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/ErrorViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/ErrorViewHolder.kt index 8d37c04..93b7924 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/ErrorViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/ErrorViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewRecyclerErrorBinding class ErrorViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/LessViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/LessViewHolder.kt index 2c27de9..0b25273 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/LessViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/LessViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewRecyclerLessBinding class LessViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/MoreViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/MoreViewHolder.kt index 1c8041b..a88d081 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/MoreViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/entities/MoreViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.entities -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewRecyclerMoreBinding class MoreViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/ItemTouchHelperAdapter.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/ItemTouchHelperAdapter.kt index 4fe575f..7271616 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/ItemTouchHelperAdapter.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/ItemTouchHelperAdapter.kt @@ -16,7 +16,7 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.helpers -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView /** * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/OnStartDragListener.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/OnStartDragListener.kt index 3363bcf..d498c4e 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/OnStartDragListener.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/OnStartDragListener.kt @@ -16,7 +16,7 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.helpers -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView /** * Listener for manual initiation of a drag. diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/SimpleItemTouchHelperCallback.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/SimpleItemTouchHelperCallback.kt index b322002..b0c0794 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/SimpleItemTouchHelperCallback.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/recycler/helpers/SimpleItemTouchHelperCallback.kt @@ -16,9 +16,9 @@ package io.gripxtech.odoojsonrpcclient.core.utils.recycler.helpers -import android.support.v7.widget.GridLayoutManager -import android.support.v7.widget.RecyclerView -import android.support.v7.widget.helper.ItemTouchHelper +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView /** * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and @@ -31,19 +31,16 @@ import android.support.v7.widget.helper.ItemTouchHelper * @author Paul Burke (ipaulpro) */ class SimpleItemTouchHelperCallback( - val adapter: ItemTouchHelperAdapter, - private val longPressDragEnabled: Boolean = false, - private val itemViewSwipeEnabled: Boolean = false + val adapter: ItemTouchHelperAdapter, + private val longPressDragEnabled: Boolean = false, + private val itemViewSwipeEnabled: Boolean = false ) : ItemTouchHelper.Callback() { companion object { const val ALPHA_FULL = 1.0f } - override fun getMovementFlags(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int { - if (recyclerView == null || viewHolder == null) { - return -1 - } + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { // Set movement flags based on the layout manager return if (recyclerView.layoutManager is GridLayoutManager) { val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT @@ -56,10 +53,11 @@ class SimpleItemTouchHelperCallback( } } - override fun onMove(recyclerView: RecyclerView?, source: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean { - if (recyclerView == null || source == null || target == null) { - return false - } + override fun onMove( + recyclerView: RecyclerView, + source: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { if (source.itemViewType != target.itemViewType) { return false } @@ -69,10 +67,7 @@ class SimpleItemTouchHelperCallback( return true } - override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) { - if (viewHolder === null) { - return - } + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { // Notify the adapter of the dismiss adapter.onItemDismiss(viewHolder.adapterPosition) } diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerAdapter.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerAdapter.kt index 35ce8fc..5391167 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerAdapter.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerAdapter.kt @@ -1,12 +1,12 @@ package io.gripxtech.odoojsonrpcclient.core.utils.tabs -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager -import android.support.v4.app.FragmentPagerAdapter +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter class PagerAdapter( - fragmentManager: FragmentManager, - val items: ArrayList + fragmentManager: FragmentManager, + val items: ArrayList ) : FragmentPagerAdapter(fragmentManager) { override fun getItem(position: Int): Fragment = items[position].fragment diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerItem.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerItem.kt index 2adef8d..ebc1766 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerItem.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/core/utils/tabs/PagerItem.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.core.utils.tabs -import android.support.v4.app.Fragment +import androidx.fragment.app.Fragment data class PagerItem( val title: String, diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerAdapter.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerAdapter.kt index c2eebc6..910e549 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerAdapter.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerAdapter.kt @@ -1,15 +1,15 @@ package io.gripxtech.odoojsonrpcclient.customer -import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.core.utils.recycler.RecyclerBaseAdapter import io.gripxtech.odoojsonrpcclient.customer.entities.Customer import io.gripxtech.odoojsonrpcclient.databinding.ItemViewCustomerBinding class CustomerAdapter( - val fragment: CustomerFragment, - items: ArrayList + private val fragment: CustomerFragment, + items: ArrayList ) : RecyclerBaseAdapter(items, fragment.binding.rv) { companion object { diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerFragment.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerFragment.kt index fdbe887..3323474 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerFragment.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerFragment.kt @@ -2,13 +2,11 @@ package io.gripxtech.odoojsonrpcclient.customer import android.content.res.Configuration import android.os.Bundle -import android.support.v4.app.Fragment -import android.support.v7.app.ActionBarDrawerToggle -import android.support.v7.widget.DividerItemDecoration -import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.recyclerview.widget.RecyclerView import com.google.gson.reflect.TypeToken import io.gripxtech.odoojsonrpcclient.* import io.gripxtech.odoojsonrpcclient.core.Odoo @@ -16,7 +14,7 @@ import io.gripxtech.odoojsonrpcclient.customer.entities.Customer import io.gripxtech.odoojsonrpcclient.databinding.FragmentCustomerBinding import io.reactivex.disposables.CompositeDisposable -class CustomerFragment : Fragment() { +class CustomerFragment : androidx.fragment.app.Fragment() { companion object { @@ -29,18 +27,18 @@ class CustomerFragment : Fragment() { private const val TYPE = "type" fun newInstance(customerType: CustomerType) = - CustomerFragment().apply { - arguments = Bundle().apply { - putString(TYPE, customerType.name) - } + CustomerFragment().apply { + arguments = Bundle().apply { + putString(TYPE, customerType.name) } + } } lateinit var activity: MainActivity private set lateinit var binding: FragmentCustomerBinding private set - lateinit var compositeDisposable: CompositeDisposable private set + private var compositeDisposable: CompositeDisposable? = null - private lateinit var customerType: CustomerType + private var customerType: CustomerType = CustomerType.Customer private lateinit var drawerToggle: ActionBarDrawerToggle val adapter: CustomerAdapter by lazy { @@ -50,8 +48,11 @@ class CustomerFragment : Fragment() { private val customerListType = object : TypeToken>() {}.type private val limit = RECORD_LIMIT - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + compositeDisposable?.dispose() compositeDisposable = CompositeDisposable() // Inflate the layout for this fragment @@ -63,7 +64,7 @@ class CustomerFragment : Fragment() { super.onActivityCreated(savedInstanceState) activity = getActivity() as MainActivity arguments?.let { - customerType = CustomerType.valueOf(it.getString(TYPE)) + customerType = CustomerType.valueOf(it.getString(TYPE) ?: "") } // Hiding MainActivity's AppBarLayout as well as NestedScrollView first @@ -91,17 +92,22 @@ class CustomerFragment : Fragment() { actionBar.setDisplayHomeAsUpEnabled(true) } - drawerToggle = ActionBarDrawerToggle(activity, activity.binding.dl, - binding.tb, R.string.navigation_drawer_open, R.string.navigation_drawer_close) + drawerToggle = ActionBarDrawerToggle( + activity, activity.binding.dl, + binding.tb, R.string.navigation_drawer_open, R.string.navigation_drawer_close + ) activity.binding.dl.addDrawerListener(drawerToggle) drawerToggle.syncState() - val layoutManager = LinearLayoutManager( - activity, LinearLayoutManager.VERTICAL, false + val layoutManager = androidx.recyclerview.widget.LinearLayoutManager( + activity, RecyclerView.VERTICAL, false ) binding.rv.layoutManager = layoutManager binding.rv.addItemDecoration( - DividerItemDecoration(activity, LinearLayoutManager.VERTICAL) + androidx.recyclerview.widget.DividerItemDecoration( + activity, + androidx.recyclerview.widget.LinearLayoutManager.VERTICAL + ) ) adapter.setupScrollListener(binding.rv) @@ -131,7 +137,7 @@ class CustomerFragment : Fragment() { binding.rv.adapter = adapter } - override fun onConfigurationChanged(newConfig: Configuration?) { + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (::drawerToggle.isInitialized) { drawerToggle.onConfigurationChanged(newConfig) @@ -139,26 +145,28 @@ class CustomerFragment : Fragment() { } override fun onDestroyView() { - compositeDisposable.dispose() + compositeDisposable?.dispose() super.onDestroyView() } private fun fetchCustomer() { - Odoo.searchRead("res.partner", Customer.fields, - when (customerType) { - CustomerType.Customer -> { - listOf(listOf("customer", "=", true)) - } - CustomerType.Supplier -> { - listOf(listOf("supplier", "=", true)) - } - CustomerType.Company -> { - listOf(listOf("is_company", "=", true)) - } - }, adapter.rowItemCount, limit, "name ASC") { + Odoo.searchRead( + "res.partner", Customer.fields, + when (customerType) { + CustomerType.Customer -> { + listOf(listOf("customer", "=", true)) + } + CustomerType.Supplier -> { + listOf(listOf("supplier", "=", true)) + } + CustomerType.Company -> { + listOf(listOf("is_company", "=", true)) + } + }, adapter.rowItemCount, limit, "name ASC" + ) { onSubscribe { disposable -> - compositeDisposable.add(disposable) + compositeDisposable?.add(disposable) } onNext { response -> @@ -182,10 +190,9 @@ class CustomerFragment : Fragment() { } } adapter.addRowItems(items) - compositeDisposable.dispose() - compositeDisposable = CompositeDisposable() } else { adapter.showError(searchRead.errorMessage) + activity.promptReport(searchRead.odooError) } } else { adapter.showError(response.errorBodySpanned) diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerViewHolder.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerViewHolder.kt index 25db4b8..20b9a99 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerViewHolder.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/CustomerViewHolder.kt @@ -1,6 +1,6 @@ package io.gripxtech.odoojsonrpcclient.customer -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import io.gripxtech.odoojsonrpcclient.databinding.ItemViewCustomerBinding class CustomerViewHolder( diff --git a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/entities/Customer.kt b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/entities/Customer.kt index 20fa2ce..2afd014 100644 --- a/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/entities/Customer.kt +++ b/app/src/main/java/io/gripxtech/odoojsonrpcclient/customer/entities/Customer.kt @@ -1,8 +1,8 @@ package io.gripxtech.odoojsonrpcclient.customer.entities -import android.databinding.BindingAdapter import android.util.Base64 import android.widget.ImageView +import androidx.databinding.BindingAdapter import com.google.gson.JsonElement import com.google.gson.annotations.Expose import com.google.gson.annotations.SerializedName @@ -11,80 +11,84 @@ import io.gripxtech.odoojsonrpcclient.GlideApp data class Customer( - @Expose - @SerializedName("id") - val id: Int, + @Expose + @SerializedName("id") + val id: Int, - @Expose - @SerializedName("name") - val name: String, + @Expose + @SerializedName("name") + val name: String, - @Expose - @SerializedName("email") - val email: String, + @Expose + @SerializedName("email") + val email: String, - @Expose - @SerializedName("company_name") - val companyName: String, + @Expose + @SerializedName("company_name") + val companyName: String, - @Expose - @SerializedName("image_small") - val imageSmall: String, + @Expose + @SerializedName("image_small") + val imageSmall: String, - @Expose - @SerializedName("website") - val website: String, + @Expose + @SerializedName("website") + val website: String, - @Expose - @SerializedName("phone") - val phone: String, + @Expose + @SerializedName("phone") + val phone: String, - @Expose - @SerializedName("mobile") - val mobile: String, + @Expose + @SerializedName("mobile") + val mobile: String, - @Expose - @SerializedName("full_address") - val fullAddress: String, + @Expose + @SerializedName("full_address") + val fullAddress: String, - @Expose - @SerializedName("state_id") - val stateId: JsonElement, + @Expose + @SerializedName("state_id") + val stateId: JsonElement, - @Expose - @SerializedName("country_id") - val countryId: JsonElement, + @Expose + @SerializedName("country_id") + val countryId: JsonElement, - @Expose - @SerializedName("comment") - val comment: String, + @Expose + @SerializedName("comment") + val comment: String, - @Expose - @SerializedName("is_company") - val isCompany: Boolean + @Expose + @SerializedName("is_company") + val isCompany: Boolean ) { companion object { @JvmStatic @BindingAdapter("image_small", "name") fun loadImage(view: ImageView, imageSmall: String, name: String) { GlideApp.with(view.context) - .asBitmap() - .load( - if (imageSmall.isNotEmpty()) - Base64.decode(imageSmall, Base64.DEFAULT) - else - (view.context.applicationContext as App) - .getLetterTile(if (name.isNotEmpty()) name else "X")) - .into(view) + .asBitmap() + .load( + if (imageSmall.isNotEmpty()) + Base64.decode(imageSmall, Base64.DEFAULT) + else + (view.context.applicationContext as App) + .getLetterTile(if (name.isNotEmpty()) name else "X") + ) + .dontAnimate() + .circleCrop() + .into(view) } @JvmField val fieldsMap: Map = mapOf( - "id" to "id", "name" to "Name", "email" to "Email", - "company_name" to "Company Name", "image_small" to "Image", "website" to "Website", - "phone" to "Phone Number", "mobile" to "Mobile Number",/* "full_address" to "Full Address",*/ - "state_id" to "State", "country_id" to "Country", "comment" to "Internal Note", - "is_company" to "Is Company") + "id" to "id", "name" to "Name", "email" to "Email", + "company_name" to "Company Name", "image_small" to "Image", "website" to "Website", + "phone" to "Phone Number", "mobile" to "Mobile Number",/* "full_address" to "Full Address",*/ + "state_id" to "State", "country_id" to "Country", "comment" to "Internal Note", + "is_company" to "Is Company" + ) @JvmField val fields: ArrayList = fieldsMap.keys.toMutableList() as ArrayList diff --git a/app/src/main/res/drawable-hdpi/baseline_feedback_black_18.png b/app/src/main/res/drawable-hdpi/baseline_feedback_black_18.png new file mode 100644 index 0000000000000000000000000000000000000000..eac2e79f42200d1e7f4a08b0075e23fe424139ba GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^(jd&i0wmS%+S~(D^`0({Ar*{gFKpy>2oPy~xZkjs zb&8uo(=on9L0&nFf`mBFi+RlsnCHS0&hcD7e&3^5yGW;I>$QrG%BeeJ)UHWIs9%$w zVVx%2ck*14!areYxkRhZPEG68Cs)S*oPE1ek3;i~PwmN7);Y(-rAylmGuhnOB=p!# hT4;Bp!lUcaj8S6Ug4>IE;($(J@O1TaS?83{1OTzrKf3?` literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/baseline_feedback_black_18.png b/app/src/main/res/drawable-mdpi/baseline_feedback_black_18.png new file mode 100644 index 0000000000000000000000000000000000000000..21b48cd99b192b2b2bc6124184e844b98caea02e GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@RhecuyC{kP61+1*|8S9+arb9=hvIgHC7nUh%r= zdm>IzaeeBd=Udfw7@uV0Xx=5cf+0kbjhlg?>B#yco{0u2K&u!$UHx3vIVCg!09KqY ACjbBd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/baseline_feedback_black_18.png b/app/src/main/res/drawable-xhdpi/baseline_feedback_black_18.png new file mode 100644 index 0000000000000000000000000000000000000000..0c97c408bc815b71bde054e296bb66997574042a GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB$~;{hLn;{GUTS1(G8AchxZL`_ zz|z+}th<_`8X63@$o_r)l=;EI=klktq<-~Z4=6Nx(X+22y!oB9gMV|}3+ZhlQx-gm z_Pp@W@jc7g=4WEOQx@2i_Vdq*ZQ^6dy67-nV*$I?nWnC*ybk8p-ED%r%2I8WKGSL$ Y%I5D=iOhdB8R!BAPgg&ebxsLQ03WbIV*mgE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/baseline_feedback_black_18.png b/app/src/main/res/drawable-xxhdpi/baseline_feedback_black_18.png new file mode 100644 index 0000000000000000000000000000000000000000..ddaa90f719c81b2937176007d046d6d319e46656 GIT binary patch literal 244 zcmV9CCHZTx<0+g?l`wH155jD`QeW+|~zI_wOou#zrlg&aJ%{?v-?F;)m= u#gy`6ehPL%IWeJ}m{3m4M`?YeTAsL8h^(&%`lC;xsZEq{ov~#&bjbbv<#$x z4YaH&wBbw&+?6f)SkRGzcVa=d9jU?^vUOEJJ=yM6KqJ{6q#85GAcG2<_Gi#@ZXkmU zGU#QHL9w913|SR7{M68V5BFY8=!!sBuu^pvFOsLp;Yh7vB#yd->)gpN9be0000 + + diff --git a/app/src/main/res/drawable/ic_dashboard_white_24dp.xml b/app/src/main/res/drawable/ic_dashboard_white_24dp.xml new file mode 100644 index 0000000..f327e1f --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_white_24dp.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..de832bb --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..867f14a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_person_add_black_24dp.xml b/app/src/main/res/drawable/ic_person_add_black_24dp.xml new file mode 100644 index 0000000..53baa5b --- /dev/null +++ b/app/src/main/res/drawable/ic_person_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index fe049ba..cf9936e 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,196 +1,196 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - - - + android:layout_height="match_parent" + android:background="#f1f1f1" + android:clipToPadding="false" + android:paddingBottom="@dimen/_64sdp" + tools:context=".core.authenticator.LoginActivity"> + + + android:id="@+id/spcBannerTop" + android:layout_width="wrap_content" + android:layout_height="@dimen/_16sdp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + android:id="@+id/ivBanner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/odoo_logo" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/spcBannerTop" + tools:ignore="ContentDescription"/> + android:id="@+id/spcBannerBottom" + android:layout_width="wrap_content" + android:layout_height="@dimen/_16sdp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/ivBanner"/> + android:id="@+id/lblProtocol" + style="@style/DisabledHintTextAppearance" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/login_protocol" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/spcBannerBottom"/> + android:id="@+id/spcProtocolTop" + android:layout_width="wrap_content" + android:layout_height="@dimen/_5sdp" + app:layout_constraintStart_toStartOf="@id/lblProtocol" + app:layout_constraintTop_toBottomOf="@id/lblProtocol"/> + android:id="@+id/spProtocol" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:entries="@array/protocols" + app:layout_constraintStart_toStartOf="@id/lblProtocol" + app:layout_constraintTop_toBottomOf="@id/spcProtocolTop"/> - - + android:id="@+id/spcHostTop" + android:layout_width="wrap_content" + android:layout_height="@dimen/_5sdp" + app:layout_constraintStart_toStartOf="@id/lblProtocol" + app:layout_constraintTop_toBottomOf="@id/spProtocol"/> - + app:layout_constraintStart_toStartOf="@id/lblProtocol" + app:layout_constraintTop_toBottomOf="@id/spcHostTop"> + + - +