diff --git a/.circleci/config.yml b/.circleci/config.yml index 06d0f33..d395d0a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,21 +45,19 @@ commands: default: "AtlasWeather" package_suffix: type: string - default: "atlas_weather" + default: "atlasWeather" steps: - android/start-emulator-and-run-tests: post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest --continue system-image: system-images;android-26;google_apis;x86 - pull-data: true - pull-data-target: ~/app-data # store screenshots for failed ui tests - when: condition: on_fail steps: - store_artifacts: - path: ~/app-data - destination: screenshots + path: app/build/outputs/connected_android_test_additional_output/monoWeatherDebugAndroidTest/connected + destination: connected_android_test # store test reports - store_artifacts: path: app/build/reports/androidTests/connected @@ -97,9 +95,6 @@ jobs: flavour: type: string default: "AtlasWeather" - package_suffix: - type: string - default: "atlas_weather" # These next lines define the Android machine image executor. # See: https://circleci.com/docs/2.0/executor-types/ executor: @@ -111,8 +106,27 @@ jobs: - setup_repo - run_tests: flavour: << parameters.flavour >> + run_instrumentation_test: + # Parameters used for determining + parameters: + flavour: + type: string + default: "AtlasWeather" + package_suffix: + type: string + default: "atlasWeather" + # These next lines define the Android machine image executor. + # See: https://circleci.com/docs/2.0/executor-types/ + executor: + name: android/android-machine + tag: 2023.05.1 + # Add steps to the job + # See: https://circleci.com/docs/2.0/configuration-reference/#steps + steps: + - setup_repo - run_ui_tests: flavour: << parameters.flavour >> + package_suffix: << parameters.package_suffix >> deploy-to-playstore: parameters: flavour: @@ -137,11 +151,19 @@ workflows: - build-and-test: context: appttude flavour: "MonoWeather" - package_suffix: "monoWeather" filters: branches: ignore: - main_atlas + - run_instrumentation_test: + context: appttude + flavour: "MonoWeather" + package_suffix: "monoWeather" + filters: + branches: + only: + - master + - main_mono - deploy-to-playstore: context: appttude flavour: "MonoWeather" @@ -156,11 +178,19 @@ workflows: - build-and-test: context: appttude flavour: "AtlasWeather" - package_suffix: "atlas_weather" filters: branches: ignore: - main_mono + - run_instrumentation_test: + context: appttude + flavour: "AtlasWeather" + package_suffix: "atlasWeather" + filters: + branches: + only: + - master + - main_atlas - deploy-to-playstore: context: appttude flavour: "AtlasWeather" diff --git a/.gitignore b/.gitignore index 6b8a6e1..87e70e7 100644 --- a/.gitignore +++ b/.gitignore @@ -91,7 +91,7 @@ gen-external-apklibs .idea/jarRepositorie # Gem/fastlane -/Gemfile.lock +Gemfile.lock /fastlane/report.xml # Google play files /google-play-key.json diff --git a/app/build.gradle b/app/build.gradle index 095d7e8..496b413 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,13 +5,20 @@ plugins { id 'kotlin-kapt' id 'androidx.navigation.safeargs' } + +def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD") +def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD") +def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS") + +def keystorePath = System.getenv('PWD') + "/app/keystore.jks" +def keystore = file(keystorePath).exists() ? file(keystorePath) : null android { lintOptions { abortOnError false } - compileSdkVersion 33 defaultConfig { applicationId "com.appttude.h_mal.atlas_weather" + compileSdk 33 minSdkVersion 26 targetSdkVersion 33 versionCode 5 @@ -40,6 +47,14 @@ android { } } + signingConfigs { + release { + storePassword relStorePassword + keyPassword relKeyPassword + keyAlias relKeyAlias + storeFile keystore + } + } testOptions { unitTests { includeAndroidResources = true @@ -48,6 +63,7 @@ android { } buildTypes { release { + signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -99,6 +115,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.fragment:fragment:1.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' @@ -187,4 +204,8 @@ dependencies { / * screenshot library * / androidTestImplementation 'tools.fastlane:screengrab:2.1.1' + / * Permissions dispatcher * / + def dispatcher_ver = "4.9.2" + implementation "com.github.permissions-dispatcher:permissionsdispatcher:${dispatcher_ver}" + kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${dispatcher_ver}" } diff --git a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/home/HomeFragment.kt b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/home/HomeFragment.kt index 31240c4..baf1ae3 100644 --- a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/home/HomeFragment.kt +++ b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/home/HomeFragment.kt @@ -13,21 +13,31 @@ import androidx.navigation.ui.onNavDestinationSelected import androidx.recyclerview.widget.LinearLayoutManager import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.model.forecast.Forecast import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay -import com.appttude.h_mal.atlas_weather.ui.BaseFragment +import com.appttude.h_mal.atlas_weather.ui.dialog.PermissionsDeclarationDialog import com.appttude.h_mal.atlas_weather.ui.home.adapter.WeatherRecyclerAdapter +import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel import kotlinx.android.synthetic.main.fragment_home.* +import permissions.dispatcher.NeedsPermission +import permissions.dispatcher.OnNeverAskAgain +import permissions.dispatcher.OnPermissionDenied +import permissions.dispatcher.OnShowRationale +import permissions.dispatcher.PermissionRequest +import permissions.dispatcher.RuntimePermissions /** * A simple [Fragment] subclass. * create an instance of this fragment. */ +@RuntimePermissions class HomeFragment : BaseFragment(R.layout.fragment_home) { - private lateinit var recyclerAdapter: WeatherRecyclerAdapter + + lateinit var recyclerAdapter: WeatherRecyclerAdapter @SuppressLint("MissingPermission") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -36,10 +46,8 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { swipe_refresh.apply { setOnRefreshListener { - getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { - viewModel.fetchData() - isRefreshing = true - } + showLocationWithPermissionCheck() + isRefreshing = true } } @@ -47,24 +55,19 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { navigateToFurtherDetails(it) }) - forecast_listview.apply { - layoutManager = LinearLayoutManager(context) - adapter = recyclerAdapter - } + forecast_listview.adapter = recyclerAdapter } @SuppressLint("MissingPermission") override fun onStart() { super.onStart() - - getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { - viewModel.fetchData() - } + showLocationWithPermissionCheck() } override fun onSuccess(data: Any?) { super.onSuccess(data) swipe_refresh.isRefreshing = false + if (data is WeatherDisplay) { recyclerAdapter.addCurrent(data) } @@ -75,14 +78,8 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { swipe_refresh.isRefreshing = false } - @SuppressLint("MissingPermission") - override fun permissionsGranted() { - viewModel.fetchData() - } - private fun navigateToFurtherDetails(forecast: Forecast) { - val directions = HomeFragmentDirections - .actionHomeFragmentToFurtherDetailsFragment(forecast) + val directions = HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(forecast) navigateTo(directions) } @@ -95,4 +92,35 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { val navController = findNavController(requireActivity(), R.id.container) return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item) } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + // NOTE: delegate the permission handling to generated method + onRequestPermissionsResult(requestCode, grantResults) + } + + @SuppressLint("MissingPermission") + @NeedsPermission(ACCESS_COARSE_LOCATION) + fun showLocation() { + viewModel.fetchData() + } + + @OnShowRationale(ACCESS_COARSE_LOCATION) + fun showRationaleForLocation(request: PermissionRequest) { + PermissionsDeclarationDialog(requireContext()).showDialog({ + request.proceed() + }, { + request.cancel() + }) + } + + @OnPermissionDenied(ACCESS_COARSE_LOCATION) + fun onLocationDenied() { + displayToast("Location permissions have been denied") + } + + @OnNeverAskAgain(ACCESS_COARSE_LOCATION) + fun onLocationNeverAskAgain() { + displayToast("Location permissions have been to never ask again") + } } \ No newline at end of file diff --git a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/AddLocationFragment.kt b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/AddLocationFragment.kt index 2c79c5d..e9d72df 100644 --- a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/AddLocationFragment.kt +++ b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/AddLocationFragment.kt @@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.ui.world import android.os.Bundle import android.view.View import com.appttude.h_mal.atlas_weather.R -import com.appttude.h_mal.atlas_weather.ui.BaseFragment +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.goBack import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel diff --git a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/WorldFragment.kt b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/WorldFragment.kt index fd9743d..3c96e70 100644 --- a/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/WorldFragment.kt +++ b/app/src/atlasWeather/java/com/appttude/h_mal/atlas_weather/ui/world/WorldFragment.kt @@ -5,8 +5,8 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.appttude.h_mal.atlas_weather.R +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay -import com.appttude.h_mal.atlas_weather.ui.BaseFragment import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel import kotlinx.android.synthetic.atlasWeather.fragment_add_location.floatingActionButton diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseActivity.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseActivity.kt index b463b90..3625704 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseActivity.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseActivity.kt @@ -1,29 +1,22 @@ package com.appttude.h_mal.atlas_weather.base import android.content.Intent -import android.os.Bundle import android.view.View import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.inflate import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.ViewModelLazy import com.appttude.h_mal.atlas_weather.R -import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt -import com.appttude.h_mal.atlas_weather.model.ViewState import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.hide import com.appttude.h_mal.atlas_weather.utils.show import com.appttude.h_mal.atlas_weather.utils.triggerAnimation -import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory -import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.BaseViewModel import org.kodein.di.KodeinAware import org.kodein.di.android.kodein -import org.kodein.di.generic.instance abstract class BaseActivity : AppCompatActivity(), KodeinAware { - private lateinit var loadingView: View + private var loadingView: View? = null override val kodein by kodein() @@ -36,9 +29,9 @@ abstract class BaseActivity : AppCompatActivity(), KodeinAware { */ private fun instantiateLoadingView() { loadingView = inflate(this, R.layout.progress_layout, null) - loadingView.setOnClickListener(null) + loadingView?.setOnClickListener(null) addContentView(loadingView, LayoutParams(MATCH_PARENT, MATCH_PARENT)) - loadingView.hide() + loadingView?.hide() } override fun onStart() { @@ -55,14 +48,14 @@ abstract class BaseActivity : AppCompatActivity(), KodeinAware { * Called in case of success or some data emitted from the liveData in viewModel */ open fun onStarted() { - loadingView.fadeIn() + loadingView?.fadeIn() } /** * Called in case of success or some data emitted from the liveData in viewModel */ open fun onSuccess(data: Any?) { - loadingView.fadeOut() + loadingView?.fadeOut() } /** @@ -70,7 +63,7 @@ abstract class BaseActivity : AppCompatActivity(), KodeinAware { */ open fun onFailure(error: Any?) { if (error is String) displayToast(error) - loadingView.fadeOut() + loadingView?.fadeOut() } private fun View.fadeIn() = apply { @@ -85,7 +78,7 @@ abstract class BaseActivity : AppCompatActivity(), KodeinAware { override fun onBackPressed() { - loadingView.hide() + loadingView?.hide() super.onBackPressed() } } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/ui/BaseFragment.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseFragment.kt similarity index 50% rename from app/src/main/java/com/appttude/h_mal/atlas_weather/ui/BaseFragment.kt rename to app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseFragment.kt index f0cd6c1..2ec2a43 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/ui/BaseFragment.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/BaseFragment.kt @@ -1,26 +1,14 @@ -package com.appttude.h_mal.atlas_weather.ui +package com.appttude.h_mal.atlas_weather.base -import android.annotation.SuppressLint -import android.content.pm.PackageManager import android.os.Bundle import android.view.View import androidx.annotation.LayoutRes -import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment import androidx.fragment.app.createViewModelLazy -import androidx.lifecycle.Observer -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST -import com.appttude.h_mal.atlas_weather.base.BaseActivity +import com.appttude.h_mal.atlas_weather.base.baseViewModels.BaseViewModel import com.appttude.h_mal.atlas_weather.helper.GenericsHelper.getGenericClassAt import com.appttude.h_mal.atlas_weather.model.ViewState -import com.appttude.h_mal.atlas_weather.utils.Event -import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory -import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.BaseViewModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.kodein.di.KodeinAware import org.kodein.di.android.x.kodein import org.kodein.di.generic.instance @@ -32,7 +20,7 @@ abstract class BaseFragment(@LayoutRes contentLayoutId: Int) KodeinAware { override val kodein by kodein() - val factory by instance() + private val factory by instance() val viewModel: V by getFragmentViewModel() @@ -47,7 +35,6 @@ abstract class BaseFragment(@LayoutRes contentLayoutId: Int) shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime) } - @Suppress("UNCHECKED_CAST") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mActivity = activity as BaseActivity @@ -84,53 +71,4 @@ abstract class BaseFragment(@LayoutRes contentLayoutId: Int) open fun onFailure(error: Any?) { mActivity?.onFailure(error) } - - /** - * Request a permission for - * @param permission with - * @param permissionCode - * Callback if is already permission granted - * @param permissionGranted - */ - fun getPermissionResult( - permission: String, - permissionCode: Int, - permissionGranted: () -> Unit - ) { - if (ActivityCompat.checkSelfPermission( - requireContext(), - permission - ) != PackageManager.PERMISSION_GRANTED - ) { - requestPermissions(arrayOf(permission), permissionCode) - return - } else { - CoroutineScope(Dispatchers.Main).launch { - permissionGranted.invoke() - } - } - } - - @SuppressLint("MissingPermission") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == LOCATION_PERMISSION_REQUEST) { - if (grantResults.isNotEmpty() && - grantResults[0] == PackageManager.PERMISSION_GRANTED - ) { - permissionsGranted() - displayToast("Permission granted") - } else { - permissionsRefused() - displayToast("Permission denied") - } - } - } - - open fun permissionsGranted() {} - open fun permissionsRefused() {} } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/BaseViewModel.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/BaseViewModel.kt similarity index 90% rename from app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/BaseViewModel.kt rename to app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/BaseViewModel.kt index a2ecbc7..c4895c5 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/BaseViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/BaseViewModel.kt @@ -1,4 +1,4 @@ -package com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels +package com.appttude.h_mal.atlas_weather.base.baseViewModels import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/WeatherViewModel.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/WeatherViewModel.kt similarity index 87% rename from app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/WeatherViewModel.kt rename to app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/WeatherViewModel.kt index 519b53b..be19ec3 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/baseViewModels/WeatherViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/base/baseViewModels/WeatherViewModel.kt @@ -1,6 +1,5 @@ -package com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels +package com.appttude.h_mal.atlas_weather.base.baseViewModels -import androidx.lifecycle.ViewModel import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.model.weather.FullWeather diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/prefs/PreferencesProvider.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/prefs/PreferencesProvider.kt index 7b39e1b..693191f 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/prefs/PreferencesProvider.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/prefs/PreferencesProvider.kt @@ -44,6 +44,8 @@ class PreferenceProvider( preference.edit().putBoolean("FIRST_TIME_RUN", false).apply() } + fun getFirstTimeRun() = preference.getBoolean("FIRST_TIME_RUN", false) + fun isWidgetBackground(): Boolean { return preference.getBoolean("widget_black_background", false) } diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepository.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepository.kt index 8eb6b51..48dd734 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepository.kt @@ -3,5 +3,6 @@ package com.appttude.h_mal.atlas_weather.data.repository interface SettingsRepository { fun isNotificationsEnabled(): Boolean fun setFirstTime() + fun getFirstTime(): Boolean fun isBlackBackground(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepositoryImpl.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepositoryImpl.kt index 6eaec4b..5764118 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/data/repository/SettingsRepositoryImpl.kt @@ -9,6 +9,7 @@ class SettingsRepositoryImpl( override fun isNotificationsEnabled(): Boolean = prefs.isNotificationsEnabled() override fun setFirstTime() = prefs.setFirstTimeRun() + override fun getFirstTime(): Boolean = prefs.getFirstTimeRun() override fun isBlackBackground() = prefs.isWidgetBackground() } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/MainViewModel.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/MainViewModel.kt index 99cb2cd..ac81c12 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/MainViewModel.kt @@ -7,7 +7,7 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay -import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.WeatherViewModel +import com.appttude.h_mal.atlas_weather.base.baseViewModels.WeatherViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/WorldViewModel.kt b/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/WorldViewModel.kt index 30fd381..699f5de 100644 --- a/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/WorldViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/atlas_weather/viewmodel/WorldViewModel.kt @@ -6,7 +6,7 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay import com.appttude.h_mal.atlas_weather.model.types.LocationType -import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.WeatherViewModel +import com.appttude.h_mal.atlas_weather.base.baseViewModels.WeatherViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/WorldItemFragment.kt b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/WorldItemFragment.kt index 4c7e6d7..3454063 100644 --- a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/WorldItemFragment.kt +++ b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/WorldItemFragment.kt @@ -6,7 +6,7 @@ import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay -import com.appttude.h_mal.atlas_weather.ui.BaseFragment +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter diff --git a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/home/HomeFragment.kt b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/home/HomeFragment.kt index 6eec52d..956af3b 100644 --- a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/home/HomeFragment.kt +++ b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/home/HomeFragment.kt @@ -11,24 +11,30 @@ import androidx.fragment.app.Fragment import androidx.navigation.Navigation.findNavController import androidx.navigation.ui.onNavDestinationSelected import com.appttude.h_mal.atlas_weather.R -import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.model.forecast.Forecast import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay +import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog -import com.appttude.h_mal.atlas_weather.ui.BaseFragment import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter import kotlinx.android.synthetic.main.fragment_home.* +import permissions.dispatcher.NeedsPermission +import permissions.dispatcher.OnNeverAskAgain +import permissions.dispatcher.OnPermissionDenied +import permissions.dispatcher.OnShowRationale +import permissions.dispatcher.PermissionRequest +import permissions.dispatcher.RuntimePermissions /** * A simple [Fragment] subclass. * create an instance of this fragment. */ +@RuntimePermissions class HomeFragment : BaseFragment(R.layout.fragment_home) { - lateinit var dialog: PermissionsDeclarationDialog lateinit var recyclerAdapter: WeatherRecyclerAdapter @SuppressLint("MissingPermission") @@ -36,14 +42,10 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) - dialog = PermissionsDeclarationDialog(requireContext()) - swipe_refresh.apply { setOnRefreshListener { - getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { - viewModel.fetchData() - isRefreshing = true - } + showLocationWithPermissionCheck() + isRefreshing = true } } @@ -57,16 +59,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { @SuppressLint("MissingPermission") override fun onStart() { super.onStart() - dialog.showDialog(agreeCallback = { - getPermissionResult(ACCESS_COARSE_LOCATION, LOCATION_PERMISSION_REQUEST) { - viewModel.fetchData() - } - }) - } - - override fun onStop() { - super.onStop() - dialog.dismiss() + showLocationWithPermissionCheck() } override fun onSuccess(data: Any?) { @@ -83,14 +76,8 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { swipe_refresh.isRefreshing = false } - @SuppressLint("MissingPermission") - override fun permissionsGranted() { - viewModel.fetchData() - } - private fun navigateToFurtherDetails(forecast: Forecast) { - val directions = HomeFragmentDirections - .actionHomeFragmentToFurtherDetailsFragment(forecast) + val directions = HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(forecast) navigateTo(directions) } @@ -103,4 +90,35 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) { val navController = findNavController(requireActivity(), R.id.container) return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item) } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + // NOTE: delegate the permission handling to generated method + onRequestPermissionsResult(requestCode, grantResults) + } + + @SuppressLint("MissingPermission") + @NeedsPermission(ACCESS_COARSE_LOCATION) + fun showLocation() { + viewModel.fetchData() + } + + @OnShowRationale(ACCESS_COARSE_LOCATION) + fun showRationaleForLocation(request: PermissionRequest) { + PermissionsDeclarationDialog(requireContext()).showDialog({ + request.proceed() + }, { + request.cancel() + }) + } + + @OnPermissionDenied(ACCESS_COARSE_LOCATION) + fun onLocationDenied() { + displayToast("Location permissions have been denied") + } + + @OnNeverAskAgain(ACCESS_COARSE_LOCATION) + fun onLocationNeverAskAgain() { + displayToast("Location permissions have been to never ask again") + } } \ No newline at end of file diff --git a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/widget/WidgetLocationPermissionActivity.kt b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/widget/WidgetLocationPermissionActivity.kt index 2efbeb6..c3f78ea 100644 --- a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/widget/WidgetLocationPermissionActivity.kt +++ b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/widget/WidgetLocationPermissionActivity.kt @@ -1,5 +1,6 @@ package com.appttude.h_mal.monoWeather.ui.widget +import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION import android.Manifest.permission.ACCESS_COARSE_LOCATION import android.app.Activity import android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE @@ -7,20 +8,26 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_IDS import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID import android.content.Intent -import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.widget.TextView +import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat.checkSelfPermission import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.monoWeather.dialog.DeclarationBuilder +import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog import kotlinx.android.synthetic.monoWeather.permissions_declaration_dialog.cancel import kotlinx.android.synthetic.monoWeather.permissions_declaration_dialog.submit +import permissions.dispatcher.NeedsPermission +import permissions.dispatcher.OnNeverAskAgain +import permissions.dispatcher.OnPermissionDenied +import permissions.dispatcher.OnShowRationale +import permissions.dispatcher.PermissionRequest +import permissions.dispatcher.RuntimePermissions -const val PERMISSION_CODE = 401 - +@RuntimePermissions class WidgetLocationPermissionActivity : AppCompatActivity(), DeclarationBuilder { override val link: String = "https://sites.google.com/view/hmaldev/home/monochrome" override var message: String = "" @@ -54,31 +61,16 @@ class WidgetLocationPermissionActivity : AppCompatActivity(), DeclarationBuilder } submit.setOnClickListener { - if (checkSelfPermission(this, ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED) { - requestPermissions(arrayOf(ACCESS_COARSE_LOCATION), PERMISSION_CODE) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + showBackgroundLocationWithPermissionCheck() } else { - submitWidget() + showLocationWithPermissionCheck() } } cancel.setOnClickListener { finish() } } - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) { - submitWidget() - } else { - displayToast("Location Permission denied") - } - } - } - private fun submitWidget() { sendUpdateIntent() finishCurrencyWidgetActivity() @@ -106,4 +98,66 @@ class WidgetLocationPermissionActivity : AppCompatActivity(), DeclarationBuilder sendBroadcast(this) } } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + // NOTE: delegate the permission handling to generated method + onRequestPermissionsResult(requestCode, grantResults) + } + + @NeedsPermission(ACCESS_COARSE_LOCATION) + fun showLocation() { + submitWidget() + } + + @RequiresApi(Build.VERSION_CODES.Q) + @NeedsPermission(ACCESS_BACKGROUND_LOCATION) + fun showBackgroundLocation() { + submitWidget() + } + + @OnShowRationale(ACCESS_COARSE_LOCATION) + fun showRationaleForLocation(request: PermissionRequest) { + PermissionsDeclarationDialog(this).showDialog({ + request.proceed() + }, { + request.cancel() + }) + } + + @RequiresApi(Build.VERSION_CODES.Q) + @OnShowRationale(ACCESS_BACKGROUND_LOCATION) + fun showRationaleForBackgroundLocation(request: PermissionRequest) { + PermissionsDeclarationDialog(this).showDialog({ + request.proceed() + }, { + request.cancel() + }) + } + + @OnPermissionDenied(ACCESS_COARSE_LOCATION) + fun onLocationDenied() { + displayToast("Location permissions have been denied") + } + + @RequiresApi(Build.VERSION_CODES.Q) + @OnPermissionDenied(ACCESS_BACKGROUND_LOCATION) + fun onBackgroundLocationDenied() { + displayToast("Background Location permissions have been denied") + } + + @OnNeverAskAgain(ACCESS_COARSE_LOCATION) + fun onLocationNeverAskAgain() { + displayToast("Location permissions have been to never ask again") + } + + @RequiresApi(Build.VERSION_CODES.Q) + @OnNeverAskAgain(ACCESS_BACKGROUND_LOCATION) + fun onBackgroundLocationNeverAskAgain() { + displayToast("Background Location permissions have been to never ask again") + } } \ No newline at end of file diff --git a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/AddLocationFragment.kt b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/AddLocationFragment.kt index a17dcad..2277614 100644 --- a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/AddLocationFragment.kt +++ b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/AddLocationFragment.kt @@ -3,7 +3,7 @@ package com.appttude.h_mal.monoWeather.ui.world import android.os.Bundle import android.view.View import com.appttude.h_mal.atlas_weather.R -import com.appttude.h_mal.atlas_weather.ui.BaseFragment +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.utils.displayToast import com.appttude.h_mal.atlas_weather.utils.goBack import com.appttude.h_mal.atlas_weather.utils.hideKeyboard diff --git a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/WorldFragment.kt b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/WorldFragment.kt index 691343e..1d8b58a 100644 --- a/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/WorldFragment.kt +++ b/app/src/monoWeather/java/com/appttude/h_mal/monoWeather/ui/world/WorldFragment.kt @@ -6,7 +6,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay -import com.appttude.h_mal.atlas_weather.ui.BaseFragment +import com.appttude.h_mal.atlas_weather.base.BaseFragment import com.appttude.h_mal.atlas_weather.utils.navigateTo import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel import com.appttude.h_mal.monoWeather.ui.world.WorldFragmentDirections.actionWorldFragmentToWorldItemFragment diff --git a/app/src/monoWeather/res/xml-v31/new_app_widget_info.xml b/app/src/monoWeather/res/xml-v31/new_app_widget_info.xml new file mode 100644 index 0000000..5caf2fd --- /dev/null +++ b/app/src/monoWeather/res/xml-v31/new_app_widget_info.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 54d6de6..f8fd544 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -42,4 +42,4 @@ platform :android do json_key: "google-play-key.json", package_name: "h_mal.appttude.com.monoWeather") end -end +end \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755