From 677f073c5f57fd379a0f0e17f4a37deafefa8aca Mon Sep 17 00:00:00 2001 From: "S. Grimault" Date: Sun, 27 Jun 2021 13:12:32 +0200 Subject: [PATCH 1/4] fix(#38): check session validity from AuthManager --- .../geonature/sync/api/GeoNatureAPIClient.kt | 16 --- .../geonature/sync/auth/AuthLoginViewModel.kt | 21 ++-- .../fr/geonature/sync/auth/AuthManager.kt | 91 +++++++++++----- .../fr/geonature/sync/auth/AuthManagerTest.kt | 102 ++++++++++++++++-- 4 files changed, 171 insertions(+), 59 deletions(-) diff --git a/sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt b/sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt index 940a298c..553a4289 100644 --- a/sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt +++ b/sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt @@ -13,9 +13,6 @@ import fr.geonature.sync.api.model.User import fr.geonature.sync.auth.AuthManager import fr.geonature.sync.util.SettingsUtils.getGeoNatureServerUrl import fr.geonature.sync.util.SettingsUtils.getTaxHubServerUrl -import kotlinx.coroutines.Dispatchers.Default -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.HttpUrl @@ -70,19 +67,6 @@ class GeoNatureAPIClient private constructor( return authManager .getCookie() ?.let { - if (it.expiresAt() < System.currentTimeMillis()) { - Log.i( - TAG, - "cookie expiry date ${it.expiresAt()} reached: perform logout" - ) - - GlobalScope.launch(Default) { - authManager.logout() - } - - return@let mutableListOf() - } - mutableListOf(it) } ?: mutableListOf() diff --git a/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt b/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt index 3d6aa8cc..925a6f08 100644 --- a/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt +++ b/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt @@ -38,7 +38,8 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio private val _loginResult = MutableLiveData() val loginResult: LiveData = _loginResult - val isLoggedIn: LiveData = authManager.isLoggedIn + private val _isLoggedIn = MutableLiveData() + val isLoggedIn: LiveData = _isLoggedIn init { if (geoNatureAPIClient == null) { @@ -107,6 +108,7 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio authManager .setAuthLogin(authLogin) .also { + _isLoggedIn.value = it _loginResult.value = LoginResult(success = authLogin) } } catch (e: Exception) { @@ -137,13 +139,15 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio } fun logout(): LiveData { - val disconnected = MutableLiveData() + val disconnectedLiveData = MutableLiveData() viewModelScope.launch { - disconnected.value = authManager.logout() + val disconnected = authManager.logout() + disconnectedLiveData.value = disconnected + _isLoggedIn.value = disconnected } - return disconnected + return disconnectedLiveData } // A placeholder username validation check @@ -173,8 +177,10 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio * @author [S. Grimault](mailto:sebastien.grimault@gmail.com) */ data class LoginFormState( - @StringRes val usernameError: Int? = null, - @StringRes val passwordError: Int? = null, + @StringRes + val usernameError: Int? = null, + @StringRes + val passwordError: Int? = null, val isValid: Boolean = false ) @@ -185,7 +191,8 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio */ data class LoginResult( val success: AuthLogin? = null, - @StringRes val error: Int? = null + @StringRes + val error: Int? = null ) { fun hasError(): Boolean { diff --git a/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt b/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt index 986c64cc..ab675448 100644 --- a/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt +++ b/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt @@ -4,9 +4,6 @@ import android.content.Context import android.content.SharedPreferences import android.util.Log import androidx.core.app.NotificationManagerCompat -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.preference.PreferenceManager import fr.geonature.sync.api.model.AuthLogin import fr.geonature.sync.auth.io.AuthLoginJsonReader @@ -32,11 +29,9 @@ class AuthManager private constructor(applicationContext: Context) { private val authLoginJsonReader = AuthLoginJsonReader() private val authLoginJsonWriter = AuthLoginJsonWriter() - private val _authLogin = MutableLiveData() + private var authLogin: AuthLogin? = null private var cookie: Cookie? = null - val isLoggedIn: LiveData = Transformations.map(_authLogin) { it != null } - init { GlobalScope.launch(Default) { getAuthLogin() @@ -56,22 +51,44 @@ class AuthManager private constructor(applicationContext: Context) { } fun getCookie(): Cookie? { - return cookie - ?: runCatching { - preferenceManager - .getString( - KEY_PREFERENCE_COOKIE, - null - ) - ?.let { CookieHelper.deserialize(it) } - }.getOrNull() + val cookie = cookie + + if (cookie != null) { + if (cookie.expiresAt() < System.currentTimeMillis()) { + Log.i( + TAG, + "cookie expiry date ${cookie.expiresAt()} reached: perform logout" + ) + + GlobalScope.launch(Default) { + logout() + } + + return null + } + + return cookie + } + + return preferenceManager + .getString( + KEY_PREFERENCE_COOKIE, + null + ) + ?.let { CookieHelper.deserialize(it) } } suspend fun getAuthLogin(): AuthLogin? = withContext(Default) { - val authLogin = _authLogin.value + val authLogin = this@AuthManager.authLogin - if (authLogin != null) return@withContext authLogin + if (authLogin != null) { + if (!checkSessionValidity(authLogin)) { + return@withContext null + } + + return@withContext authLogin + } val authLoginAsJson = preferenceManager.getString( KEY_PREFERENCE_AUTH_LOGIN, @@ -79,24 +96,23 @@ class AuthManager private constructor(applicationContext: Context) { ) if (authLoginAsJson.isNullOrBlank()) { - _authLogin.postValue(null) + this@AuthManager.authLogin = null return@withContext null } authLoginJsonReader .read(authLoginAsJson) .let { - if (it?.expires?.before(Calendar.getInstance().time) == true) { - Log.i( - TAG, - "auth login expiry date ${it.expires} reached: perform logout" - ) + if (it == null) { + this@AuthManager.authLogin = null + return@let it + } - logout() + if (!checkSessionValidity(it)) { return@let null } - _authLogin.postValue(it) + this@AuthManager.authLogin = it it } } @@ -107,14 +123,14 @@ class AuthManager private constructor(applicationContext: Context) { "successfully authenticated, login expiration date: ${authLogin.expires}" ) - _authLogin.value = authLogin + this.authLogin = authLogin return withContext(Default) { val authLoginAsJson = authLoginJsonWriter.write(authLogin) if (authLoginAsJson.isNullOrBlank()) { - _authLogin.postValue(null) + this@AuthManager.authLogin = null return@withContext false } @@ -139,11 +155,30 @@ class AuthManager private constructor(applicationContext: Context) { .commit() .also { if (it) { - _authLogin.postValue(null) + authLogin = null + cookie = null } } } + private fun checkSessionValidity(authLogin: AuthLogin): Boolean { + if (authLogin.expires.before(Calendar.getInstance().time)) { + Log.i( + TAG, + "auth login expiry date ${authLogin.expires} reached: perform logout" + ) + + + GlobalScope.launch(Default) { + logout() + } + + return false + } + + return true + } + companion object { private val TAG = AuthManager::class.java.name diff --git a/sync/src/test/java/fr/geonature/sync/auth/AuthManagerTest.kt b/sync/src/test/java/fr/geonature/sync/auth/AuthManagerTest.kt index 2b8469aa..9afbd49f 100644 --- a/sync/src/test/java/fr/geonature/sync/auth/AuthManagerTest.kt +++ b/sync/src/test/java/fr/geonature/sync/auth/AuthManagerTest.kt @@ -34,10 +34,7 @@ class AuthManagerTest { fun setUp() { val application = ApplicationProvider.getApplicationContext() authManager = AuthManager.getInstance(application) - authManager.preferenceManager - .edit() - .clear() - .commit() + runBlocking { authManager.logout() } } @Test @@ -49,8 +46,79 @@ class AuthManagerTest { assertNull(noSuchCookie) } + @Test + fun testGetUndefinedAuthLogin() { + // when reading non existing AuthLogin instance + val noSuchAuthLogin = runBlocking { authManager.getAuthLogin() } + + // then + assertNull(noSuchAuthLogin) + } + + @Test + fun testLogout() { + // given a valid Cookie to save and read + val cookie = Cookie + .Builder() + .name("token") + .value("some_value") + .domain("demo.geonature.fr") + .path("/") + .expiresAt( + Date().add( + Calendar.HOUR, + 1 + ).time + ) + .build() + + // when setting new cookie + authManager.setCookie(cookie) + + // given an AuthLogin instance to save and read + val authLogin = AuthLogin( + AuthUser( + 1234L, + "Admin", + "Test", + 3, + 1, + "admin" + ), + Calendar + .getInstance() + .apply { + add( + Calendar.DAY_OF_YEAR, + 7 + ) + set( + Calendar.MILLISECOND, + 0 + ) + }.time + ) + + // when saving this AuthLogin + val saved = runBlocking { authManager.setAuthLogin(authLogin) } + + // then + assertTrue(saved) + + // when perform logout from manager + runBlocking { authManager.logout() } + + // then + val noSuchCookie = authManager.getCookie() + assertNull(noSuchCookie) + + val noSuchAuthLogin = runBlocking { authManager.getAuthLogin() } + assertNull(noSuchAuthLogin) + } + @Test fun testSaveAndGetCookie() { + // given a valid Cookie to save and read val cookie = Cookie .Builder() .name("token") @@ -79,12 +147,30 @@ class AuthManagerTest { } @Test - fun testGetUndefinedAuthLogin() { - // when reading non existing AuthLogin instance - val noSuchAuthLogin = runBlocking { authManager.getAuthLogin() } + fun testSaveAndGetExpiredCookie() { + // given an expired Cookie to save and read + val cookie = Cookie + .Builder() + .name("token") + .value("some_value") + .domain("demo.geonature.fr") + .path("/") + .expiresAt( + Date().add( + Calendar.HOUR, + -1 + ).time + ) + .build() + + // when setting new cookie + authManager.setCookie(cookie) + + // when reading this cookie from manager + val cookieFromManager = authManager.getCookie() // then - assertNull(noSuchAuthLogin) + assertNull(cookieFromManager) } @Test From cc8ed37ae99deecd8e04e7da93ff114b69117c2d Mon Sep 17 00:00:00 2001 From: Sebastien Grimault Date: Wed, 28 Jul 2021 15:07:05 +0200 Subject: [PATCH 2/4] feat(#40): android 11 support * feat: load app settings from Content Provider URI * feat: dark theme * feat: inputs synchronization through ContentProvider * fix: load settings from Content Provider may throw exception... * feat: introducing IInputManager, reduce dependency with InputViewModel * chore: IDE global settings * fix: expected "packageName" attribute from ContentValues to resolve input file path. * fix: notify if current user is still logged in through LiveData... * feat: Api 30 full support * refactor: introducing Service Locator IPackageInfoManager refactoring, remove obsolete code * feat: introducing Either type * feat: helper about the current network connection state * chore: upgrade libraries dependencies * chore: IDE global settings * chore: upgrade kotlin and gradle * refactor: CookieManager, AuthManager, GeoNatureAPIClient through ServiceLocator * feat: adding 'pag' flavor (Parc amazonien de Guyane) * fix: add required query params attribute "fields" (needed for GeoNature 2.7.x) --- .idea/codeStyles/Project.xml | 2 - build.gradle | 4 +- commons/build.gradle | 4 +- commons/src/main/AndroidManifest.xml | 19 +- .../geonature/commons/data/helper/Provider.kt | 17 +- .../java/fr/geonature/commons/fp/Either.kt | 120 +++++ .../java/fr/geonature/commons/fp/Failure.kt | 12 + .../geonature/commons/input/AbstractInput.kt | 40 +- .../geonature/commons/input/IInputManager.kt | 84 ++++ .../geonature/commons/input/InputManager.kt | 246 ---------- .../commons/input/InputManagerImpl.kt | 200 +++++++++ .../geonature/commons/input/InputViewModel.kt | 48 +- .../commons/settings/AppSettingsManager.kt | 152 ++++--- .../geonature/commons/util/NetworkHandler.kt | 33 ++ commons/src/main/res/values/strings.xml | 1 + commons/src/main/res/values/styles.xml | 8 - .../commons/data/DummyContentProvider.kt | 55 +++ .../fr/geonature/commons/fp/EitherTest.kt | 196 ++++++++ .../commons/input/InputManagerTest.kt | 76 ++-- .../fr/geonature/commons/input/InputTest.kt | 63 ++- .../commons/input/InputViewModelTest.kt | 44 +- commons/version.properties | 4 +- sync/README.md | 40 +- sync/build.gradle | 10 +- sync/src/main/AndroidManifest.xml | 8 +- .../java/fr/geonature/sync/MainApplication.kt | 46 +- .../geonature/sync/api/GeoNatureAPIClient.kt | 228 ---------- .../sync/api/GeoNatureAPIClientImpl.kt | 203 +++++++++ .../geonature/sync/api/IGeoNatureAPIClient.kt | 76 ++++ ...oNatureService.kt => IGeoNatureService.kt} | 30 +- .../{TaxHubService.kt => ITaxHubService.kt} | 4 +- .../fr/geonature/sync/auth/AuthFailure.kt | 11 + .../geonature/sync/auth/AuthLoginViewModel.kt | 106 ++--- .../fr/geonature/sync/auth/AuthManager.kt | 205 --------- .../fr/geonature/sync/auth/AuthManagerImpl.kt | 214 +++++++++ .../geonature/sync/auth/CookieManagerImpl.kt | 79 ++++ .../fr/geonature/sync/auth/IAuthManager.kt | 38 ++ .../fr/geonature/sync/auth/ICookieManager.kt | 14 + .../sync/data/MainContentProvider.kt | 234 +++++++--- .../fr/geonature/sync/data/dao/AppSyncDao.kt | 18 +- .../fr/geonature/sync/data/dao/InputDao.kt | 87 ++++ .../fr/geonature/sync/di/ServiceLocator.kt | 45 ++ .../sync/sync/AppPackageDownloadStatus.kt | 5 +- .../sync/sync/AppPackageInputsStatus.kt | 15 + .../sync/sync/IPackageInfoManager.kt | 39 ++ .../fr/geonature/sync/sync/PackageInfo.kt | 5 +- .../geonature/sync/sync/PackageInfoManager.kt | 205 --------- .../sync/sync/PackageInfoManagerImpl.kt | 218 +++++++++ .../sync/sync/PackageInfoViewModel.kt | 140 +++--- .../sync/sync/io/AppSettingsJsonWriter.kt | 3 +- .../sync/sync/worker/CheckAuthLoginWorker.kt | 5 +- .../worker/CheckInputsToSynchronizeWorker.kt | 14 +- .../sync/sync/worker/DataSyncWorker.kt | 53 ++- .../sync/sync/worker/DownloadPackageWorker.kt | 59 +-- .../sync/sync/worker/InputsSyncWorker.kt | 24 +- .../fr/geonature/sync/ui/home/HomeActivity.kt | 147 +++--- .../ui/home/PackageInfoRecyclerViewAdapter.kt | 112 +++-- .../geonature/sync/ui/login/LoginActivity.kt | 61 +-- .../res/color-night/action_drawable_color.xml | 4 + .../main/res/color/action_drawable_color.xml | 4 + .../main/res/drawable/background_login.xml | 4 +- .../src/main/res/drawable/ic_action_login.xml | 4 +- .../main/res/drawable/ic_action_logout.xml | 4 +- .../main/res/drawable/ic_action_refresh.xml | 4 +- .../main/res/drawable/ic_action_settings.xml | 4 +- sync/src/main/res/layout/activity_login.xml | 39 +- sync/src/main/res/layout/list_icon_item_2.xml | 42 +- sync/src/main/res/values-fr/strings.xml | 1 + sync/src/main/res/values-night/themes.xml | 37 ++ sync/src/main/res/values/strings.xml | 1 + sync/src/main/res/values/styles.xml | 33 -- sync/src/main/res/values/themes.xml | 63 +++ sync/src/pag/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3130 bytes .../pag/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5225 bytes sync/src/pag/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2119 bytes .../pag/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3266 bytes sync/src/pag/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4423 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7500 bytes .../src/pag/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6940 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 12134 bytes .../pag/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9380 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16942 bytes sync/src/pag/res/values/colors.xml | 9 + .../pagDebug/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3464 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5597 bytes .../pagDebug/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2264 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3454 bytes .../pagDebug/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7966 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7546 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 12923 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10303 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 17987 bytes .../fr/geonature/sync/MockitoKotlinHelper.kt | 35 ++ .../fr/geonature/sync/auth/AuthManagerTest.kt | 420 +++++++++++------- .../geonature/sync/auth/CookieManagerTest.kt | 129 ++++++ 96 files changed, 3239 insertions(+), 1827 deletions(-) create mode 100644 commons/src/main/java/fr/geonature/commons/fp/Either.kt create mode 100644 commons/src/main/java/fr/geonature/commons/fp/Failure.kt create mode 100644 commons/src/main/java/fr/geonature/commons/input/IInputManager.kt delete mode 100644 commons/src/main/java/fr/geonature/commons/input/InputManager.kt create mode 100644 commons/src/main/java/fr/geonature/commons/input/InputManagerImpl.kt create mode 100644 commons/src/main/java/fr/geonature/commons/util/NetworkHandler.kt delete mode 100644 commons/src/main/res/values/styles.xml create mode 100644 commons/src/test/java/fr/geonature/commons/data/DummyContentProvider.kt create mode 100644 commons/src/test/java/fr/geonature/commons/fp/EitherTest.kt delete mode 100644 sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClient.kt create mode 100644 sync/src/main/java/fr/geonature/sync/api/GeoNatureAPIClientImpl.kt create mode 100644 sync/src/main/java/fr/geonature/sync/api/IGeoNatureAPIClient.kt rename sync/src/main/java/fr/geonature/sync/api/{GeoNatureService.kt => IGeoNatureService.kt} (79%) rename sync/src/main/java/fr/geonature/sync/api/{TaxHubService.kt => ITaxHubService.kt} (88%) create mode 100644 sync/src/main/java/fr/geonature/sync/auth/AuthFailure.kt delete mode 100644 sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt create mode 100644 sync/src/main/java/fr/geonature/sync/auth/AuthManagerImpl.kt create mode 100644 sync/src/main/java/fr/geonature/sync/auth/CookieManagerImpl.kt create mode 100644 sync/src/main/java/fr/geonature/sync/auth/IAuthManager.kt create mode 100644 sync/src/main/java/fr/geonature/sync/auth/ICookieManager.kt create mode 100644 sync/src/main/java/fr/geonature/sync/data/dao/InputDao.kt create mode 100644 sync/src/main/java/fr/geonature/sync/di/ServiceLocator.kt create mode 100644 sync/src/main/java/fr/geonature/sync/sync/AppPackageInputsStatus.kt create mode 100644 sync/src/main/java/fr/geonature/sync/sync/IPackageInfoManager.kt delete mode 100644 sync/src/main/java/fr/geonature/sync/sync/PackageInfoManager.kt create mode 100644 sync/src/main/java/fr/geonature/sync/sync/PackageInfoManagerImpl.kt create mode 100644 sync/src/main/res/color-night/action_drawable_color.xml create mode 100644 sync/src/main/res/color/action_drawable_color.xml create mode 100644 sync/src/main/res/values-night/themes.xml delete mode 100644 sync/src/main/res/values/styles.xml create mode 100644 sync/src/main/res/values/themes.xml create mode 100644 sync/src/pag/res/mipmap-hdpi/ic_launcher.png create mode 100644 sync/src/pag/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sync/src/pag/res/mipmap-mdpi/ic_launcher.png create mode 100644 sync/src/pag/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sync/src/pag/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sync/src/pag/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sync/src/pag/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sync/src/pag/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sync/src/pag/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sync/src/pag/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sync/src/pag/res/values/colors.xml create mode 100644 sync/src/pagDebug/res/mipmap-hdpi/ic_launcher.png create mode 100644 sync/src/pagDebug/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sync/src/pagDebug/res/mipmap-mdpi/ic_launcher.png create mode 100644 sync/src/pagDebug/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sync/src/pagDebug/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sync/src/pagDebug/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sync/src/pagDebug/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sync/src/pagDebug/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sync/src/pagDebug/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sync/src/pagDebug/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sync/src/test/java/fr/geonature/sync/MockitoKotlinHelper.kt create mode 100644 sync/src/test/java/fr/geonature/sync/auth/CookieManagerTest.kt diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 43602fdf..144d718b 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -152,7 +152,6 @@