diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index ae78c11..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/app/build.gradle b/app/build.gradle index 3bba2f4..19592a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,19 +2,34 @@ apply plugin: 'com.android.application' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'kotlin-kapt' +def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD") +def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD") +def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS") + +def keystorePath = "/keystore.jks" +def keystore = file(keystorePath).exists() ? file(keystorePath) : null android { compileSdkVersion 31 defaultConfig { applicationId "com.appttude.h_mal.farmr" minSdkVersion 21 targetSdkVersion 31 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "2.0" testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner' vectorDrawables.useSupportLibrary = true } + signingConfigs { + release { + storePassword relStorePassword + keyPassword relKeyPassword + keyAlias relKeyAlias + storeFile keystore + } + } buildTypes { release { + signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt index 7a11af7..31f500e 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/data/ShiftProviderTest.kt @@ -3,6 +3,7 @@ package com.appttude.h_mal.farmr.data import android.content.ContentResolver import android.content.ContentValues import androidx.test.rule.provider.ProviderTestRule +import com.appttude.h_mal.farmr.data.legacydb.ShiftProvider import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.CONTENT_AUTHORITY import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE @@ -16,7 +17,6 @@ import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_ import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.CONTENT_URI import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID -import com.appttude.h_mal.farmr.data.legacydb.ShiftProvider import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNull import org.junit.After diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt index 0536254..8d4ebb8 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt @@ -6,9 +6,7 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.view.View -import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction @@ -18,7 +16,6 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.appttude.h_mal.farmr.application.TestAppClass -import com.appttude.h_mal.farmr.di.ShiftApplication import com.appttude.h_mal.farmr.ui.utils.getShifts import kotlinx.coroutines.runBlocking import org.hamcrest.Matcher diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/FilterScreenRobot.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/FilterScreenRobot.kt index 5907ac6..acd6f63 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/FilterScreenRobot.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/FilterScreenRobot.kt @@ -1,8 +1,8 @@ package com.appttude.h_mal.farmr.ui.robots import com.appttude.h_mal.farmr.R -import com.appttude.h_mal.farmr.ui.BaseTestRobot import com.appttude.h_mal.farmr.model.ShiftType +import com.appttude.h_mal.farmr.ui.BaseTestRobot fun filterScreen(func: FilterScreenRobot.() -> Unit) = FilterScreenRobot().apply { func() } class FilterScreenRobot : BaseTestRobot() { diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/HomeScreenRobot.kt index 4584b4a..81bf480 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/HomeScreenRobot.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/HomeScreenRobot.kt @@ -1,6 +1,5 @@ package com.appttude.h_mal.farmr.ui.robots -import androidx.test.espresso.matcher.RootMatchers.isDialog import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder import com.appttude.h_mal.farmr.model.Order diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/ViewItemScreenRobot.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/ViewItemScreenRobot.kt index 0dbcdad..b56d799 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/ViewItemScreenRobot.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/robots/ViewItemScreenRobot.kt @@ -1,8 +1,8 @@ package com.appttude.h_mal.farmr.ui.robots import com.appttude.h_mal.farmr.R -import com.appttude.h_mal.farmr.ui.BaseTestRobot import com.appttude.h_mal.farmr.model.ShiftType +import com.appttude.h_mal.farmr.ui.BaseTestRobot fun viewScreen(func: ViewItemScreenRobot.() -> Unit) = ViewItemScreenRobot().apply { func() } class ViewItemScreenRobot : BaseTestRobot() { diff --git a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/tests/ShiftTests.kt b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/tests/ShiftTests.kt index e5f4a2f..a7decde 100644 --- a/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/tests/ShiftTests.kt +++ b/app/src/androidTest/java/com/appttude/h_mal/farmr/ui/tests/ShiftTests.kt @@ -9,7 +9,6 @@ import com.appttude.h_mal.farmr.ui.robots.addScreen import com.appttude.h_mal.farmr.ui.robots.filterScreen import com.appttude.h_mal.farmr.ui.robots.homeScreen import com.appttude.h_mal.farmr.ui.robots.viewScreen -import com.appttude.h_mal.farmr.utils.ID import org.junit.Test class ShiftTests : BaseTest(MainActivity::class.java) { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b13a45..6fc2773 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,7 @@ ?, selection: String?, selectionArgs: Array?, - sortOrder: String?): Cursor? { - var selection = selection - var selectionArgs = selectionArgs + override fun query( + uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, + sortOrder: String? + ): Cursor { val database = mDbHelper!!.readableDatabase - val cursor: Cursor - val match = sUriMatcher.match(uri) - when (match) { - SHIFTS -> cursor = database.query(ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs, - null, null, sortOrder) + val cursor: Cursor = when (sUriMatcher.match(uri)) { + SHIFTS -> database.query( + ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs, + null, null, sortOrder + ) SHIFT_ID -> { - selection = ShiftsEntry._ID + "=?" - selectionArgs = arrayOf(ContentUris.parseId(uri).toString()) - cursor = database.query(ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs, - null, null, sortOrder) + val mSelection = ShiftsEntry._ID + "=?" + val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString()) + database.query( + ShiftsEntry.TABLE_NAME, projection, mSelection, mSelectionArgs, + null, null, sortOrder + ) } else -> throw IllegalArgumentException("Cannot query $uri") @@ -44,26 +46,25 @@ class ShiftProvider : ContentProvider() { } override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { - val match = sUriMatcher.match(uri) - return when (match) { + return when (sUriMatcher.match(uri)) { SHIFTS -> insertShift(uri, contentValues) else -> throw IllegalArgumentException("Insertion is not supported for $uri") } } private fun insertShift(uri: Uri, values: ContentValues?): Uri? { - val description = values!!.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION) - ?: throw IllegalArgumentException("Description required") - val date = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE) - ?: throw IllegalArgumentException("Date required") - val timeIn = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN) - ?: throw IllegalArgumentException("Time In required") - val timeOut = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT) - ?: throw IllegalArgumentException("Time Out required") + values!!.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION) + ?: throw IllegalArgumentException("Description required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE) + ?: throw IllegalArgumentException("Date required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN) + ?: throw IllegalArgumentException("Time In required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT) + ?: throw IllegalArgumentException("Time Out required") val duration = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_DURATION) require(duration >= 0) { "Duration cannot be negative" } - val shiftType = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TYPE) - ?: throw IllegalArgumentException("Shift type required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_TYPE) + ?: throw IllegalArgumentException("Shift type required") val shiftUnits = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_UNIT) require(shiftUnits >= 0) { "Units cannot be negative" } val payRate = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_PAYRATE) @@ -82,43 +83,47 @@ class ShiftProvider : ContentProvider() { return ContentUris.withAppendedId(uri, id) } - override fun update(uri: Uri, contentValues: ContentValues?, selection: String?, - selectionArgs: Array?): Int { - var selection = selection - var selectionArgs = selectionArgs - val match = sUriMatcher.match(uri) - return when (match) { + override fun update( + uri: Uri, contentValues: ContentValues?, selection: String?, + selectionArgs: Array? + ): Int { + return when (sUriMatcher.match(uri)) { SHIFTS -> updateShift(uri, contentValues, selection, selectionArgs) SHIFT_ID -> { - selection = ShiftsEntry._ID + "=?" - selectionArgs = arrayOf(ContentUris.parseId(uri).toString()) - updateShift(uri, contentValues, selection, selectionArgs) + val mSelection = ShiftsEntry._ID + "=?" + val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString()) + updateShift(uri, contentValues, mSelection, mSelectionArgs) } else -> throw IllegalArgumentException("Update is not supported for $uri") } } - private fun updateShift(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { + private fun updateShift( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { if (values!!.containsKey(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)) { - val description = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION) - ?: throw IllegalArgumentException("description required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION) + ?: throw IllegalArgumentException("description required") } if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_DATE)) { - val date = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE) - ?: throw IllegalArgumentException("date required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE) + ?: throw IllegalArgumentException("date required") } if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_IN)) { - val timeIn = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN) - ?: throw IllegalArgumentException("time in required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN) + ?: throw IllegalArgumentException("time in required") } if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)) { - val timeOut = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT) - ?: throw IllegalArgumentException("time out required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT) + ?: throw IllegalArgumentException("time out required") } if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_BREAK)) { - val breaks = values.getAsString(ShiftsEntry.COLUMN_SHIFT_BREAK) - ?: throw IllegalArgumentException("break required") + values.getAsString(ShiftsEntry.COLUMN_SHIFT_BREAK) + ?: throw IllegalArgumentException("break required") } if (values.size() == 0) { return 0 @@ -132,17 +137,15 @@ class ShiftProvider : ContentProvider() { } override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { - var selection = selection - var selectionArgs = selectionArgs val database = mDbHelper!!.writableDatabase - val rowsDeleted: Int - val match = sUriMatcher.match(uri) - when (match) { - SHIFTS -> rowsDeleted = database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs) + val rowsDeleted: Int = when (sUriMatcher.match(uri)) { + SHIFTS -> database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs) + SHIFT_ID -> { - selection = ShiftsEntry._ID + "=?" - selectionArgs = arrayOf(ContentUris.parseId(uri).toString()) - rowsDeleted = database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs) + val mSelection = ShiftsEntry._ID + "=?" + val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString()) + + database.delete(ShiftsEntry.TABLE_NAME, mSelection, mSelectionArgs) } else -> throw IllegalArgumentException("Deletion is not supported for $uri") @@ -169,7 +172,11 @@ class ShiftProvider : ContentProvider() { init { sUriMatcher.addURI(ShiftsContract.CONTENT_AUTHORITY, ShiftsContract.PATH_SHIFTS, SHIFTS) - sUriMatcher.addURI(ShiftsContract.CONTENT_AUTHORITY, ShiftsContract.PATH_SHIFTS + "/#", SHIFT_ID) + sUriMatcher.addURI( + ShiftsContract.CONTENT_AUTHORITY, + ShiftsContract.PATH_SHIFTS + "/#", + SHIFT_ID + ) } } diff --git a/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt index c0195fe..9fa0a96 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/data/legacydb/ShiftsContract.kt @@ -3,13 +3,12 @@ package com.appttude.h_mal.farmr.data.legacydb import android.content.ContentResolver import android.net.Uri import android.provider.BaseColumns -import com.appttude.h_mal.farmr.BuildConfig /** * Created by h_mal on 26/12/2017. */ object ShiftsContract { - const val CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID + const val CONTENT_AUTHORITY = "com.appttude.h_mal.farmr" val BASE_CONTENT_URI = Uri.parse("content://$CONTENT_AUTHORITY") const val PATH_SHIFTS = "shifts" diff --git a/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt b/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt index 3b0cd5f..cc4b998 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/di/ShiftApplication.kt @@ -1,17 +1,8 @@ package com.appttude.h_mal.farmr.di import com.appttude.h_mal.farmr.base.BaseApplication -import com.appttude.h_mal.farmr.data.RepositoryImpl import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider -import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory -import org.kodein.di.Kodein -import org.kodein.di.KodeinAware -import org.kodein.di.android.x.androidXModule -import org.kodein.di.generic.bind -import org.kodein.di.generic.instance -import org.kodein.di.generic.provider -import org.kodein.di.generic.singleton class ShiftApplication: BaseApplication() { diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt index 2f87159..4df1de5 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/model/Shift.kt @@ -1,10 +1,7 @@ package com.appttude.h_mal.farmr.model -import com.appttude.h_mal.farmr.data.legacydb.ShiftObject import com.appttude.h_mal.farmr.utils.calculateDuration -import com.appttude.h_mal.farmr.utils.convertTimeStringToHourMinutesPair import com.appttude.h_mal.farmr.utils.formatToTwoDp -import java.io.IOException data class Shift( val type: ShiftType, diff --git a/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt b/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt index 609fd20..bb2bbf7 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt @@ -10,8 +10,6 @@ enum class Sortable(val label: String) { TOTALPAY("Total Pay"); companion object { - val entries = Sortable.values() - fun getEnumByType(label: String): Sortable { return Sortable.values().first { it.label == label } } diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt index 889198e..1b9caf5 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt @@ -26,7 +26,6 @@ import com.appttude.h_mal.farmr.utils.setDatePicker import com.appttude.h_mal.farmr.utils.setTimePicker import com.appttude.h_mal.farmr.utils.show import com.appttude.h_mal.farmr.utils.validateField -import com.appttude.h_mal.farmr.viewmodel.MainViewModel import com.appttude.h_mal.farmr.viewmodel.SubmissionViewModel class FragmentAddItem : BaseFragment(R.layout.fragment_add_item), @@ -120,6 +119,7 @@ class FragmentAddItem : BaseFragment(R.layout.fragment_add_ private fun setupViewAfterViewCreated() { id = arguments?.getLong(ID) + wholeView.hide() val title = when (arguments?.containsKey(ID)) { true -> { diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt index 09592b9..5e1866b 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt @@ -1,12 +1,10 @@ package com.appttude.h_mal.farmr.ui import android.Manifest -import android.annotation.SuppressLint import android.app.Activity import android.app.AlertDialog import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Bundle import android.view.MenuItem import android.view.View @@ -95,7 +93,7 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr AlertDialog.Builder(context) .setTitle("Help & Support:") .setView(R.layout.dialog_layout) - .setPositiveButton(android.R.string.yes) { arg0, arg1 -> arg0.dismiss() } + .setPositiveButton(android.R.string.ok) { arg0, _ -> arg0.dismiss() } .create().show() return true } @@ -120,12 +118,11 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr AlertDialog.Builder(context) .setTitle("Export?") .setMessage("Exporting current filtered data. Continue?") - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes) { arg0, arg1 -> exportData() } + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> exportData() } .create().show() } else { - Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT) - .show() + displayToast("Storage permissions required") } return true } @@ -134,7 +131,7 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr AlertDialog.Builder(context) .setTitle("Info:") .setMessage(viewModel.getInformation()) - .setPositiveButton(android.R.string.yes) { arg0, arg1 -> + .setPositiveButton(android.R.string.ok) { arg0, _ -> arg0.dismiss() }.create().show() return true @@ -144,7 +141,7 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr } private fun sortData() { - val groupName = Sortable.entries.map { it.label }.toTypedArray() + val groupName = Sortable.values().map { it.label }.toTypedArray() var sort = Sortable.ID val sortAndOrder = viewModel.getSortAndOrder() @@ -155,11 +152,11 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr .setSingleChoiceItems( groupName, checkedItem - ) { p0, p1 -> sort = Sortable.getEnumByType(groupName[p1]) } - .setPositiveButton("Ascending") { dialog, id -> + ) { _, p1 -> sort = Sortable.getEnumByType(groupName[p1]) } + .setPositiveButton("Ascending") { dialog, _ -> viewModel.setSortAndOrder(sort) dialog.dismiss() - }.setNegativeButton("Descending") { dialog, id -> + }.setNegativeButton("Descending") { dialog, _ -> viewModel.setSortAndOrder(sort, Order.DESCENDING) dialog.dismiss() } @@ -210,25 +207,25 @@ class FragmentMain : BaseFragment(R.layout.fragment_main), BackPr super.onRequestPermissionsResult(requestCode, permissions, grantResults) println("request code$requestCode") if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) { - if (grantResults.size > 0 + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED ) { exportDialog() } else { - Toast.makeText(context, "Storage Permissions denied", Toast.LENGTH_SHORT).show() + displayToast("Storage Permissions denied") } } } - fun exportDialog() { + private fun exportDialog() { AlertDialog.Builder(context) .setTitle("Export?") .setMessage("Exporting current filtered data. Continue?") - .setNegativeButton(android.R.string.no, null) - .setPositiveButton(android.R.string.yes) { arg0, arg1 -> exportData() }.create().show() + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> exportData() }.create().show() } - fun checkStoragePermissions(activity: Activity?): Boolean { + private fun checkStoragePermissions(activity: Activity?): Boolean { var status = false val permission = ActivityCompat.checkSelfPermission( activity!!, diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt index e536e10..be79fa7 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/FurtherInfoFragment.kt @@ -12,7 +12,6 @@ import com.appttude.h_mal.farmr.data.legacydb.ShiftObject import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.utils.CURRENCY import com.appttude.h_mal.farmr.utils.formatAsCurrencyString -import com.appttude.h_mal.farmr.utils.formatToTwoDpString import com.appttude.h_mal.farmr.utils.hide import com.appttude.h_mal.farmr.utils.navigateToFragment import com.appttude.h_mal.farmr.utils.show diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt index 245b54f..2bb2763 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/MainActivity.kt @@ -1,11 +1,7 @@ package com.appttude.h_mal.farmr.ui import android.Manifest -import android.R.string.cancel -import android.R.string.ok import android.app.Activity -import android.app.AlertDialog -import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.view.Menu @@ -14,10 +10,7 @@ import androidx.core.app.ActivityCompat import com.appttude.h_mal.farmr.R import com.appttude.h_mal.farmr.base.BackPressedListener import com.appttude.h_mal.farmr.base.BaseActivity -import com.appttude.h_mal.farmr.utils.createDialog import com.appttude.h_mal.farmr.utils.popBackStack -import com.appttude.h_mal.farmr.viewmodel.MainViewModel -import kotlin.system.exitProcess class MainActivity : BaseActivity() { private lateinit var toolbar: Toolbar diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftListAdapter.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftListAdapter.kt index 59d8987..5151661 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftListAdapter.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/ShiftListAdapter.kt @@ -1,5 +1,6 @@ package com.appttude.h_mal.farmr.ui +import android.annotation.SuppressLint import android.app.AlertDialog import android.os.Bundle import android.view.ViewGroup @@ -28,6 +29,7 @@ class ShiftListAdapter( return BaseRecyclerAdapter.CurrentViewHolder(currentViewHolder) } + @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: BaseRecyclerAdapter.CurrentViewHolder, position: Int) { val view = holder.itemView val data = getItem(position) @@ -90,8 +92,8 @@ class ShiftListAdapter( view.setOnLongClickListener { AlertDialog.Builder(it.context) .setMessage("Are you sure you want to delete") - .setPositiveButton("delete") { dialog, id -> longPressCallback.invoke(data.id) } - .setNegativeButton("cancel") { dialog, id -> + .setPositiveButton("delete") { _, _ -> longPressCallback.invoke(data.id) } + .setNegativeButton("cancel") { dialog, _ -> dialog?.dismiss() } .create().show() diff --git a/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt b/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt index 087320e..5f8cfd8 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/ui/SplashScreen.kt @@ -1,18 +1,17 @@ package com.appttude.h_mal.farmr.ui +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper -import android.view.View -import android.widget.RelativeLayout -import androidx.core.app.ActivityOptionsCompat import com.appttude.h_mal.farmr.R /** * Created by h_mal on 27/06/2017. */ +@SuppressLint("CustomSplashScreen") class SplashScreen : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -20,13 +19,9 @@ class SplashScreen : Activity() { val i = Intent(this@SplashScreen, MainActivity::class.java) i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK) - Handler().postDelayed({ - // This method will be executed once the timer is over - // Start your app main activity -// startActivity(i,bundle); + Handler(Looper.getMainLooper()).postDelayed({ startActivity(i) overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) - // finish(); }, SPLASH_TIME_OUT) } diff --git a/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt b/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt index 659b798..1e68035 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt @@ -19,9 +19,7 @@ import androidx.annotation.AnimRes import androidx.annotation.IdRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentTransaction import com.appttude.h_mal.farmr.R -import com.appttude.h_mal.farmr.ui.FragmentAddItem import java.util.Calendar fun View.show() { @@ -161,12 +159,12 @@ fun EditText.setDatePicker(onSelected: (String) -> Unit) { } val mDatePicker = DatePickerDialog( (this.context), - { datepicker, selectedyear, selectedmonth, selectedday -> - var currentMonth = selectedmonth - val dateString = StringBuilder().append(selectedyear).append("-") + { _, selectedYear, selectedMonth, selectedDay -> + var currentMonth = selectedMonth + val dateString = StringBuilder().append(selectedYear).append("-") .append(String.format("%02d", (currentMonth + 1.also { currentMonth = it }))) .append("-") - .append(String.format("%02d", selectedday)) + .append(String.format("%02d", selectedDay)) .toString() setText(dateString) onSelected.invoke(dateString) diff --git a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModel.kt b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModel.kt index dea7585..025829e 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModel.kt @@ -35,6 +35,5 @@ class InfoViewModel( stringBuilder.append(" (+ ").append(shiftObject.breakMins).append(" minutes break)") } return stringBuilder.toString() - } } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt index 7a99874..7929615 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/MainViewModel.kt @@ -22,7 +22,6 @@ import com.appttude.h_mal.farmr.model.Order import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.Sortable import com.appttude.h_mal.farmr.model.Success -import com.appttude.h_mal.farmr.utils.CURRENCY import com.appttude.h_mal.farmr.utils.convertDateString import com.appttude.h_mal.farmr.utils.formatAsCurrencyString import com.appttude.h_mal.farmr.utils.sortedByOrder @@ -239,7 +238,7 @@ class MainViewModel( val data = shiftLiveData.value!!.applyFilters() .sortList(sortAndOrder.first, sortAndOrder.second) var currentRow = 0 - val cells = data.mapIndexed { index, shift -> + val cells = data.map { shift -> currentRow += 1 listOf( Label(0, currentRow, shift.id.toString()), diff --git a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ShiftViewModel.kt b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ShiftViewModel.kt index 33c7ea5..abcc710 100644 --- a/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ShiftViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/farmr/viewmodel/ShiftViewModel.kt @@ -2,9 +2,9 @@ package com.appttude.h_mal.farmr.viewmodel import com.appttude.h_mal.farmr.base.BaseViewModel import com.appttude.h_mal.farmr.data.Repository -import com.appttude.h_mal.farmr.data.prefs.DESCRIPTION import com.appttude.h_mal.farmr.data.prefs.DATE_IN import com.appttude.h_mal.farmr.data.prefs.DATE_OUT +import com.appttude.h_mal.farmr.data.prefs.DESCRIPTION import com.appttude.h_mal.farmr.data.prefs.TYPE import com.appttude.h_mal.farmr.model.FilterStore diff --git a/app/src/test/java/com/appttude/h_mal/farmr/data/RepositoryImplTest.kt b/app/src/test/java/com/appttude/h_mal/farmr/data/RepositoryImplTest.kt index 4c53925..4688aa6 100644 --- a/app/src/test/java/com/appttude/h_mal/farmr/data/RepositoryImplTest.kt +++ b/app/src/test/java/com/appttude/h_mal/farmr/data/RepositoryImplTest.kt @@ -10,7 +10,6 @@ import io.mockk.mockk import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyLong -import java.util.UUID import kotlin.test.assertEquals import kotlin.test.assertIs diff --git a/app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt b/app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt index 4d64b8d..e04e3ce 100644 --- a/app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt +++ b/app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt @@ -2,9 +2,12 @@ package com.appttude.h_mal.farmr.utils import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import com.appttude.h_mal.farmr.data.legacydb.ShiftObject +import com.appttude.h_mal.farmr.model.ShiftType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking +import org.mockito.ArgumentMatchers import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException @@ -36,4 +39,111 @@ fun LiveData.getOrAwaitValue( fun sleep(millis: Long = 1000) { runBlocking(Dispatchers.Default) { delay(millis) } -} \ No newline at end of file +} + +fun getShifts() = listOf( + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.HOURLY.type, + "Day one", + "2023-08-01", + "12:00", + "13:00", + 1f, + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyFloat(), + 10f, + 10f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.HOURLY.type, + "Day two", + "2023-08-02", + "12:00", + "13:00", + 1f, + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyFloat(), + 10f, + 10f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.HOURLY.type, + "Day three", + "2023-08-03", + "12:00", + "13:00", + 1f, + 30, + ArgumentMatchers.anyFloat(), + 10f, + 5f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.HOURLY.type, + "Day four", + "2023-08-04", + "12:00", + "13:00", + 1f, + 30, + ArgumentMatchers.anyFloat(), + 10f, + 5f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.PIECE.type, + "Day five", + "2023-08-05", + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyFloat(), + ArgumentMatchers.anyInt(), + 1f, + 10f, + 10f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.PIECE.type, + "Day six", + "2023-08-06", + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyFloat(), + ArgumentMatchers.anyInt(), + 1f, + 10f, + 10f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.PIECE.type, + "Day seven", + "2023-08-07", + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyFloat(), + ArgumentMatchers.anyInt(), + 1f, + 10f, + 10f + ), + ShiftObject( + ArgumentMatchers.anyLong(), + ShiftType.PIECE.type, + "Day eight", + "2023-08-08", + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyFloat(), + ArgumentMatchers.anyInt(), + 1f, + 10f, + 10f + ), +) \ No newline at end of file diff --git a/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModelTest.kt b/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModelTest.kt new file mode 100644 index 0000000..3fb73c0 --- /dev/null +++ b/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/InfoViewModelTest.kt @@ -0,0 +1,93 @@ +package com.appttude.h_mal.farmr.viewmodel + +import android.os.Bundle +import com.appttude.h_mal.farmr.data.legacydb.ShiftObject +import com.appttude.h_mal.farmr.utils.ID +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Test +import org.mockito.ArgumentMatchers.anyLong +import kotlin.test.assertIs + +class InfoViewModelTest : ShiftViewModelTest() { + + @Test + fun retrieveData_validBundleAndId_successfulRetrieval() { + // Arrange + val id = anyLong() + val shift = mockk() + val bundle = mockk() + + // Act + every { repository.readSingleShiftFromDatabase(id) }.returns(shift) + every { bundle.getLong(ID) }.returns(id) + viewModel.retrieveData(bundle) + + // Assert + assertIs(retrieveCurrentData()) + assertEquals( + retrieveCurrentData(), + shift + ) + } + + @Test + fun retrieveData_noValidBundleAndId_unsuccessfulRetrieval() { + // Arrange + val id = anyLong() + val shift = mockk() + val bundle = mockk() + + // Act + every { repository.readSingleShiftFromDatabase(id) }.returns(shift) + every { bundle.getLong(ID) }.returns(id) + viewModel.retrieveData(null) + + // Assert + assertEquals( + retrieveCurrentError(), + "Failed to retrieve shift" + ) + } + + @Test + fun retrieveData_validBundleNoShift_successfulRetrieval() { + // Arrange + val id = anyLong() + val bundle = mockk() + + // Act + every { repository.readSingleShiftFromDatabase(id) }.returns(null) + every { bundle.getLong(ID) }.returns(id) + viewModel.retrieveData(bundle) + + // Assert + assertEquals( + retrieveCurrentError(), + "Failed to retrieve shift" + ) + } + + @Test + fun buildDurationSummary_validHourlyShift_successfulRetrieval() { + // Arrange + val shift = getShifts()[0] + val shiftWithBreak = getShifts()[3] + + // Act + val summary = viewModel.buildDurationSummary(shift) + val summaryWithBreak = viewModel.buildDurationSummary(shiftWithBreak) + + // Assert + assertEquals( + "1 Hours 0 Minutes ", + summary + ) + assertEquals( + "1 Hours 0 Minutes (+ 30 minutes break)", + summaryWithBreak + ) + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/MainViewModelTest.kt b/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/MainViewModelTest.kt index 127dab4..4ba2d24 100644 --- a/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/MainViewModelTest.kt +++ b/app/src/test/java/com/appttude/h_mal/farmr/viewmodel/MainViewModelTest.kt @@ -10,17 +10,14 @@ import com.appttude.h_mal.farmr.data.prefs.TYPE import com.appttude.h_mal.farmr.model.ShiftType import com.appttude.h_mal.farmr.model.ViewState import com.appttude.h_mal.farmr.utils.getOrAwaitValue +import com.appttude.h_mal.farmr.utils.getShifts import io.mockk.every import io.mockk.mockk import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Rule import org.junit.Test -import org.mockito.ArgumentMatchers.anyFloat -import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyList -import org.mockito.ArgumentMatchers.anyLong -import org.mockito.ArgumentMatchers.anyString import java.util.concurrent.TimeoutException import kotlin.test.assertEquals @@ -130,110 +127,4 @@ class MainViewModelTest { Pair(TYPE, type) ) - private fun getShifts() = listOf( - ShiftObject( - anyLong(), - ShiftType.HOURLY.type, - "Day one", - "2023-08-01", - "12:00", - "13:00", - 1f, - anyInt(), - anyFloat(), - 10f, - 10f - ), - ShiftObject( - anyLong(), - ShiftType.HOURLY.type, - "Day two", - "2023-08-02", - "12:00", - "13:00", - 1f, - anyInt(), - anyFloat(), - 10f, - 10f - ), - ShiftObject( - anyLong(), - ShiftType.HOURLY.type, - "Day three", - "2023-08-03", - "12:00", - "13:00", - 1f, - 30, - anyFloat(), - 10f, - 5f - ), - ShiftObject( - anyLong(), - ShiftType.HOURLY.type, - "Day four", - "2023-08-04", - "12:00", - "13:00", - 1f, - 30, - anyFloat(), - 10f, - 5f - ), - ShiftObject( - anyLong(), - ShiftType.PIECE.type, - "Day five", - "2023-08-05", - anyString(), - anyString(), - anyFloat(), - anyInt(), - 1f, - 10f, - 10f - ), - ShiftObject( - anyLong(), - ShiftType.PIECE.type, - "Day six", - "2023-08-06", - anyString(), - anyString(), - anyFloat(), - anyInt(), - 1f, - 10f, - 10f - ), - ShiftObject( - anyLong(), - ShiftType.PIECE.type, - "Day seven", - "2023-08-07", - anyString(), - anyString(), - anyFloat(), - anyInt(), - 1f, - 10f, - 10f - ), - ShiftObject( - anyLong(), - ShiftType.PIECE.type, - "Day eight", - "2023-08-08", - anyString(), - anyString(), - anyFloat(), - anyInt(), - 1f, - 10f, - 10f - ), - ) } \ No newline at end of file diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..116ee2e --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +json_key_file("google-play-key.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one +package_name("com.appttude.h_mal.farmr") # e.g. com.krausefx.app diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..5d272f7 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,29 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:android) + +platform :android do + desc "Runs all the tests" + lane :test do + gradle(task: "test") + end + + desc "Deploy a new version to the Google Play" + lane :deploy do + gradle(task: "clean assembleRelease") + upload_to_play_store + end +end diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..5649d3a --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,40 @@ +fastlane documentation +---- + +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +```sh +xcode-select --install +``` + +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) + +# Available Actions + +## Android + +### android test + +```sh +[bundle exec] fastlane android test +``` + +Runs all the tests + +### android deploy + +```sh +[bundle exec] fastlane android deploy +``` + +Deploy a new version to the Google Play + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).