mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
- mid commit (broken)
This commit is contained in:
@@ -14,18 +14,11 @@ commands:
|
||||
description: checkout repo and android dependencies
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- run:
|
||||
name: Chmod permissions
|
||||
command: sudo chmod +x ./gradlew
|
||||
- android/restore-gradle-cache
|
||||
- run:
|
||||
name: Download Dependencies
|
||||
command: ./gradlew androidDependencies
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
|
||||
- android/save-gradle-cache
|
||||
run_tests:
|
||||
description: run tests for flavour specified
|
||||
parameters:
|
||||
|
||||
21
.idea/navEditor.xml
generated
21
.idea/navEditor.xml
generated
@@ -79,6 +79,18 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="settings_fragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="12" />
|
||||
<option name="y" value="12" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="worldItemFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@@ -88,6 +100,15 @@
|
||||
<option name="y" value="408" />
|
||||
</Point>
|
||||
</option>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="action_worldItemFragment_to_furtherDetailsFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'androidx.navigation.safeargs'
|
||||
id 'kotlin-parcelize'
|
||||
}
|
||||
android {
|
||||
lintOptions {
|
||||
@@ -11,12 +11,12 @@ android {
|
||||
}
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
applicationId "com.appttude.h_mal.atlas_weather"
|
||||
applicationId "com.appttude.h_mal"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
versionCode 5
|
||||
versionName "3.0"
|
||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||
testInstrumentationRunner "com.appttude.h_mal.application.TestRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
Properties properties = new Properties()
|
||||
@@ -61,6 +61,9 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += [
|
||||
'-Xjvm-default=enable'
|
||||
]
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
@@ -71,7 +74,7 @@ android {
|
||||
versionName "3.0.0"
|
||||
}
|
||||
monoWeather {
|
||||
applicationId "com.appttude.h_mal.atlas_weather.monoWeather"
|
||||
applicationId "com.appttude.h_mal.monoWeather"
|
||||
versionCode 5
|
||||
versionName "3.0.0"
|
||||
}
|
||||
@@ -106,6 +109,7 @@ dependencies {
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
|
||||
implementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
// Unit testing
|
||||
@@ -181,9 +185,9 @@ dependencies {
|
||||
def coroutines_google_ver = "1.1.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines_google_ver"
|
||||
|
||||
/ * Glide */
|
||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||
/ * Picasso */
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
|
||||
/ * screenshot library */
|
||||
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
package com.appttude.h_mal.atlas_weather.testsuite
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.testsuite
|
||||
package com.appttude.h_mal.atlas_weather.testsuite
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ActivityScenario.launch
|
||||
import androidx.test.espresso.Espresso
|
||||
@@ -10,13 +9,11 @@ import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.application.TestAppClass
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.ui.widget
|
||||
package com.appttude.h_mal.atlas_weather.ui.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
|
||||
@@ -4,7 +4,7 @@ package com.appttude.h_mal.atlas_weather.tests
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.appttude.h_mal.atlas_weather.tests
|
||||
|
||||
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.testsuite.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomePageUITest : BaseTest<MainActivity>() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
tools:node="merge">
|
||||
|
||||
<application
|
||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
||||
android:name="com.appttude.h_mal.application.AppClass"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -14,7 +14,7 @@
|
||||
android:theme="@style/AppTheme"
|
||||
tools:node="merge">
|
||||
|
||||
<activity android:name=".atlasWeather.ui.MainActivity"
|
||||
<activity android:name=".ui.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.notification
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
data class NotificationData(
|
||||
val temp: String,
|
||||
val icon: Bitmap
|
||||
)
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.notification
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||
import com.appttude.h_mal.atlas_weather.utils.displayToast
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.LateInitKodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
/**
|
||||
* Created by h_mal on 29/04/2018.
|
||||
* Updated by h_mal on 27/11/2020
|
||||
*/
|
||||
const val NOTIFICATION_CHANNEL_ID = "my_notification_channel_1"
|
||||
|
||||
class NotificationReceiver : BroadcastReceiver() {
|
||||
|
||||
private val kodein = LateInitKodein()
|
||||
private val helper: ServicesHelper by kodein.instance()
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
context.displayToast("Please enable location permissions")
|
||||
return
|
||||
}
|
||||
|
||||
// notification validation
|
||||
}
|
||||
|
||||
private fun pushNotif(context: Context?, weather: FullWeather) {
|
||||
val notificationIntent = Intent(context, MainActivity::class.java)
|
||||
|
||||
val stackBuilder = TaskStackBuilder.create(context).apply {
|
||||
addParentStack(MainActivity::class.java)
|
||||
addNextIntent(notificationIntent)
|
||||
}
|
||||
|
||||
val pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
|
||||
} else {
|
||||
Notification.Builder(context)
|
||||
}
|
||||
|
||||
val notification = builder.setContentTitle("Weather App")
|
||||
.setContentText(weather.current?.main + "°C")
|
||||
.setSmallIcon(R.mipmap.ic_notif) //change icon
|
||||
// .setLargeIcon(Icon.createWithResource(context, getImageResource(forecastItem.getCurrentForecast().getIconURL(), context)))
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent).build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
val notificationManager = context!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(0, notification)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity(){
|
||||
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.view.View
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
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.utils.hide
|
||||
import com.appttude.h_mal.atlas_weather.utils.show
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class BaseFragment: Fragment(){
|
||||
|
||||
// toggle visibility of progress spinner while async operations are taking place
|
||||
fun progressBarStateObserver(progressBar: View) = Observer<Event<Boolean>> {
|
||||
when(it.getContentIfNotHandled()){
|
||||
true -> {
|
||||
progressBar.show()
|
||||
}
|
||||
false -> {
|
||||
progressBar.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display a toast when operation fails
|
||||
fun errorObserver() = Observer<Event<String>> {
|
||||
it.getContentIfNotHandled()?.let { message ->
|
||||
displayToast(message)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.settings.UnitSettingsActivity
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import kotlinx.android.synthetic.atlasWeather.activity_main.*
|
||||
|
||||
|
||||
class MainActivity : BaseActivity(){
|
||||
|
||||
lateinit var navHost: NavHostFragment
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
navHost = supportFragmentManager
|
||||
.findFragmentById(R.id.container) as NavHostFragment
|
||||
val navController = navHost.navController
|
||||
navController.setGraph(R.navigation.main_navigation)
|
||||
|
||||
setupBottomBar(navView, navController)
|
||||
|
||||
}
|
||||
|
||||
private fun setupBottomBar(navView: BottomNavigationView, navController: NavController) {
|
||||
val tabs = setOf(R.id.nav_home, R.id.nav_world)
|
||||
val appBarConfiguration = AppBarConfiguration(tabs)
|
||||
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
navView.setupWithNavController(navController)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
val i = Intent(this, UnitSettingsActivity::class.java)
|
||||
startActivity(i)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.atlasWeather.ui.home.adapter.WeatherRecyclerAdapter
|
||||
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
|
||||
|
||||
class WorldItemFragment : Fragment() {
|
||||
private var param1: WeatherDisplay? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
param1 = WorldItemFragmentArgs.fromBundle(requireArguments()).weatherDisplay
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recyclerAdapter = WeatherRecyclerAdapter {
|
||||
val directions =
|
||||
WorldItemFragmentDirections.actionWorldItemFragmentToFurtherDetailsFragment(it)
|
||||
navigateTo(directions)
|
||||
}
|
||||
|
||||
param1?.let { recyclerAdapter.addCurrent(it) }
|
||||
|
||||
forecast_listview.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = recyclerAdapter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.details
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||
import kotlinx.android.synthetic.main.activity_further_info.*
|
||||
|
||||
|
||||
private const val WEATHER = "param1"
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [FurtherInfoFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class FurtherInfoFragment : Fragment() {
|
||||
private var param1: Forecast? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
param1 = FurtherInfoFragmentArgs.fromBundle(requireArguments()).forecast
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.activity_further_info, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
maxtemp.text = param1?.mainTemp
|
||||
averagetemp.text = param1?.averageTemp
|
||||
minimumtemp.text = param1?.minorTemp
|
||||
windtext.text = param1?.windText
|
||||
preciptext.text = param1?.precipitation
|
||||
humiditytext.text = param1?.humidity
|
||||
uvtext.text = param1?.uvi
|
||||
sunrisetext.text = param1?.sunrise
|
||||
sunsettext.text = param1?.sunset
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.observe
|
||||
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.atlasWeather.ui.BaseFragment
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.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.ApplicationViewModelFactory
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_home.*
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.kodein
|
||||
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class HomeFragment : BaseFragment(), KodeinAware {
|
||||
override val kodein by kodein()
|
||||
private val factory by instance<ApplicationViewModelFactory>()
|
||||
|
||||
private val viewModel by activityViewModels<MainViewModel> { factory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recyclerAdapter = WeatherRecyclerAdapter {
|
||||
val directions =
|
||||
HomeFragmentDirections.actionHomeFragmentToFurtherDetailsFragment(it)
|
||||
navigateTo(directions)
|
||||
}
|
||||
|
||||
forecast_listview.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = recyclerAdapter
|
||||
}
|
||||
|
||||
getPermissionResult(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_PERMISSION_REQUEST){
|
||||
viewModel.fetchData()
|
||||
}
|
||||
|
||||
swipe_refresh.apply {
|
||||
setOnRefreshListener {
|
||||
getPermissionResult(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION_PERMISSION_REQUEST){
|
||||
viewModel.fetchData()
|
||||
}
|
||||
isRefreshing = true
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.weatherLiveData.observe(viewLifecycleOwner) {
|
||||
recyclerAdapter.addCurrent(it)
|
||||
}
|
||||
|
||||
viewModel.operationState.observe(viewLifecycleOwner, progressBarStateObserver(progressBar))
|
||||
viewModel.operationError.observe(viewLifecycleOwner, errorObserver())
|
||||
|
||||
viewModel.operationState.observe(viewLifecycleOwner){
|
||||
swipe_refresh.isRefreshing = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == LOCATION_PERMISSION_REQUEST) {
|
||||
if (grantResults.isNotEmpty()
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
viewModel.fetchData()
|
||||
displayToast("Permission granted")
|
||||
} else {
|
||||
displayToast("Permission denied")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class EmptyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.utils.loadImage
|
||||
|
||||
class ViewHolderCurrent(listItemView: View) : RecyclerView.ViewHolder(listItemView) {
|
||||
|
||||
var locationTV: TextView = listItemView.findViewById(R.id.location_main_4)
|
||||
var conditionTV: TextView = listItemView.findViewById(R.id.condition_main_4)
|
||||
var weatherIV: ImageView = listItemView.findViewById(R.id.icon_main_4)
|
||||
var avgTempTV: TextView = listItemView.findViewById(R.id.temp_main_4)
|
||||
var tempUnit: TextView = listItemView.findViewById(R.id.temp_unit_4)
|
||||
|
||||
fun bindData(weather: WeatherDisplay?){
|
||||
locationTV.text = weather?.location
|
||||
conditionTV.text = weather?.description
|
||||
weatherIV.loadImage(weather?.iconURL)
|
||||
avgTempTV.text = weather?.averageTemp?.toInt().toString()
|
||||
tempUnit.text = weather?.unit
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
|
||||
import com.appttude.h_mal.atlas_weather.utils.loadImage
|
||||
|
||||
class ViewHolderForecast(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var dateTV: TextView = itemView.findViewById(R.id.list_date)
|
||||
var dayTV: TextView = itemView.findViewById(R.id.list_day)
|
||||
var conditionTV: TextView = itemView.findViewById(R.id.list_condition)
|
||||
var weatherIV: ImageView = itemView.findViewById(R.id.list_icon)
|
||||
var mainTempTV: TextView = itemView.findViewById(R.id.list_main_temp)
|
||||
var minorTempTV: TextView = itemView.findViewById(R.id.list_minor_temp)
|
||||
|
||||
fun bindView(forecast: Forecast?) {
|
||||
dateTV.text = forecast?.date
|
||||
dayTV.text = forecast?.day
|
||||
conditionTV.text = forecast?.condition
|
||||
weatherIV.loadImage(forecast?.weatherIcon)
|
||||
mainTempTV.text = forecast?.mainTemp
|
||||
minorTempTV.text = forecast?.minorTemp
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||
|
||||
internal class ViewHolderFurtherDetails(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
var windSpeed: TextView = itemView.findViewById(R.id.windspeed)
|
||||
var windDirection: TextView = itemView.findViewById(R.id.winddirection)
|
||||
var precipitation: TextView = itemView.findViewById(R.id.precip_)
|
||||
var humidity: TextView = itemView.findViewById(R.id.humidity_)
|
||||
var clouds: TextView = itemView.findViewById(R.id.clouds_)
|
||||
|
||||
fun bindData(weather: WeatherDisplay?){
|
||||
windSpeed.text = weather?.windSpeed
|
||||
windDirection.text = weather?.windDirection
|
||||
precipitation.text = weather?.precipitation
|
||||
humidity.text = weather?.humidity
|
||||
clouds.text = weather?.clouds
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.home.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
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.generateView
|
||||
|
||||
class WeatherRecyclerAdapter(
|
||||
val itemClick: (Forecast) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var weather: WeatherDisplay? = null
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun addCurrent(current: WeatherDisplay){
|
||||
weather = current
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (getDataType(viewType)){
|
||||
is ViewType.Empty -> {
|
||||
val emptyViewHolder = View(parent.context)
|
||||
EmptyViewHolder(emptyViewHolder)
|
||||
}
|
||||
is ViewType.Current -> {
|
||||
val viewCurrent = parent.generateView(R.layout.list_item_current)
|
||||
ViewHolderCurrent(viewCurrent)
|
||||
}
|
||||
is ViewType.Forecast -> {
|
||||
val viewForecast = parent.generateView(R.layout.list_item_forecast)
|
||||
ViewHolderForecast(viewForecast)
|
||||
}
|
||||
is ViewType.Further -> {
|
||||
val viewFurther = parent.generateView(R.layout.list_item_further)
|
||||
ViewHolderFurtherDetails(viewFurther)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewType{
|
||||
object Empty : ViewType()
|
||||
object Current : ViewType()
|
||||
object Forecast : ViewType()
|
||||
object Further : ViewType()
|
||||
}
|
||||
|
||||
private fun getDataType(type: Int): ViewType {
|
||||
return when (type){
|
||||
0 -> ViewType.Empty
|
||||
1 -> ViewType.Current
|
||||
2 -> ViewType.Forecast
|
||||
3 -> ViewType.Further
|
||||
else -> ViewType.Empty
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (weather == null) return 0
|
||||
|
||||
return when(position){
|
||||
0 -> 1
|
||||
in 1 until itemCount -2 -> 2
|
||||
itemCount - 1 -> 3
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (getDataType(getItemViewType(position))){
|
||||
is ViewType.Empty -> {
|
||||
holder as EmptyViewHolder
|
||||
}
|
||||
is ViewType.Current -> {
|
||||
val viewHolderCurrent = holder as ViewHolderCurrent
|
||||
viewHolderCurrent.bindData(weather)
|
||||
}
|
||||
is ViewType.Forecast -> {
|
||||
val viewHolderForecast = holder as ViewHolderForecast
|
||||
|
||||
weather?.forecast?.get(position - 1)?.let { i ->
|
||||
viewHolderForecast.bindView(i)
|
||||
viewHolderForecast.itemView.setOnClickListener {
|
||||
itemClick(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ViewType.Further -> {
|
||||
val viewHolderCurrent = holder as ViewHolderFurtherDetails
|
||||
viewHolderCurrent.bindData(weather)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
if (weather == null) return 0
|
||||
return 2 + (weather?.forecast?.size?: 0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.settings
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceActivity
|
||||
import android.preference.PreferenceFragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.notification.NotificationReceiver
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.widget.NewAppWidget
|
||||
import java.util.*
|
||||
|
||||
|
||||
class UnitSettingsActivity : PreferenceActivity() {
|
||||
private var prefListener: OnSharedPreferenceChangeListener? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
PreferenceManager.setDefaultValues(this, R.xml.prefs, false)
|
||||
fragmentManager.beginTransaction().replace(android.R.id.content, MyPreferenceFragment()).commit()
|
||||
|
||||
//listener on changed sort order preference:
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
prefListener = OnSharedPreferenceChangeListener { _, key ->
|
||||
if (key == "temp_units") {
|
||||
val intent = Intent(baseContext, NewAppWidget::class.java)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
val ids = AppWidgetManager.getInstance(application).getAppWidgetIds(ComponentName(application, NewAppWidget::class.java))
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
if (key == "notif_boolean") {
|
||||
setupNotificationBroadcaster(baseContext)
|
||||
}
|
||||
|
||||
if (key == "widget_black_background"){
|
||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
|
||||
val widgetManager = AppWidgetManager.getInstance(this)
|
||||
val ids = widgetManager.getAppWidgetIds(ComponentName(this, NewAppWidget::class.java))
|
||||
AppWidgetManager.getInstance(this).notifyAppWidgetViewDataChanged(ids, R.id.whole_widget_view)
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
prefs.registerOnSharedPreferenceChangeListener(prefListener)
|
||||
}
|
||||
|
||||
fun setupNotificationBroadcaster(context: Context) {
|
||||
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
val notificationIntent = Intent(context, NotificationReceiver::class.java)
|
||||
val broadcast = PendingIntent.getBroadcast(context, 100, notificationIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.set(Calendar.HOUR_OF_DAY, 6)
|
||||
cal.set(Calendar.MINUTE, 8)
|
||||
cal.set(Calendar.SECOND, 5)
|
||||
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, cal.timeInMillis, AlarmManager.INTERVAL_DAY, broadcast)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
|
||||
// Log.i(TAG, "onSharedPreferenceChanged: " + s);
|
||||
// if (s == "temp_units"){
|
||||
// Intent intent = new Intent(getBaseContext(), NewAppWidget.class);
|
||||
// intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
|
||||
//
|
||||
// int[] ids = AppWidgetManager.getInstance(getApplication()).getAppWidgetIds(new ComponentName(getApplication(), NewAppWidget.class));
|
||||
// intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
|
||||
// sendBroadcast(intent);
|
||||
// }
|
||||
// }
|
||||
class MyPreferenceFragment : PreferenceFragment() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.prefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.world
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.observe
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.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.ApplicationViewModelFactory
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||
import kotlinx.android.synthetic.main.activity_add_forecast.*
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
|
||||
class AddLocationFragment : BaseFragment(), KodeinAware {
|
||||
override val kodein by kodein()
|
||||
private val factory by instance<ApplicationViewModelFactory>()
|
||||
|
||||
private val viewModel by viewModels<WorldViewModel> { factory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.activity_add_forecast, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
submit.setOnClickListener {
|
||||
val locationName = location_name_tv.text?.trim()?.toString()
|
||||
if (locationName.isNullOrBlank()){
|
||||
submit.error = "Location cannot be blank"
|
||||
return@setOnClickListener
|
||||
}
|
||||
viewModel.fetchDataForSingleLocation(locationName)
|
||||
}
|
||||
|
||||
viewModel.operationState.observe(viewLifecycleOwner, progressBarStateObserver(progressBar))
|
||||
viewModel.operationError.observe(viewLifecycleOwner, errorObserver())
|
||||
|
||||
viewModel.operationComplete.observe(viewLifecycleOwner) {
|
||||
it?.getContentIfNotHandled()?.let {message ->
|
||||
displayToast(message)
|
||||
}
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.world
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.observe
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.world.WorldRecyclerAdapter
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.BaseFragment
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.world.WorldFragmentDirections
|
||||
import com.appttude.h_mal.atlas_weather.utils.navigateTo
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_add_location.*
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.x.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class WorldFragment : BaseFragment(), KodeinAware {
|
||||
override val kodein by kodein()
|
||||
private val factory by instance<ApplicationViewModelFactory>()
|
||||
|
||||
val viewModel by viewModels<WorldViewModel> { factory }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_add_location, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val recyclerAdapter = WorldRecyclerAdapter{
|
||||
val direction =
|
||||
WorldFragmentDirections.actionWorldFragmentToWorldItemFragment(it)
|
||||
navigateTo(direction)
|
||||
}
|
||||
|
||||
world_recycler.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = recyclerAdapter
|
||||
}
|
||||
|
||||
viewModel.weatherLiveData.observe(viewLifecycleOwner) {
|
||||
recyclerAdapter.addCurrent(it)
|
||||
}
|
||||
|
||||
floatingActionButton.setOnClickListener{
|
||||
navigateTo(R.id.action_worldFragment_to_addLocationFragment)
|
||||
}
|
||||
|
||||
viewModel.operationState.observe(viewLifecycleOwner, progressBarStateObserver(progressBar2))
|
||||
viewModel.operationError.observe(viewLifecycleOwner, errorObserver())
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
viewModel.fetchAllLocations()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.ui.world
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.utils.generateView
|
||||
import com.appttude.h_mal.atlas_weather.utils.loadImage
|
||||
|
||||
class WorldRecyclerAdapter(
|
||||
val itemClick: (WeatherDisplay) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
var weather: MutableList<WeatherDisplay> = mutableListOf()
|
||||
|
||||
fun addCurrent(current: List<WeatherDisplay>){
|
||||
weather.clear()
|
||||
weather.addAll(current)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (getDataType(viewType)){
|
||||
is ViewType.Empty -> {
|
||||
val emptyViewHolder = View(parent.context)
|
||||
EmptyViewHolder(emptyViewHolder)
|
||||
}
|
||||
is ViewType.Current -> {
|
||||
val viewCurrent = parent.generateView(R.layout.db_list_item)
|
||||
WorldHolderCurrent(viewCurrent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ViewType{
|
||||
object Empty : ViewType()
|
||||
object Current : ViewType()
|
||||
}
|
||||
|
||||
private fun getDataType(type: Int): ViewType{
|
||||
return when (type){
|
||||
0 -> ViewType.Empty
|
||||
1 -> ViewType.Current
|
||||
else -> ViewType.Empty
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (weather.isEmpty()) 0 else 1
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (getDataType(getItemViewType(position))){
|
||||
is ViewType.Empty -> {
|
||||
holder as EmptyViewHolder
|
||||
|
||||
}
|
||||
is ViewType.Current -> {
|
||||
val viewHolderCurrent = holder as WorldHolderCurrent
|
||||
viewHolderCurrent.bindData(weather[position])
|
||||
viewHolderCurrent.itemView.setOnClickListener {
|
||||
itemClick.invoke(weather[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (weather.size == 0) 1 else weather.size
|
||||
}
|
||||
|
||||
internal class WorldHolderCurrent(listItemView: View) : RecyclerView.ViewHolder(listItemView) {
|
||||
|
||||
var locationTV: TextView = listItemView.findViewById(R.id.db_location)
|
||||
var conditionTV: TextView = listItemView.findViewById(R.id.db_condition)
|
||||
var weatherIV: ImageView = listItemView.findViewById(R.id.db_icon)
|
||||
var avgTempTV: TextView = listItemView.findViewById(R.id.db_main_temp)
|
||||
// var tempUnit: TextView = listItemView.findViewById(R.id.db_minor_temp)
|
||||
|
||||
fun bindData(weather: WeatherDisplay?){
|
||||
locationTV.text = weather?.location
|
||||
conditionTV.text = weather?.description
|
||||
weatherIV.loadImage(weather?.iconURL)
|
||||
avgTempTV.text = weather?.forecast?.get(0)?.mainTemp
|
||||
// tempUnit.text = weather?.unit
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal class EmptyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.widget
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.TaskStackBuilder
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.LayoutRes
|
||||
|
||||
abstract class BaseWidgetClass : AppWidgetProvider(){
|
||||
|
||||
fun createRemoteView(context: Context, @LayoutRes id: Int): RemoteViews {
|
||||
return RemoteViews(context.packageName, id)
|
||||
}
|
||||
|
||||
fun AppWidgetProvider.createUpdatePendingIntent(context: Context, appWidgetId: Int): PendingIntent? {
|
||||
val seconds = (System.currentTimeMillis() / 1000L).toInt()
|
||||
val intentUpdate = Intent(context, this::class.java)
|
||||
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
val idArray = intArrayOf(appWidgetId)
|
||||
intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray)
|
||||
|
||||
return PendingIntent.getBroadcast(
|
||||
context, seconds, intentUpdate,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
fun <T: Activity> createClickingPendingIntent(context: Context, activityClass: Class<T>): PendingIntent {
|
||||
val clickIntentTemplate = Intent(context, activityClass)
|
||||
|
||||
return TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(clickIntentTemplate)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.LateInitKodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
|
||||
class MyWidgetRemoteViewsFactory(
|
||||
private val context: Context,
|
||||
val intent: Intent
|
||||
) : RemoteViewsFactory{
|
||||
private val TAG = "MyWidgetRemoteViewsFactory"
|
||||
|
||||
private val kodein = LateInitKodein()
|
||||
private val helper : ServicesHelper by kodein.instance()
|
||||
|
||||
private var appWidgetId: Int? = 0
|
||||
private var list: List<InnerWidgetData>? = null
|
||||
|
||||
init {
|
||||
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate() {}
|
||||
override fun onDataSetChanged() {
|
||||
runBlocking {
|
||||
list = helper.getWidgetInnerWeather()
|
||||
}
|
||||
}
|
||||
override fun onDestroy() {}
|
||||
|
||||
override fun getCount(): Int = list?.size ?: 5
|
||||
|
||||
override fun getViewAt(i: Int): RemoteViews {
|
||||
val rv = RemoteViews(context.packageName, R.layout.widget_item)
|
||||
|
||||
if (list.isNullOrEmpty()) return rv
|
||||
|
||||
|
||||
list?.get(i)?.let {
|
||||
rv.setTextViewText(R.id.widget_item_day, it.date)
|
||||
rv.setImageViewBitmap(R.id.widget_item_image, it.icon)
|
||||
rv.setTextViewText(R.id.widget_item_temp_high, it.highTemp)
|
||||
rv.setOnClickFillInIntent(R.id.widget_item_layout, intent)
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
override fun getLoadingView(): RemoteViews {
|
||||
return RemoteViews(context.packageName, R.layout.widget_item_loading)
|
||||
}
|
||||
|
||||
override fun getViewTypeCount(): Int = 1
|
||||
|
||||
|
||||
override fun getItemId(i: Int): Long = i.toLong()
|
||||
|
||||
|
||||
override fun hasStableIds(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.widget
|
||||
|
||||
import android.Manifest
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.app.ActivityCompat
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.atlasWeather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.LateInitKodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
/**
|
||||
* Implementation of App Widget functionality.
|
||||
*/
|
||||
private val TAG = NewAppWidget::class.java.simpleName
|
||||
class NewAppWidget : BaseWidgetClass() {
|
||||
|
||||
private val kodein = LateInitKodein()
|
||||
private val helper : ServicesHelper by kodein.instance()
|
||||
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||
// There may be multiple widgets active, so update all of them
|
||||
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
return
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val results = helper.fetchData()
|
||||
if (results) return@launch
|
||||
val weatherWidgetCurrent = helper.getWidgetWeather()
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
val updatePendingIntent = createUpdatePendingIntent(context, appWidgetId)
|
||||
val views = createRemoteView(context, R.layout.new_app_widget)
|
||||
bindView(context, appWidgetId, views, updatePendingIntent, weatherWidgetCurrent)
|
||||
}
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnabled(context: Context) {
|
||||
try {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val thisAppWidget = ComponentName(context.packageName, NewAppWidget::class.java.name)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
|
||||
onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_listview)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "onEnabled: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
// Enter relevant functionality for when the last widget is disabled
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action ==
|
||||
AppWidgetManager.ACTION_APPWIDGET_UPDATE) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val thisAppWidget = ComponentName(context.packageName, NewAppWidget::class.java.name)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_listview)
|
||||
}
|
||||
super.onReceive(context, intent)
|
||||
}
|
||||
|
||||
private fun createForecastListIntent(
|
||||
context: Context,
|
||||
appWidgetId: Int
|
||||
): Intent {
|
||||
return Intent(context, WidgetRemoteViewsService::class.java).apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindView(
|
||||
context: Context,
|
||||
appWidgetId: Int,
|
||||
views: RemoteViews,
|
||||
updatePendingIntent: PendingIntent?,
|
||||
weather: WidgetData?){
|
||||
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
|
||||
views.setInt(R.id.whole_widget_view, "setBackgroundColor", helper.getWidgetBackground())
|
||||
|
||||
weather?.let {
|
||||
|
||||
val intent = createForecastListIntent(
|
||||
context,
|
||||
appWidgetId
|
||||
)
|
||||
|
||||
views.setRemoteAdapter(R.id.widget_listview, intent)
|
||||
views.setTextViewText(R.id.widget_main_temp, it.currentTemp)
|
||||
views.setTextViewText(R.id.widget_feel_temp, "°C")
|
||||
views.setTextViewText(R.id.widget_current_location, it.location)
|
||||
views.setImageViewResource(R.id.location_icon, R.drawable.location_flag)
|
||||
// views.setImageViewBitmap(R.id.widget_current_icon, it.icon)
|
||||
|
||||
val clickPendingIntentTemplate = createClickingPendingIntent(context, MainActivity::class.java)
|
||||
views.setPendingIntentTemplate(R.id.widget_listview, clickPendingIntentTemplate)
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widget_current_icon, updatePendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.widget_current_location, updatePendingIntent)
|
||||
|
||||
// Instruct the widget manager to update the widget
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "onPostExecute: weather is empty")
|
||||
views.setTextViewText(R.id.widget_current_location, "Refresh")
|
||||
views.setImageViewResource(R.id.widget_current_icon, R.drawable.widget_error_icon)
|
||||
views.setImageViewResource(R.id.location_icon, R.drawable.refreshing)
|
||||
views.setOnClickPendingIntent(R.id.widget_current_icon, updatePendingIntent)
|
||||
views.setOnClickPendingIntent(R.id.widget_current_location, updatePendingIntent)
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.widget
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
|
||||
class WidgetRemoteViewsService : RemoteViewsService() {
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||
return MyWidgetRemoteViewsFactory(applicationContext, intent)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.atlasWeather.widget
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
fun <T: Activity> createClickingPendingIntent(context: Context, activityClass: Class<T>): PendingIntent {
|
||||
val clickIntentTemplate = Intent(context, activityClass)
|
||||
|
||||
return TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(clickIntentTemplate)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:configure="com.appttude.h_mal.atlas_weather.monoWeather.ui.widget.WidgetLocationPermissionActivity"
|
||||
android:configure="com.appttude.h_mal.ui.widget.WidgetLocationPermissionActivity"
|
||||
android:initialKeyguardLayout="@layout/weather_app_widget"
|
||||
android:initialLayout="@layout/weather_app_widget"
|
||||
android:minHeight="110.0dp"
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.appttude.h_mal.atlas_weather">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.appttude.h_mal">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SET_ALARM" />
|
||||
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
||||
|
||||
<application android:networkSecurityConfig="@xml/network_security_config" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location"
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import androidx.room.RoomDatabase
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
import com.google.gson.Gson
|
||||
import org.kodein.di.Kodein
|
||||
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
|
||||
|
||||
const val LOCATION_PERMISSION_REQUEST = 505
|
||||
|
||||
class AppClass : BaseAppClass() {
|
||||
|
||||
override fun createNetworkModule(): WeatherApi {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor
|
||||
) as WeatherApi
|
||||
}
|
||||
|
||||
override fun createLocationModule() = LocationProviderImpl(this)
|
||||
|
||||
override fun createRoomDatabase(): AppDatabase = AppDatabase(this)
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.application
|
||||
|
||||
import android.app.Application
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
import com.google.gson.Gson
|
||||
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
|
||||
|
||||
abstract class BaseAppClass : Application(), KodeinAware {
|
||||
|
||||
// Kodein creation of modules to be retrieve within the app
|
||||
override val kodein = Kodein.lazy {
|
||||
import(androidXModule(this@BaseAppClass))
|
||||
|
||||
bind() from singleton { createNetworkModule() }
|
||||
bind() from singleton { createLocationModule() }
|
||||
|
||||
bind() from singleton { Gson() }
|
||||
bind() from singleton { createRoomDatabase() }
|
||||
bind() from singleton { PreferenceProvider(instance()) }
|
||||
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||
bind() from singleton { SettingsRepositoryImpl(instance()) }
|
||||
bind() from singleton { ServicesHelper(instance(), instance(), instance()) }
|
||||
bind() from provider { ApplicationViewModelFactory(instance(), instance()) }
|
||||
}
|
||||
|
||||
abstract fun createNetworkModule(): WeatherApi
|
||||
abstract fun createLocationModule(): LocationProvider
|
||||
abstract fun createRoomDatabase(): AppDatabase
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.location
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.appttude.h_mal.atlas_weather.BuildConfig
|
||||
import com.appttude.h_mal.atlas_weather.utils.createSuspend
|
||||
import com.tomtom.online.sdk.search.OnlineSearchApi
|
||||
import com.tomtom.online.sdk.search.data.common.Address
|
||||
import com.tomtom.online.sdk.search.data.reversegeocoder.ReverseGeocoderSearchQueryBuilder
|
||||
|
||||
abstract class LocationHelper(
|
||||
context: Context
|
||||
) {
|
||||
|
||||
private val key = BuildConfig.ParamTwo
|
||||
private val searchApi = OnlineSearchApi.create(context, key)
|
||||
|
||||
suspend fun getAddressFromLatLong(
|
||||
lat: Double, long: Double
|
||||
): Address? {
|
||||
return createSuspend {
|
||||
val revGeoQuery =
|
||||
ReverseGeocoderSearchQueryBuilder(lat, long).build()
|
||||
|
||||
val resultSingle =
|
||||
searchApi.reverseGeocoding(revGeoQuery)
|
||||
|
||||
resultSingle.blockingGet()?.addresses?.get(0)?.address
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected fun Location.getLatLonPair(): Pair<Double, Double> = Pair(latitude, longitude)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.location
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||
|
||||
interface LocationProvider {
|
||||
suspend fun getCurrentLatLong(): Pair<Double, Double>
|
||||
fun getLatLongFromLocationName(location: String): Pair<Double, Double>
|
||||
suspend fun getLocationNameFromLatLong(
|
||||
lat: Double,
|
||||
long: Double,
|
||||
type: LocationType = LocationType.Town
|
||||
): String
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.location
|
||||
|
||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.HandlerThread
|
||||
import androidx.annotation.RequiresPermission
|
||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
import com.google.android.gms.location.LocationRequest
|
||||
import com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY
|
||||
import com.google.android.gms.location.LocationRequest.PRIORITY_LOW_POWER
|
||||
import com.google.android.gms.location.LocationResult
|
||||
import com.google.android.gms.tasks.CancellationToken
|
||||
import com.google.android.gms.tasks.OnTokenCanceledListener
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
class LocationProviderImpl(
|
||||
val applicationContext: Context
|
||||
) : LocationProvider, LocationHelper(applicationContext) {
|
||||
private var locationManager =
|
||||
applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
|
||||
private val client = FusedLocationProviderClient(applicationContext)
|
||||
private val geoCoder: Geocoder by lazy { Geocoder(applicationContext, Locale.getDefault()) }
|
||||
|
||||
@RequiresPermission(value = ACCESS_COARSE_LOCATION)
|
||||
override suspend fun getCurrentLatLong(): Pair<Double, Double> {
|
||||
val location = client.lastLocation.await() ?: getAFreshLocation()
|
||||
return location?.getLatLonPair() ?: throw IOException("Unable to get location")
|
||||
}
|
||||
|
||||
override fun getLatLongFromLocationName(location: String): Pair<Double, Double> {
|
||||
val locations = geoCoder.getFromLocationName(location, 1)
|
||||
|
||||
locations?.takeIf { it.isNotEmpty() }?.get(0)?.let {
|
||||
return Pair(it.latitude, it.longitude)
|
||||
}
|
||||
throw IOException("No location found")
|
||||
}
|
||||
|
||||
override suspend fun getLocationNameFromLatLong(
|
||||
lat: Double, long: Double, type: LocationType
|
||||
): String {
|
||||
val address = getAddressFromLatLong(lat, long) ?: return "$lat $long"
|
||||
|
||||
return when (type) {
|
||||
LocationType.Town -> {
|
||||
val location = address
|
||||
.municipalitySubdivision
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?: address.municipality
|
||||
location ?: throw IOException("No location municipalitySubdivision or municipality")
|
||||
}
|
||||
LocationType.City -> {
|
||||
address.municipality ?: throw IOException("No location municipality")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun getAFreshLocation(): Location? {
|
||||
return client.getCurrentLocation(PRIORITY_LOW_POWER, object : CancellationToken() {
|
||||
override fun isCancellationRequested(): Boolean = false
|
||||
override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken = this
|
||||
}).await()
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun requestFreshLocation(): Location? {
|
||||
val handlerThread = HandlerThread("MyHandlerThread")
|
||||
handlerThread.start()
|
||||
// Now get the Looper from the HandlerThread
|
||||
// NOTE: This call will block until the HandlerThread gets control and initializes its Looper
|
||||
val looper = handlerThread.looper
|
||||
|
||||
return suspendCoroutine { cont ->
|
||||
val callback = object : LocationCallback() {
|
||||
override fun onLocationResult(p0: LocationResult?) {
|
||||
client.removeLocationUpdates(this)
|
||||
cont.resume(p0?.lastLocation)
|
||||
}
|
||||
}
|
||||
|
||||
with(locationManager!!) {
|
||||
when {
|
||||
isProviderEnabled(LocationManager.GPS_PROVIDER) -> {
|
||||
client.requestLocationUpdates(createLocationRequest(PRIORITY_HIGH_ACCURACY), callback, looper)
|
||||
}
|
||||
isProviderEnabled(LocationManager.NETWORK_PROVIDER) -> {
|
||||
client.requestLocationUpdates(createLocationRequest(PRIORITY_LOW_POWER), callback, looper)
|
||||
}
|
||||
else -> {
|
||||
cont.resume(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLocationRequest(priority: Int) = LocationRequest.create()
|
||||
.setPriority(priority)
|
||||
.setNumUpdates(1)
|
||||
.setExpirationDuration(1000)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
interface Api
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.buildOkHttpClient
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.createRetrofit
|
||||
import okhttp3.Interceptor
|
||||
|
||||
open class BaseNetworkModule {
|
||||
// Declare the method we want/can change (no annotations)
|
||||
open fun baseUrl() = "/"
|
||||
|
||||
inline fun <reified T: Api> invoke(
|
||||
vararg interceptors: Interceptor
|
||||
): Api {
|
||||
|
||||
val okHttpClient = buildOkHttpClient(*interceptors)
|
||||
|
||||
return createRetrofit(
|
||||
baseUrl(),
|
||||
okHttpClient,
|
||||
T::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
class NetworkModule : BaseNetworkModule() {
|
||||
override fun baseUrl(): String = "https://api.openweathermap.org/data/2.5/"
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
abstract class ResponseUnwrap {
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun <T : Any> responseUnwrap(
|
||||
call: suspend () -> Response<T>
|
||||
): T {
|
||||
|
||||
val response = call.invoke()
|
||||
if (response.isSuccessful) {
|
||||
return response.body()!!
|
||||
} else {
|
||||
val error = response.errorBody()?.string()
|
||||
|
||||
val errorMessage = error?.let {
|
||||
try {
|
||||
JSONObject(it).getString("message")
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
} ?: "Error Code: ${response.code()}"
|
||||
|
||||
throw IOException(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
interface WeatherApi: Api {
|
||||
|
||||
@GET("onecall?")
|
||||
suspend fun getFromApi(
|
||||
@Query("lat") query: String,
|
||||
@Query("lon") lon: String,
|
||||
@Query("exclude") exclude: String = "minutely",
|
||||
@Query("units") units: String = "metric"
|
||||
): Response<WeatherResponse>
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.interceptors
|
||||
|
||||
import android.content.Context
|
||||
import com.appttude.h_mal.atlas_weather.utils.isInternetAvailable
|
||||
import okhttp3.Interceptor
|
||||
import java.io.IOException
|
||||
|
||||
class NetworkConnectionInterceptor(
|
||||
context: Context
|
||||
) : NetworkInterceptor {
|
||||
|
||||
private val applicationContext = context.applicationContext
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
|
||||
if (!isInternetAvailable(applicationContext)){
|
||||
throw IOException("Make sure you have an active data connection")
|
||||
}
|
||||
return chain.proceed(chain.request())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.interceptors
|
||||
|
||||
import okhttp3.Interceptor
|
||||
|
||||
interface NetworkInterceptor : Interceptor
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.interceptors
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.BuildConfig
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Interceptor used to add default query parameters to api calls
|
||||
*/
|
||||
class QueryParamsInterceptor : Interceptor{
|
||||
|
||||
val id = BuildConfig.ParamOne
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val original = chain.request()
|
||||
|
||||
val url = original.url.newBuilder()
|
||||
.addQueryParameter("appid", id)
|
||||
.build()
|
||||
|
||||
// Request customization: add request headers
|
||||
val requestBuilder = original.newBuilder().url(url)
|
||||
|
||||
val request: Request = requestBuilder.build()
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
fun buildOkHttpClient(
|
||||
vararg interceptor: Interceptor,
|
||||
timeoutSeconds: Long = 30L
|
||||
): OkHttpClient {
|
||||
|
||||
val builder = OkHttpClient.Builder()
|
||||
|
||||
interceptor.forEach {
|
||||
if (it is NetworkInterceptor) {
|
||||
builder.addNetworkInterceptor(it)
|
||||
} else {
|
||||
builder.addInterceptor(it)
|
||||
}
|
||||
}
|
||||
|
||||
builder.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.readTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun <T> createRetrofit(
|
||||
baseUrl: String,
|
||||
okHttpClient: OkHttpClient,
|
||||
service: Class<T>
|
||||
): T {
|
||||
return Retrofit.Builder()
|
||||
.client(okHttpClient)
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(service)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Current(
|
||||
|
||||
@field:SerializedName("sunrise")
|
||||
val sunrise: Int? = null,
|
||||
|
||||
@field:SerializedName("temp")
|
||||
val temp: Double? = null,
|
||||
|
||||
@field:SerializedName("visibility")
|
||||
val visibility: Int? = null,
|
||||
|
||||
@field:SerializedName("uvi")
|
||||
val uvi: Double? = null,
|
||||
|
||||
@field:SerializedName("pressure")
|
||||
val pressure: Int? = null,
|
||||
|
||||
@field:SerializedName("clouds")
|
||||
val clouds: Int? = null,
|
||||
|
||||
@field:SerializedName("feels_like")
|
||||
val feelsLike: Double? = null,
|
||||
|
||||
@field:SerializedName("dt")
|
||||
val dt: Int? = null,
|
||||
|
||||
@field:SerializedName("wind_deg")
|
||||
val windDeg: Int? = null,
|
||||
|
||||
@field:SerializedName("dew_point")
|
||||
val dewPoint: Double? = null,
|
||||
|
||||
@field:SerializedName("sunset")
|
||||
val sunset: Int? = null,
|
||||
|
||||
@field:SerializedName("weather")
|
||||
val weather: List<WeatherItem?>? = null,
|
||||
|
||||
@field:SerializedName("humidity")
|
||||
val humidity: Int? = null,
|
||||
|
||||
@field:SerializedName("wind_speed")
|
||||
val windSpeed: Double? = null
|
||||
)
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DailyItem(
|
||||
|
||||
@field:SerializedName("sunrise")
|
||||
val sunrise: Int? = null,
|
||||
|
||||
@field:SerializedName("temp")
|
||||
val temp: Temp? = null,
|
||||
|
||||
@field:SerializedName("uvi")
|
||||
val uvi: Double? = null,
|
||||
|
||||
@field:SerializedName("pressure")
|
||||
val pressure: Int? = null,
|
||||
|
||||
@field:SerializedName("clouds")
|
||||
val clouds: Int? = null,
|
||||
|
||||
@field:SerializedName("feels_like")
|
||||
val feelsLike: FeelsLike? = null,
|
||||
|
||||
@field:SerializedName("dt")
|
||||
val dt: Int? = null,
|
||||
|
||||
@field:SerializedName("pop")
|
||||
val pop: Double? = null,
|
||||
|
||||
@field:SerializedName("wind_deg")
|
||||
val windDeg: Int? = null,
|
||||
|
||||
@field:SerializedName("dew_point")
|
||||
val dewPoint: Double? = null,
|
||||
|
||||
@field:SerializedName("sunset")
|
||||
val sunset: Int? = null,
|
||||
|
||||
@field:SerializedName("weather")
|
||||
val weather: List<WeatherItem?>? = null,
|
||||
|
||||
@field:SerializedName("humidity")
|
||||
val humidity: Int? = null,
|
||||
|
||||
@field:SerializedName("wind_speed")
|
||||
val windSpeed: Double? = null,
|
||||
|
||||
@field:SerializedName("rain")
|
||||
val rain: Double? = null
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FeelsLike(
|
||||
|
||||
@field:SerializedName("eve")
|
||||
val eve: Double? = null,
|
||||
|
||||
@field:SerializedName("night")
|
||||
val night: Double? = null,
|
||||
|
||||
@field:SerializedName("day")
|
||||
val day: Double? = null,
|
||||
|
||||
@field:SerializedName("morn")
|
||||
val morn: Double? = null
|
||||
)
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Hour(
|
||||
|
||||
@field:SerializedName("sunrise")
|
||||
val sunrise: Int? = null,
|
||||
|
||||
@field:SerializedName("temp")
|
||||
val temp: Double? = null,
|
||||
|
||||
@field:SerializedName("visibility")
|
||||
val visibility: Int? = null,
|
||||
|
||||
@field:SerializedName("uvi")
|
||||
val uvi: Double? = null,
|
||||
|
||||
@field:SerializedName("pressure")
|
||||
val pressure: Int? = null,
|
||||
|
||||
@field:SerializedName("clouds")
|
||||
val clouds: Int? = null,
|
||||
|
||||
@field:SerializedName("feels_like")
|
||||
val feelsLike: Double? = null,
|
||||
|
||||
@field:SerializedName("dt")
|
||||
val dt: Int? = null,
|
||||
|
||||
@field:SerializedName("wind_deg")
|
||||
val windDeg: Int? = null,
|
||||
|
||||
@field:SerializedName("dew_point")
|
||||
val dewPoint: Double? = null,
|
||||
|
||||
@field:SerializedName("sunset")
|
||||
val sunset: Int? = null,
|
||||
|
||||
@field:SerializedName("weather")
|
||||
val weather: List<WeatherItem?>? = null,
|
||||
|
||||
@field:SerializedName("humidity")
|
||||
val humidity: Int? = null,
|
||||
|
||||
@field:SerializedName("wind_speed")
|
||||
val windSpeed: Double? = null
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Response(
|
||||
|
||||
@field:SerializedName("current")
|
||||
val current: Current? = null,
|
||||
|
||||
@field:SerializedName("timezone")
|
||||
val timezone: String? = null,
|
||||
|
||||
@field:SerializedName("timezone_offset")
|
||||
val timezoneOffset: Int? = null,
|
||||
|
||||
@field:SerializedName("daily")
|
||||
val daily: List<DailyItem?>? = null,
|
||||
|
||||
@field:SerializedName("lon")
|
||||
val lon: Double? = null,
|
||||
|
||||
@field:SerializedName("lat")
|
||||
val lat: Double? = null
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Temp(
|
||||
|
||||
@field:SerializedName("min")
|
||||
val min: Double? = null,
|
||||
|
||||
@field:SerializedName("max")
|
||||
val max: Double? = null,
|
||||
|
||||
@field:SerializedName("eve")
|
||||
val eve: Double? = null,
|
||||
|
||||
@field:SerializedName("night")
|
||||
val night: Double? = null,
|
||||
|
||||
@field:SerializedName("day")
|
||||
val day: Double? = null,
|
||||
|
||||
@field:SerializedName("morn")
|
||||
val morn: Double? = null
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class WeatherItem(
|
||||
|
||||
@field:SerializedName("icon")
|
||||
val icon: String? = null,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String? = null,
|
||||
|
||||
@field:SerializedName("main")
|
||||
val main: String? = null,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int? = null
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class WeatherResponse(
|
||||
|
||||
@field:SerializedName("current")
|
||||
val current: Current? = null,
|
||||
|
||||
@field:SerializedName("timezone")
|
||||
val timezone: String? = null,
|
||||
|
||||
@field:SerializedName("timezone_offset")
|
||||
val timezoneOffset: Int? = null,
|
||||
|
||||
@field:SerializedName("hourly")
|
||||
val hourly: List<Hour>? = null,
|
||||
|
||||
@field:SerializedName("daily")
|
||||
val daily: List<DailyItem>? = null,
|
||||
|
||||
@field:SerializedName("lon")
|
||||
val lon: Double = 0.00,
|
||||
|
||||
@field:SerializedName("lat")
|
||||
val lat: Double = 0.00
|
||||
)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||
|
||||
/**
|
||||
* Shared preferences to save & load last timestamp
|
||||
*/
|
||||
const val LOCATION_CONST = "location_"
|
||||
class PreferenceProvider(
|
||||
context: Context
|
||||
){
|
||||
|
||||
private val appContext = context.applicationContext
|
||||
|
||||
private val preference: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(appContext)
|
||||
|
||||
fun saveLastSavedAt(locationName: String) {
|
||||
preference.edit().putLong(
|
||||
locationName,
|
||||
System.currentTimeMillis()
|
||||
).apply()
|
||||
}
|
||||
|
||||
fun getLastSavedAt(locationName: String): Long? {
|
||||
return preference.getLong(locationName, 0L)
|
||||
}
|
||||
|
||||
fun getAllKeys() = preference.all.keys.apply {
|
||||
remove(CURRENT_LOCATION)
|
||||
}
|
||||
|
||||
fun deleteLocation(locationName: String){
|
||||
preference.edit().remove(locationName).apply()
|
||||
}
|
||||
|
||||
fun isNotificationsEnabled(): Boolean = preference.getBoolean("notif_boolean", true)
|
||||
|
||||
fun setFirstTimeRun(){
|
||||
preference.edit().putBoolean("FIRST_TIME_RUN", false).apply()
|
||||
}
|
||||
|
||||
fun isWidgetBlackground(): Boolean {
|
||||
return preference.getBoolean("widget_black_background", false)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
|
||||
interface Repository {
|
||||
|
||||
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse
|
||||
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
|
||||
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
||||
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
||||
suspend fun loadWeatherList() : List<String>
|
||||
fun loadCurrentWeatherFromRoom(id: String): LiveData<EntityItem>
|
||||
suspend fun loadSingleCurrentWeatherFromRoom(id: String): EntityItem
|
||||
fun isSearchValid(locationName: String): Boolean
|
||||
fun saveLastSavedAt(locationName: String)
|
||||
suspend fun deleteSavedWeatherEntry(locationName: String): Boolean
|
||||
fun getSavedLocations(): List<String>
|
||||
suspend fun getSingleWeather(locationName: String): EntityItem
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
|
||||
|
||||
|
||||
class RepositoryImpl(
|
||||
private val api: WeatherApi,
|
||||
private val db: AppDatabase,
|
||||
private val prefs: PreferenceProvider
|
||||
) : Repository, ResponseUnwrap() {
|
||||
|
||||
override suspend fun getWeatherFromApi(
|
||||
lat: String,
|
||||
long: String
|
||||
): WeatherResponse {
|
||||
return responseUnwrap { api.getFromApi(lat, long) }
|
||||
}
|
||||
|
||||
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem){
|
||||
db.getSimpleDao().upsertFullWeather(entityItem)
|
||||
}
|
||||
|
||||
override suspend fun saveWeatherListToRoom(
|
||||
list: List<EntityItem>
|
||||
){
|
||||
db.getSimpleDao().upsertListOfFullWeather(list)
|
||||
}
|
||||
|
||||
override fun loadRoomWeatherLiveData() = db.getSimpleDao().getAllFullWeatherWithoutCurrent()
|
||||
|
||||
override suspend fun loadWeatherList() : List<String>{
|
||||
return db.getSimpleDao()
|
||||
.getWeatherListWithoutCurrent()
|
||||
.map { it.id }
|
||||
}
|
||||
|
||||
override fun loadCurrentWeatherFromRoom(id: String)
|
||||
= db.getSimpleDao().getCurrentFullWeather(id)
|
||||
|
||||
override suspend fun loadSingleCurrentWeatherFromRoom(id: String)
|
||||
= db.getSimpleDao().getCurrentFullWeatherSingle(id)
|
||||
|
||||
override fun isSearchValid(locationName: String): Boolean {
|
||||
val lastSaved = prefs
|
||||
.getLastSavedAt("$LOCATION_CONST$locationName")
|
||||
?: return true
|
||||
val difference = System.currentTimeMillis() - lastSaved
|
||||
|
||||
return difference > FALLBACK_TIME
|
||||
}
|
||||
|
||||
override fun saveLastSavedAt(locationName: String) {
|
||||
prefs.saveLastSavedAt("$LOCATION_CONST$locationName")
|
||||
}
|
||||
|
||||
override suspend fun deleteSavedWeatherEntry(locationName: String): Boolean {
|
||||
prefs.deleteLocation(locationName)
|
||||
return db.getSimpleDao().deleteEntry(locationName) > 0
|
||||
}
|
||||
|
||||
override fun getSavedLocations(): List<String> {
|
||||
return prefs.getAllKeys().toList()
|
||||
}
|
||||
|
||||
override suspend fun getSingleWeather(locationName: String): EntityItem {
|
||||
return db.getSimpleDao().getCurrentFullWeatherSingle(locationName)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
interface SettingsRepository {
|
||||
fun isNotificationsEnabled(): Boolean
|
||||
fun setFirstTime()
|
||||
fun isBlackBackground(): Boolean
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
|
||||
class SettingsRepositoryImpl(
|
||||
private val prefs: PreferenceProvider
|
||||
) : SettingsRepository{
|
||||
|
||||
override fun isNotificationsEnabled(): Boolean = prefs.isNotificationsEnabled()
|
||||
|
||||
override fun setFirstTime() = prefs.setFirstTimeRun()
|
||||
|
||||
override fun isBlackBackground() = prefs.isWidgetBlackground()
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.room
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
|
||||
@Database(
|
||||
entities = [EntityItem::class],
|
||||
version = 1,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(Converter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun getSimpleDao(): WeatherDao
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var instance: AppDatabase? = null
|
||||
private val LOCK = Any()
|
||||
|
||||
// create an instance of room database or use previously created instance
|
||||
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
|
||||
instance ?: buildDatabase(context).also {
|
||||
instance = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabase(context: Context) =
|
||||
Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
AppDatabase::class.java,
|
||||
"MyDatabase.db"
|
||||
).addTypeConverter(Converter(context))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.room
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.ProvidedTypeConverter
|
||||
import androidx.room.TypeConverter
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||
import com.google.gson.Gson
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
|
||||
@ProvidedTypeConverter
|
||||
class Converter(context: Context) : KodeinAware {
|
||||
override val kodein by kodein(context)
|
||||
private val gson by instance<Gson>()
|
||||
|
||||
@TypeConverter
|
||||
fun fullWeatherToString(fullWeather: FullWeather): String {
|
||||
return gson.toJson(fullWeather)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToFullWeather(string: String): FullWeather {
|
||||
return gson.fromJson(string, FullWeather::class.java)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
|
||||
@Dao
|
||||
interface WeatherDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun upsertFullWeather(item: EntityItem)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun upsertListOfFullWeather(items: List<EntityItem>)
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
||||
fun getCurrentFullWeather(userId: String) : LiveData<EntityItem>
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
||||
fun getCurrentFullWeatherSingle(userId: String) : EntityItem
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
||||
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION) : LiveData<List<EntityItem>>
|
||||
|
||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
||||
fun getWeatherListWithoutCurrent(id: String = CURRENT_LOCATION) : List<EntityItem>
|
||||
|
||||
@Query("DELETE FROM EntityItem WHERE id = :userId")
|
||||
fun deleteEntry(userId: String): Int
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.room.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||
|
||||
|
||||
const val CURRENT_LOCATION = "CurrentLocation"
|
||||
@Entity
|
||||
data class EntityItem(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
val id: String,
|
||||
val weather: FullWeather
|
||||
)
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.helper
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object GenericsHelper {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
|
||||
((javaClass.genericSuperclass as? ParameterizedType)
|
||||
?.actualTypeArguments?.getOrNull(position) as? Class<CLASS>)
|
||||
?.kotlin
|
||||
?: throw IllegalStateException("Can not find class from generic argument")
|
||||
|
||||
// /**
|
||||
// * Create a view binding out of the the generic [VB]
|
||||
// *
|
||||
// * @sample inflateBindingByType(getGenericClassAt(0), layoutInflater)
|
||||
// */
|
||||
// fun <VB: ViewBinding> inflateBindingByType(
|
||||
// genericClassAt: KClass<VB>,
|
||||
// layoutInflater: LayoutInflater
|
||||
// ): VB = try {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
//
|
||||
// genericClassAt.java.methods.first { viewBinding ->
|
||||
// viewBinding.parameterTypes.size == 1
|
||||
// && viewBinding.parameterTypes.getOrNull(0) == LayoutInflater::class.java
|
||||
// }.invoke(null, layoutInflater) as VB
|
||||
// } catch (exception: Exception) {
|
||||
// println ("generic class failed at = $genericClassAt")
|
||||
// exception.printStackTrace()
|
||||
// throw IllegalStateException("Can not inflate binding from generic")
|
||||
// }
|
||||
//
|
||||
// fun <VB: ViewBinding> LayoutInflater.inflateBindingByType(
|
||||
// container: ViewGroup?,
|
||||
// genericClassAt: KClass<VB>
|
||||
// ): VB = try {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// genericClassAt.java.methods.first { inflateFun ->
|
||||
// inflateFun.parameterTypes.size == 3
|
||||
// && inflateFun.parameterTypes.getOrNull(0) == LayoutInflater::class.java
|
||||
// && inflateFun.parameterTypes.getOrNull(1) == ViewGroup::class.java
|
||||
// && inflateFun.parameterTypes.getOrNull(2) == Boolean::class.java
|
||||
// }.invoke(null, this, container, false) as VB
|
||||
// } catch (exception: Exception) {
|
||||
// throw IllegalStateException("Can not inflate binding from generic")
|
||||
// }
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.helper
|
||||
|
||||
import android.Manifest
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.RequiresPermission
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||
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.weather.FullWeather
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
|
||||
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.Target
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
||||
class ServicesHelper(
|
||||
private val repository: Repository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val locationProvider: LocationProvider
|
||||
) {
|
||||
|
||||
@RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
suspend fun fetchData(): Boolean {
|
||||
if (!repository.isSearchValid(CURRENT_LOCATION)) return false
|
||||
|
||||
return try {
|
||||
// Get location
|
||||
val latLong = locationProvider.getCurrentLatLong()
|
||||
// Get weather from api
|
||||
val weather = repository
|
||||
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||
val currentLocation = locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||
val fullWeather = FullWeather(weather).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = currentLocation
|
||||
}
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
// Save data if not null
|
||||
repository.saveLastSavedAt(CURRENT_LOCATION)
|
||||
repository.saveCurrentWeatherToRoom(entityItem)
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWidgetWeather(): WidgetData? {
|
||||
return try {
|
||||
val result = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||
val epoc = System.currentTimeMillis()
|
||||
|
||||
result.weather.let {
|
||||
val bitmap = it.current?.icon
|
||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
||||
val temp = it.current?.temp?.toInt().toString()
|
||||
|
||||
WidgetData(location, bitmap, temp, epoc)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWidgetInnerWeather(): List<InnerWidgetData>? {
|
||||
return try {
|
||||
val result = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||
val list = mutableListOf<InnerWidgetData>()
|
||||
|
||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
||||
val day = dailyWeather.dt?.toSmallDayName()
|
||||
val bitmap = withContext(Dispatchers.Main) {
|
||||
getBitmapFromUrl(dailyWeather.icon)
|
||||
}
|
||||
val temp = dailyWeather.max?.toInt().toString()
|
||||
|
||||
val item = InnerWidgetData(day, bitmap, temp)
|
||||
list.add(item)
|
||||
}
|
||||
list.toList()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWidgetWeatherCollection(): WidgetWeatherCollection? {
|
||||
return try {
|
||||
val result = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||
|
||||
val widgetData = result.weather.let {
|
||||
val bitmap = it.current?.icon
|
||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
||||
val temp = it.current?.temp?.toInt().toString()
|
||||
val epoc = System.currentTimeMillis()
|
||||
|
||||
WidgetData(location, bitmap, temp, epoc)
|
||||
}
|
||||
|
||||
val list = mutableListOf<InnerWidgetCellData>()
|
||||
|
||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
||||
val day = dailyWeather.dt?.toSmallDayName()
|
||||
val icon = dailyWeather.icon
|
||||
val temp = dailyWeather.max?.toInt().toString()
|
||||
|
||||
val item = InnerWidgetCellData(day, icon, temp)
|
||||
list.add(item)
|
||||
}
|
||||
list.toList()
|
||||
|
||||
WidgetWeatherCollection(widgetData, list)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getBitmapFromUrl(imageAddress: String?): Bitmap? {
|
||||
return suspendCoroutine { cont ->
|
||||
Picasso.get().load(imageAddress).into(object : Target {
|
||||
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
|
||||
cont.resume(bitmap)
|
||||
}
|
||||
|
||||
override fun onBitmapFailed(e: Exception?, d: Drawable?) {
|
||||
cont.resume(null)
|
||||
}
|
||||
|
||||
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun getWidgetBackground(): Int {
|
||||
return if (settingsRepository.isBlackBackground())
|
||||
Color.BLACK
|
||||
else
|
||||
Color.TRANSPARENT
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.forecast
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.DailyWeather
|
||||
import com.appttude.h_mal.atlas_weather.utils.parcelableCreator
|
||||
import com.appttude.h_mal.atlas_weather.utils.toDayName
|
||||
import com.appttude.h_mal.atlas_weather.utils.toDayString
|
||||
import com.appttude.h_mal.atlas_weather.utils.toTime
|
||||
|
||||
data class Forecast(
|
||||
val date: String?,
|
||||
val day: String?,
|
||||
val condition: String?,
|
||||
val weatherIcon: String?,
|
||||
val mainTemp: String?,
|
||||
val minorTemp: String?,
|
||||
val averageTemp: String?,
|
||||
val windText: String?,
|
||||
val precipitation: String?,
|
||||
val humidity: String?,
|
||||
val uvi: String?,
|
||||
val sunrise: String?,
|
||||
val sunset: String?,
|
||||
val cloud: String?
|
||||
): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString())
|
||||
|
||||
constructor(dailyWeather: DailyWeather) : this(
|
||||
dailyWeather.dt?.toDayString(),
|
||||
dailyWeather.dt?.toDayName(),
|
||||
dailyWeather.description,
|
||||
dailyWeather.icon,
|
||||
dailyWeather.max?.toInt().toString(),
|
||||
dailyWeather.min?.toInt().toString(),
|
||||
dailyWeather.average?.toInt().toString(),
|
||||
dailyWeather.windSpeed?.toInt().toString(),
|
||||
(dailyWeather.pop?.times(100))?.toInt().toString(),
|
||||
dailyWeather.humidity?.toString(),
|
||||
dailyWeather.uvi?.toInt().toString(),
|
||||
dailyWeather.sunrise?.toTime(),
|
||||
dailyWeather.sunset?.toTime(),
|
||||
dailyWeather.clouds?.toString()
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(date)
|
||||
parcel.writeString(day)
|
||||
parcel.writeString(condition)
|
||||
parcel.writeString(weatherIcon)
|
||||
parcel.writeString(mainTemp)
|
||||
parcel.writeString(minorTemp)
|
||||
parcel.writeString(averageTemp)
|
||||
parcel.writeString(windText)
|
||||
parcel.writeString(precipitation)
|
||||
parcel.writeString(humidity)
|
||||
parcel.writeString(uvi)
|
||||
parcel.writeString(sunrise)
|
||||
parcel.writeString(sunset)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object{
|
||||
@JvmField val CREATOR = parcelableCreator(::Forecast)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.forecast
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.Hour
|
||||
|
||||
|
||||
data class WeatherDisplay(
|
||||
val averageTemp: Double?,
|
||||
var unit: String?,
|
||||
var location: String?,
|
||||
val iconURL: String?,
|
||||
val description: String?,
|
||||
val hourly: List<Hour>?,
|
||||
val forecast: List<Forecast>?,
|
||||
val windSpeed: String?,
|
||||
val windDirection: String?,
|
||||
val precipitation: String?,
|
||||
val humidity: String?,
|
||||
val clouds: String?,
|
||||
val lat: Double = 0.00,
|
||||
val lon: Double = 0.00,
|
||||
var displayName: String?
|
||||
){
|
||||
|
||||
constructor(entity: EntityItem) : this(
|
||||
entity.weather.current?.temp,
|
||||
entity.weather.temperatureUnit,
|
||||
entity.id,
|
||||
entity.weather.current?.icon,
|
||||
entity.weather.current?.description,
|
||||
entity.weather.hourly,
|
||||
entity.weather.daily?.drop(1)?.map { Forecast(it) },
|
||||
entity.weather.current?.windSpeed?.toString(),
|
||||
entity.weather.current?.windDeg?.toString(),
|
||||
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
|
||||
entity.weather.current?.humidity?.toString(),
|
||||
entity.weather.current?.clouds?.toString(),
|
||||
entity.weather.lat,
|
||||
entity.weather.lon,
|
||||
entity.weather.locationString
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.types
|
||||
|
||||
enum class LocationType{
|
||||
City,
|
||||
Town
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Current
|
||||
|
||||
data class Current(
|
||||
val dt: Int? = null,
|
||||
val sunrise: Int? = null,
|
||||
val sunset: Int? = null,
|
||||
val temp: Double? = null,
|
||||
val visibility: Int? = null,
|
||||
val uvi: Double? = null,
|
||||
val pressure: Int? = null,
|
||||
val clouds: Int? = null,
|
||||
val feelsLike: Double? = null,
|
||||
val windDeg: Int? = null,
|
||||
val dewPoint: Double? = null,
|
||||
val icon: String? = null,
|
||||
val description: String? = null,
|
||||
val main: String? = null,
|
||||
val id: Int? = null,
|
||||
val humidity: Int? = null,
|
||||
val windSpeed: Double? = null
|
||||
){
|
||||
|
||||
constructor(dailyItem: Current): this(
|
||||
dailyItem.dt,
|
||||
dailyItem.sunrise,
|
||||
dailyItem.sunset,
|
||||
dailyItem.temp,
|
||||
dailyItem.visibility,
|
||||
dailyItem.uvi,
|
||||
dailyItem.pressure,
|
||||
dailyItem.clouds,
|
||||
dailyItem.feelsLike,
|
||||
dailyItem.windDeg,
|
||||
dailyItem.dewPoint,
|
||||
dailyItem.weather?.get(0)?.icon?.let { "https://openweathermap.org/img/wn/${it}@4x.png" },
|
||||
dailyItem.weather?.get(0)?.description,
|
||||
dailyItem.weather?.get(0)?.main,
|
||||
dailyItem.weather?.get(0)?.id,
|
||||
dailyItem.humidity,
|
||||
dailyItem.windSpeed
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.DailyItem
|
||||
|
||||
|
||||
data class DailyWeather(
|
||||
val dt: Int?,
|
||||
val sunrise: Int?,
|
||||
val sunset: Int?,
|
||||
val min: Double? = null,
|
||||
val max: Double? = null,
|
||||
val average: Double? = null,
|
||||
var feelsLike: Double?,
|
||||
val pressure: Int?,
|
||||
val humidity: Int?,
|
||||
val dewPoint: Double?,
|
||||
val windSpeed: Double?,
|
||||
val windDeg: Int?,
|
||||
val icon: String? = null,
|
||||
val description: String? = null,
|
||||
val main: String? = null,
|
||||
val id: Int? = null,
|
||||
val clouds: Int?,
|
||||
val pop: Double?,
|
||||
val uvi: Double?,
|
||||
val rain: Double?
|
||||
){
|
||||
|
||||
constructor(dailyItem: DailyItem): this(
|
||||
dailyItem.dt,
|
||||
dailyItem.sunrise,
|
||||
dailyItem.sunset,
|
||||
dailyItem.temp?.min,
|
||||
dailyItem.temp?.max,
|
||||
dailyItem.temp?.day,
|
||||
dailyItem.feelsLike?.day,
|
||||
dailyItem.pressure,
|
||||
dailyItem.humidity,
|
||||
dailyItem.dewPoint,
|
||||
dailyItem.windSpeed,
|
||||
dailyItem.windDeg,
|
||||
dailyItem.weather?.get(0)?.icon?.let { "https://openweathermap.org/img/wn/${it}@4x.png" },
|
||||
dailyItem.weather?.get(0)?.description,
|
||||
dailyItem.weather?.get(0)?.main,
|
||||
dailyItem.weather?.get(0)?.id,
|
||||
dailyItem.clouds,
|
||||
dailyItem.pop,
|
||||
dailyItem.uvi,
|
||||
dailyItem.rain
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
|
||||
data class FullWeather(
|
||||
val current: Current? = null,
|
||||
val timezone: String? = null,
|
||||
val timezoneOffset: Int? = null,
|
||||
val hourly: List<Hour>? = null,
|
||||
val daily: List<DailyWeather>? = null,
|
||||
val lon: Double = 0.00,
|
||||
val lat: Double = 0.00,
|
||||
var locationString: String? = null,
|
||||
var temperatureUnit: String? = null
|
||||
) {
|
||||
|
||||
constructor(weatherResponse: WeatherResponse): this(
|
||||
weatherResponse.current?.let { Current(it) },
|
||||
weatherResponse.timezone,
|
||||
weatherResponse.timezoneOffset,
|
||||
weatherResponse.hourly?.subList(0,23)?.map { Hour(it) },
|
||||
weatherResponse.daily?.map { DailyWeather(it) },
|
||||
weatherResponse.lon,
|
||||
weatherResponse.lat
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Hour as ForecastHour
|
||||
|
||||
|
||||
data class Hour(
|
||||
val dt: Int? = null,
|
||||
val temp: Double? = null,
|
||||
val icon: String? = null
|
||||
){
|
||||
constructor(hour: ForecastHour) : this(
|
||||
hour.dt,
|
||||
hour.temp,
|
||||
generateIconUrlString(hour.weather?.getOrNull(0)?.icon)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.widget
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
data class WidgetData(
|
||||
val location: String?,
|
||||
val icon: String?,
|
||||
val currentTemp: String?,
|
||||
val timeStamp: Long
|
||||
)
|
||||
|
||||
data class InnerWidgetData(
|
||||
val date: String?,
|
||||
val icon: Bitmap?,
|
||||
val highTemp: String?
|
||||
)
|
||||
|
||||
|
||||
data class InnerWidgetCellData(
|
||||
val date: String?,
|
||||
val icon: String?,
|
||||
val highTemp: String?
|
||||
)
|
||||
|
||||
data class WidgetWeatherCollection(
|
||||
val widgetData: WidgetData,
|
||||
val forecast: List<InnerWidgetCellData>
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
val FALLBACK_TIME: Long = 300000L
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
/**
|
||||
* Used with livedata<T> to make observation lifecycle aware
|
||||
* Display livedata response only once
|
||||
*/
|
||||
open class Event<out T>(private val content: T) {
|
||||
|
||||
var hasBeenHandled = false
|
||||
private set // Allow external read but not write
|
||||
|
||||
/**
|
||||
* Returns the content and prevents its use again.
|
||||
*/
|
||||
fun getContentIfNotHandled(): T? {
|
||||
return if (hasBeenHandled) {
|
||||
null
|
||||
} else {
|
||||
hasBeenHandled = true
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
inline fun <reified T> parcelableCreator(
|
||||
crossinline create: (Parcel) -> T) =
|
||||
object : Parcelable.Creator<T> {
|
||||
override fun createFromParcel(source: Parcel) = create(source)
|
||||
override fun newArray(size: Int) = arrayOfNulls<T>(size)
|
||||
}
|
||||
|
||||
suspend fun <T : Any?> tryOrNullSuspended(
|
||||
call: suspend () -> T?
|
||||
): T? {
|
||||
|
||||
return try {
|
||||
call.invoke()
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any?> tryOrNull(
|
||||
call: () -> T?
|
||||
): T? {
|
||||
|
||||
return try {
|
||||
call.invoke()
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a suspend function
|
||||
* @param call - the body of the lambda
|
||||
*
|
||||
* @sample
|
||||
* fun getNumber() = 2
|
||||
* suspend fun getSuspendNumber{ getNumber() }
|
||||
*
|
||||
* Both equal 2.
|
||||
*/
|
||||
suspend fun <T: Any> createSuspend(
|
||||
call: () -> T?
|
||||
): T?{
|
||||
|
||||
return suspendCoroutine { cont ->
|
||||
cont.resume(call())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
fun printToLog(msg: String) {
|
||||
println("widget monitoring: $msg")
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.Navigation
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
|
||||
fun Fragment.navigateToFragment(newFragment: Fragment){
|
||||
childFragmentManager.beginTransaction()
|
||||
.add(R.id.container, newFragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
||||
fun View.navigateTo(navigationId: Int) {
|
||||
Navigation.findNavController(this).navigate(navigationId)
|
||||
}
|
||||
|
||||
fun View.navigateTo(navDirections: NavDirections) {
|
||||
Navigation.findNavController(this).navigate(navDirections)
|
||||
}
|
||||
|
||||
fun Fragment.navigateTo(navigationId: Int) {
|
||||
Navigation.findNavController(requireView()).navigate(navigationId)
|
||||
}
|
||||
|
||||
fun Fragment.navigateTo(navDirections: NavDirections) {
|
||||
Navigation.findNavController(requireView()).navigate(navDirections)
|
||||
}
|
||||
|
||||
fun Fragment.goBack() = Navigation.findNavController(requireView()).popBackStack()
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
fun isInternetAvailable(
|
||||
context: Context
|
||||
): Boolean {
|
||||
var result = false
|
||||
val connectivityManager =
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
|
||||
connectivityManager?.let {
|
||||
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
|
||||
result = when {
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
|
||||
import android.os.Build
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.time.OffsetTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
fun Int.toDayString(): String {
|
||||
return try {
|
||||
val date = Date(this.makeMilliseconds())
|
||||
val format = SimpleDateFormat("MMM d", Locale.getDefault())
|
||||
format.format(date)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
"Unable to parse date"
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.makeMilliseconds(): Long = this * 1000L
|
||||
|
||||
fun Int.toDayName(): String {
|
||||
return try {
|
||||
val date = Date(this.makeMilliseconds())
|
||||
val format = SimpleDateFormat("EEEE", Locale.getDefault())
|
||||
format.format(date)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
"Unable to parse date"
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toSmallDayName(): String {
|
||||
return try {
|
||||
val date = Date(this.makeMilliseconds())
|
||||
val format = SimpleDateFormat("EEE", Locale.getDefault())
|
||||
format.format(date)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
"Unable to parse date"
|
||||
}
|
||||
}
|
||||
|
||||
fun Int?.toTime(): String? {
|
||||
return this?.makeMilliseconds()?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
OffsetTime.ofInstant(Instant.ofEpochMilli(it), ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||
} else {
|
||||
val date = Date(it)
|
||||
val format = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||
format.format(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
|
||||
fun generateIconUrlString(icon: String?): String?{
|
||||
return icon?.let {
|
||||
StringBuilder()
|
||||
.append("https://openweathermap.org/img/wn/")
|
||||
.append(it)
|
||||
.append("@4x.png")
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
fun View.show() {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.hide() {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun Context.displayToast(message: String) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun Fragment.displayToast(message: String) {
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun ViewGroup.generateView(layoutId: Int): View = LayoutInflater
|
||||
.from(context)
|
||||
.inflate(layoutId, this, false)
|
||||
|
||||
fun ImageView.loadImage(url: String?){
|
||||
val c = Glide.with(this)
|
||||
.load(url)
|
||||
viewTreeObserver.addOnPreDrawListener {
|
||||
c.override(width, height)
|
||||
true
|
||||
}
|
||||
c.placeholder(R.drawable.ic_baseline_cloud_queue_24)
|
||||
.error(R.drawable.ic_baseline_cloud_off_24)
|
||||
.fitCenter()
|
||||
.into(this)
|
||||
}
|
||||
|
||||
fun Fragment.hideKeyboard() {
|
||||
val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
imm?.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
|
||||
class ApplicationViewModelFactory(
|
||||
private val locationProvider: LocationProvider,
|
||||
private val repository: RepositoryImpl
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
with(modelClass){
|
||||
return when{
|
||||
isAssignableFrom(WorldViewModel::class.java) -> WorldViewModel(locationProvider, repository)
|
||||
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(locationProvider, repository)
|
||||
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||
} as T
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.viewmodel
|
||||
|
||||
import android.Manifest
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
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.utils.Event
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.WeatherViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainViewModel(
|
||||
private val locationProvider: LocationProvider,
|
||||
private val repository: Repository
|
||||
): WeatherViewModel(){
|
||||
|
||||
val weatherLiveData = MutableLiveData<WeatherDisplay>()
|
||||
|
||||
val operationState = MutableLiveData<Event<Boolean>>()
|
||||
val operationError = MutableLiveData<Event<String>>()
|
||||
val operationRefresh = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
init {
|
||||
repository.loadCurrentWeatherFromRoom(CURRENT_LOCATION).observeForever {
|
||||
it?.let {
|
||||
val weather = WeatherDisplay(it)
|
||||
weatherLiveData.postValue(weather)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(value = Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
fun fetchData(){
|
||||
if (!repository.isSearchValid(CURRENT_LOCATION)){
|
||||
operationRefresh.postValue(Event(false))
|
||||
return
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
operationState.postValue(Event(true))
|
||||
try {
|
||||
// Get location
|
||||
val latLong = locationProvider.getCurrentLatLong()
|
||||
// Get weather from api
|
||||
val weather = repository
|
||||
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||
val currentLocation = locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||
val fullWeather = createFullWeather(weather, currentLocation)
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
// Save data if not null
|
||||
repository.saveLastSavedAt(CURRENT_LOCATION)
|
||||
repository.saveCurrentWeatherToRoom(entityItem)
|
||||
}catch (e: Exception){
|
||||
operationError.postValue(Event(e.message!!))
|
||||
}finally {
|
||||
operationState.postValue(Event(false))
|
||||
operationRefresh.postValue(Event(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.viewmodel
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
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.utils.Event
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.baseViewModels.WeatherViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
|
||||
const val ALL_LOADED = "all_loaded"
|
||||
class WorldViewModel(
|
||||
private val locationProvider: LocationProvider,
|
||||
private val repository: Repository
|
||||
) : WeatherViewModel() {
|
||||
|
||||
val weatherLiveData = MutableLiveData<List<WeatherDisplay>>()
|
||||
val singleWeatherLiveData = MutableLiveData<WeatherDisplay>()
|
||||
|
||||
val operationState = MutableLiveData<Event<Boolean>>()
|
||||
val operationError = MutableLiveData<Event<String>>()
|
||||
val operationRefresh = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val operationComplete = MutableLiveData<Event<String>>()
|
||||
|
||||
private val weatherListLiveData = repository.loadRoomWeatherLiveData()
|
||||
|
||||
init {
|
||||
weatherListLiveData.observeForever {
|
||||
val list = it.map { data ->
|
||||
WeatherDisplay(data)
|
||||
}
|
||||
weatherLiveData.postValue(list)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSingleLocation(locationName: String){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val entity = repository.getSingleWeather(locationName)
|
||||
val item = WeatherDisplay(entity)
|
||||
singleWeatherLiveData.postValue(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchDataForSingleLocation(locationName: String) {
|
||||
if (!repository.isSearchValid(locationName)){
|
||||
operationRefresh.postValue(Event(false))
|
||||
return
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
operationState.postValue(Event(true))
|
||||
try {
|
||||
val weatherEntity = createWeatherEntity(locationName)
|
||||
repository.saveCurrentWeatherToRoom(weatherEntity)
|
||||
repository.saveLastSavedAt(locationName)
|
||||
} catch (e: IOException) {
|
||||
operationError.postValue(Event(e.message!!))
|
||||
} finally {
|
||||
operationState.postValue(Event(false))
|
||||
operationRefresh.postValue(Event(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchDataForSingleLocationSearch(locationName: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
operationState.postValue(Event(true))
|
||||
// Check if location exists
|
||||
if (repository.getSavedLocations().contains(locationName)){
|
||||
operationError.postValue(Event("$locationName already exists"))
|
||||
return@launch
|
||||
}
|
||||
|
||||
try {
|
||||
// Get weather from api
|
||||
val entityItem = createWeatherEntity(locationName)
|
||||
|
||||
// retrieved location name
|
||||
val retrievedLocation = locationProvider.getLocationNameFromLatLong(entityItem.weather.lat, entityItem.weather.lon, LocationType.City)
|
||||
if (repository.getSavedLocations().contains(retrievedLocation)){
|
||||
operationError.postValue(Event("$retrievedLocation already exists"))
|
||||
return@launch
|
||||
}
|
||||
// Save data if not null
|
||||
repository.saveCurrentWeatherToRoom(entityItem)
|
||||
repository.saveLastSavedAt(retrievedLocation)
|
||||
operationComplete.postValue(Event("$retrievedLocation saved"))
|
||||
|
||||
} catch (e: IOException) {
|
||||
operationError.postValue(Event(e.message!!))
|
||||
} finally {
|
||||
operationState.postValue(Event(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchAllLocations() {
|
||||
if (!repository.isSearchValid(ALL_LOADED)){
|
||||
operationRefresh.postValue(Event(false))
|
||||
return
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
operationState.postValue(Event(true))
|
||||
try {
|
||||
val list = mutableListOf<EntityItem>()
|
||||
repository.loadWeatherList().forEach { locationName ->
|
||||
// If search not valid move onto next in loop
|
||||
if (!repository.isSearchValid(locationName)) return@forEach
|
||||
|
||||
try {
|
||||
val entity = createWeatherEntity(locationName)
|
||||
list.add(entity)
|
||||
repository.saveLastSavedAt(locationName)
|
||||
} catch (e: IOException) { }
|
||||
}
|
||||
repository.saveWeatherListToRoom(list)
|
||||
repository.saveLastSavedAt(ALL_LOADED)
|
||||
} catch (e: IOException) {
|
||||
operationError.postValue(Event(e.message!!))
|
||||
} finally {
|
||||
operationState.postValue(Event(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLocation(locationName: String){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
operationState.postValue(Event(true))
|
||||
try {
|
||||
val success = repository.deleteSavedWeatherEntry(locationName)
|
||||
if (!success){
|
||||
operationError.postValue(Event("Failed to delete"))
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
operationError.postValue(Event(e.message!!))
|
||||
} finally {
|
||||
operationState.postValue(Event(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getWeather(locationName: String): WeatherResponse {
|
||||
// Get location
|
||||
val latLong =
|
||||
locationProvider.getLatLongFromLocationName(locationName)
|
||||
val lat = latLong.first
|
||||
val lon = latLong.second
|
||||
|
||||
// Get weather from api
|
||||
return repository.getWeatherFromApi(lat.toString(), lon.toString())
|
||||
}
|
||||
|
||||
private suspend fun createWeatherEntity(locationName: String): EntityItem {
|
||||
val weather = getWeather(locationName)
|
||||
val location = locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, LocationType.City)
|
||||
val fullWeather = createFullWeather(weather, location)
|
||||
return createWeatherEntity(location,fullWeather)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.viewmodel.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
|
||||
|
||||
abstract class WeatherViewModel : ViewModel(){
|
||||
|
||||
fun createFullWeather(
|
||||
weather: WeatherResponse,
|
||||
location: String
|
||||
): FullWeather {
|
||||
return FullWeather(weather).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = location
|
||||
}
|
||||
}
|
||||
|
||||
fun createWeatherEntity(
|
||||
locationId: String,
|
||||
weather: FullWeather
|
||||
): EntityItem{
|
||||
weather.apply {
|
||||
locationString = locationId
|
||||
}
|
||||
|
||||
return EntityItem(locationId, weather)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.appttude.h_mal.atlas_weather.monoWeather.ui.world.AddLocationFragment">
|
||||
tools:context="com.appttude.h_mal.ui.world.AddLocationFragment">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu="@menu/tabs_menu" />
|
||||
|
||||
<fragment
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.appttude.h_mal.atlas_weather.monoWeather.ui.world.WorldFragment">
|
||||
tools:context="com.appttude.h_mal.ui.world.WorldFragment">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.appttude.h_mal.atlas_weather.monoWeather.ui.world.AddLocationFragment">
|
||||
tools:context="com.appttude.h_mal.ui.world.AddLocationFragment">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -34,6 +34,4 @@
|
||||
android:layout_height="wrap_content"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".legacy.ui.home.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:id="@+id/settings_fragment"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
android:icon="@drawable/ic_round_settings_24"
|
||||
|
||||
6
app/src/main/res/xml/network_security_config.xml
Normal file
6
app/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">openweathermap.org</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -6,7 +6,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<application
|
||||
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
|
||||
android:name="com.appttude.h_mal.application.AppClass"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -15,7 +15,7 @@
|
||||
android:theme="@style/AppTheme"
|
||||
tools:node="merge">
|
||||
<activity
|
||||
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity"
|
||||
android:name="com.appttude.h_mal.ui.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
@@ -27,18 +27,15 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.settings.UnitSettingsActivity"
|
||||
android:label="Settings" />
|
||||
|
||||
<activity android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.widget.WidgetLocationPermissionActivity"
|
||||
<activity android:name="com.appttude.h_mal.monoWeather.ui.widget.WidgetLocationPermissionActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="com.appttude.h_mal.atlas_weather.monoWeather.widget.NewAppWidget"
|
||||
<receiver android:name="com.appttude.h_mal.widget.NewAppWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
@@ -52,7 +49,7 @@
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="com.appttude.h_mal.atlas_weather.monoWeather.widget.WidgetJobServiceIntent"
|
||||
android:name="com.appttude.h_mal.widget.WidgetJobServiceIntent"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
</application>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.dialog
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
interface DeclarationBuilder{
|
||||
val link: String
|
||||
val message: String
|
||||
|
||||
fun Context.readFromResources(@StringRes id: Int) = resources.getString(id)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun buildMessage(): CharSequence? {
|
||||
val link1 = "<font color='blue'><a href=\"$link\">here</a></font>"
|
||||
val message = "$message See my privacy policy: $link1"
|
||||
return Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY)
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.dialog
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
|
||||
|
||||
class PermissionsDeclarationDialog(context: Context) : BaseDeclarationDialog(context) {
|
||||
|
||||
override val link: String = "https://sites.google.com/view/hmaldev/home/monochrome"
|
||||
override val message: String = "Hi, thank you for downloading my app. Google play isn't letting me upload my app to the Playstore until I have a privacy declaration :(. My app is basically used to demonstrate my code=ing to potential employers and others. I do NOT store or process any information. The location permission in the app is there just to provide the end user with weather data."
|
||||
}
|
||||
|
||||
abstract class BaseDeclarationDialog(val context: Context): DeclarationBuilder {
|
||||
abstract override val link: String
|
||||
abstract override val message: String
|
||||
|
||||
lateinit var dialog: AlertDialog
|
||||
|
||||
fun showDialog(agreeCallback: () -> Unit = { }, disagreeCallback: () -> Unit = { }) {
|
||||
val myMessage = buildMessage()
|
||||
|
||||
val builder = AlertDialog.Builder(context)
|
||||
.setPositiveButton("agree") { _, _ ->
|
||||
agreeCallback()
|
||||
}
|
||||
.setNegativeButton("disagree") { _, _ ->
|
||||
disagreeCallback()
|
||||
}
|
||||
.setMessage(myMessage)
|
||||
.setCancelable(false)
|
||||
|
||||
dialog = builder.create()
|
||||
dialog.show()
|
||||
|
||||
// Make the textview clickable. Must be called after show()
|
||||
val msgTxt = dialog.findViewById<View>(android.R.id.message) as TextView?
|
||||
msgTxt?.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
fun dismiss() = dialog.dismiss()
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.ui
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
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.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST
|
||||
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.utils.hide
|
||||
import com.appttude.h_mal.atlas_weather.utils.show
|
||||
import com.appttude.h_mal.atlas_weather.viewmodel.ApplicationViewModelFactory
|
||||
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
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
abstract class BaseFragment(@LayoutRes contentLayoutId: Int) : Fragment(contentLayoutId), KodeinAware {
|
||||
|
||||
override val kodein by kodein()
|
||||
val factory by instance<ApplicationViewModelFactory>()
|
||||
|
||||
inline fun <reified VM : ViewModel> getFragmentViewModel(): Lazy<VM> = viewModels { factory }
|
||||
|
||||
private var shortAnimationDuration by Delegates.notNull<Int>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
}
|
||||
|
||||
// toggle visibility of progress spinner while async operations are taking place
|
||||
fun progressBarStateObserver(progressBar: View) = Observer<Event<Boolean>> {
|
||||
when (it.getContentIfNotHandled()) {
|
||||
true -> progressBar.fadeIn()
|
||||
false -> progressBar.fadeOut()
|
||||
}
|
||||
}
|
||||
|
||||
// display a toast when operation fails
|
||||
fun errorObserver() = Observer<Event<String>> {
|
||||
it.getContentIfNotHandled()?.let { message ->
|
||||
displayToast(message)
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshObserver(refresher: SwipeRefreshLayout) = Observer<Event<Boolean>> {
|
||||
refresher.isRefreshing = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.fadeIn() {
|
||||
apply {
|
||||
// Set the content view to 0% opacity but visible, so that it is visible
|
||||
// (but fully transparent) during the animation.
|
||||
alpha = 0f
|
||||
hide()
|
||||
|
||||
// Animate the content view to 100% opacity, and clear any animation
|
||||
// listener set on the view.
|
||||
animate()
|
||||
.alpha(1f)
|
||||
.setDuration(shortAnimationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
show()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.fadeOut() {
|
||||
apply {
|
||||
// Set the content view to 0% opacity but visible, so that it is visible
|
||||
// (but fully transparent) during the animation.
|
||||
alpha = 1f
|
||||
show()
|
||||
|
||||
// Animate the content view to 100% opacity, and clear any animation
|
||||
// listener set on the view.
|
||||
animate()
|
||||
.alpha(0f)
|
||||
.setDuration(shortAnimationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, 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() {}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.ui
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
|
||||
class EmptyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
|
||||
val icon: ImageView = itemView.findViewById(R.id.icon)
|
||||
var bodyTV: TextView = itemView.findViewById(R.id.body_text)
|
||||
var headerTV: TextView = itemView.findViewById(R.id.header_text)
|
||||
|
||||
fun bindData(@DrawableRes imageRes: Int?,header: String, body: String){
|
||||
imageRes?.let { icon.setImageResource(it) }
|
||||
headerTV.text = header
|
||||
bodyTV.text = body
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.appttude.h_mal.atlas_weather.monoWeather.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.monoWeather.ui.settings.UnitSettingsActivity
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import kotlinx.android.synthetic.main.activity_main_navigation.*
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var navHost: NavHostFragment
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main_navigation)
|
||||
|
||||
val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
navHost = supportFragmentManager
|
||||
.findFragmentById(R.id.container) as NavHostFragment
|
||||
val navController = navHost.navController
|
||||
navController.setGraph(R.navigation.main_navigation)
|
||||
|
||||
setupBottomBar(navView, navController)
|
||||
}
|
||||
|
||||
private fun setupBottomBar(navView: BottomNavigationView, navController: NavController) {
|
||||
val tabs = setOf(R.id.nav_home, R.id.nav_world)
|
||||
val appBarConfiguration = AppBarConfiguration(tabs)
|
||||
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
navView.setupWithNavController(navController)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
val i = Intent(this, UnitSettingsActivity::class.java)
|
||||
startActivity(i)
|
||||
return true
|
||||
}
|
||||
android.R.id.home -> onBackPressed()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user