Refactor flavours (#17)

- Fastlane completed
 - Circleci config completed
 - Flavours build completed
This commit is contained in:
2023-08-07 20:17:08 +01:00
committed by GitHub
parent 4a37b724a6
commit baabebd40d
121 changed files with 1326 additions and 1586 deletions

View File

@@ -2,9 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:name="com.appttude.h_mal.atlas_weather.application.AppClass"
android:allowBackup="true"
@@ -15,7 +12,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.atlas_weather.ui.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar"
@@ -27,18 +24,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.atlas_weather.widget.NewAppWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -52,7 +46,7 @@
</receiver>
<service
android:name="com.appttude.h_mal.atlas_weather.monoWeather.widget.WidgetJobServiceIntent"
android:name="com.appttude.h_mal.atlas_weather.widget.WidgetJobServiceIntent"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
</application>

View File

@@ -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() {}
}

View File

@@ -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)
}
}

View File

@@ -1,53 +0,0 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.settings
import android.appwidget.AppWidgetManager
import android.content.ComponentName
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.monoWeather.widget.NewAppWidget
class UnitSettingsActivity : PreferenceActivity() {
private var prefListener: OnSharedPreferenceChangeListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PreferenceManager.setDefaultValues(this, R.xml.prefs_screen, 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 == "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)
}
class MyPreferenceFragment : PreferenceFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.prefs_screen)
}
}
}

View File

@@ -1,79 +0,0 @@
package com.appttude.h_mal.atlas_weather.monoWeather.widget
import android.app.Activity
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.TaskStackBuilder
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Intent
import android.os.Build
import android.widget.RemoteViews
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.core.app.JobIntentService
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseWidgetServiceIntentClass<T : AppWidgetProvider> : JobIntentService() {
lateinit var appWidgetManager: AppWidgetManager
lateinit var appWidgetIds: IntArray
fun initBaseWidget(componentName: ComponentName) {
appWidgetManager = AppWidgetManager.getInstance(baseContext)
appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
}
fun createRemoteView(@LayoutRes id: Int): RemoteViews {
return RemoteViews(packageName, id)
}
// Create pending intent commonly used for 'click to update' features
fun createUpdatePendingIntent(
appWidgetProvider: Class<T>,
appWidgetId: Int
): PendingIntent? {
val seconds = (System.currentTimeMillis() / 1000L).toInt()
val intentUpdate = Intent(applicationContext, appWidgetProvider)
intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val idArray = intArrayOf(appWidgetId)
intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getBroadcast(this, seconds, intentUpdate, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
} else {
PendingIntent.getBroadcast(this, seconds, intentUpdate, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
/**
* create a pending intent used to navigate to activity:
* @param activityClass
*/
fun <T : Activity> createClickingPendingIntent(activityClass: Class<T>): PendingIntent {
val clickIntentTemplate = Intent(this, activityClass)
return TaskStackBuilder.create(this)
.addNextIntentWithParentStack(clickIntentTemplate)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
fun setImageView(
path: String?,
views: RemoteViews,
@IdRes viewId: Int,
appWidgetId: Int
) {
CoroutineScope(Dispatchers.Main).launch {
Picasso.get().load(path).into(views, viewId, intArrayOf(appWidgetId))
}
}
open fun bindView(widgetId: Int, views: RemoteViews, data: Any?) {}
open fun bindEmptyView(widgetId: Int, views: RemoteViews, data: Any?) {}
open fun bindErrorView(widgetId: Int, views: RemoteViews, data: Any?) {}
}

View File

@@ -1,33 +0,0 @@
package com.appttude.h_mal.atlas_weather.monoWeather.widget
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.appttude.h_mal.atlas_weather.monoWeather.widget.WidgetJobServiceIntent.Companion.enqueueWork
/**
* Implementation of App Widget functionality.
*/
class NewAppWidget : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
loadWidget(context)
}
override fun onEnabled(context: Context) {
super.onEnabled(context)
loadWidget(context)
}
override fun onDisabled(context: Context) { }
private fun loadWidget(context: Context){
val mIntent = Intent(context, WidgetJobServiceIntent::class.java)
enqueueWork(context, mIntent)
}
}

View File

@@ -1,220 +0,0 @@
package com.appttude.h_mal.atlas_weather.monoWeather.widget
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.icu.text.SimpleDateFormat
import android.os.PowerManager
import android.widget.RemoteViews
import android.os.Build
import androidx.core.app.ActivityCompat.checkSelfPermission
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.InnerWidgetCellData
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
import com.appttude.h_mal.atlas_weather.monoWeather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.monoWeather.widget.WidgetState.*
import com.appttude.h_mal.atlas_weather.monoWeather.widget.WidgetState.Companion.getWidgetState
import com.appttude.h_mal.atlas_weather.utils.isInternetAvailable
import com.appttude.h_mal.atlas_weather.utils.tryOrNullSuspended
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
import java.util.*
/**
* Implementation of a JobIntentService used for home screen widget
*/
const val HALF_DAY = 43200000L
class WidgetJobServiceIntent : BaseWidgetServiceIntentClass<NewAppWidget>() {
private val kodein = LateInitKodein()
private val helper: ServicesHelper by kodein.instance()
override fun onHandleWork(intent: Intent) {
// We have received work to do. The system or framework is already
// holding a wake lock for us at this point, so we can just go.
kodein.baseKodein = (applicationContext as KodeinAware).kodein
executeWidgetUpdate()
}
private fun executeWidgetUpdate() {
val componentName = ComponentName(this, NewAppWidget::class.java)
initBaseWidget(componentName)
initiateWidgetUpdate(getCurrentWidgetState())
}
private fun initiateWidgetUpdate(state: WidgetState) {
when (state) {
NO_LOCATION, SCREEN_ON_CONNECTION_UNAVAILABLE -> updateErrorWidget(state)
SCREEN_ON_CONNECTION_AVAILABLE -> updateWidget(false)
SCREEN_OFF_CONNECTION_AVAILABLE -> updateWidget(true)
SCREEN_OFF_CONNECTION_UNAVAILABLE -> return
}
}
private fun updateWidget(fromStorage: Boolean) {
CoroutineScope(Dispatchers.IO).launch {
val result = getWidgetWeather(fromStorage)
appWidgetIds.forEach { id -> setupView(id, result) }
}
}
private fun updateErrorWidget(state: WidgetState) {
appWidgetIds.forEach { id -> setEmptyView(id, state) }
}
private fun getCurrentWidgetState(): WidgetState {
val pm = getSystemService(POWER_SERVICE) as PowerManager
val isScreenOn = pm.isInteractive
val locationGranted =
checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED
val internetAvailable = isInternetAvailable(this.applicationContext)
return getWidgetState(locationGranted, isScreenOn, internetAvailable)
}
@SuppressLint("MissingPermission")
suspend fun getWidgetWeather(storageOnly: Boolean): WidgetWeatherCollection? {
return tryOrNullSuspended {
if (!storageOnly) helper.fetchData()
helper.getWidgetWeatherCollection()
}
}
private fun setEmptyView(appWidgetId: Int, state: WidgetState) {
val error = when (state) {
NO_LOCATION -> "No Location Permission"
SCREEN_ON_CONNECTION_UNAVAILABLE -> "No network available"
else -> "No data"
}
val views = createRemoteView(R.layout.weather_app_widget)
bindErrorView(appWidgetId, views, error)
}
private fun setupView(
appWidgetId: Int,
collection: WidgetWeatherCollection?
) {
val views = createRemoteView(R.layout.weather_app_widget)
setLastUpdated(views, collection?.widgetData?.timeStamp)
views.setInt(R.id.whole_widget_view, "setBackgroundColor", helper.getWidgetBackground())
if (collection != null) {
bindView(appWidgetId, views, collection)
} else {
bindEmptyView(appWidgetId, views, "No weather available")
}
}
override fun bindErrorView(
widgetId: Int,
views: RemoteViews,
data: Any?
) {
bindEmptyView(widgetId, views, data)
}
override fun bindEmptyView(
widgetId: Int,
views: RemoteViews,
data: Any?
) {
val clickUpdate = createUpdatePendingIntent(NewAppWidget::class.java, widgetId)
views.apply {
setTextViewText(R.id.widget_current_location, data as String)
setImageViewResource(R.id.widget_current_icon, R.drawable.ic_baseline_cloud_off_24)
setImageViewResource(R.id.location_icon, 0)
setTextViewText(R.id.widget_main_temp, "")
setTextViewText(R.id.widget_feel_temp, "")
setOnClickPendingIntent(R.id.widget_current_icon, clickUpdate)
setOnClickPendingIntent(R.id.widget_current_location, clickUpdate)
appWidgetManager.updateAppWidget(widgetId, this)
}
}
override fun bindView(widgetId: Int, views: RemoteViews, data: Any?) {
val clickUpdate = createUpdatePendingIntent(NewAppWidget::class.java, widgetId)
val clickToMain = createClickingPendingIntent(MainActivity::class.java)
val collection = data as WidgetWeatherCollection
val weather = collection.widgetData
views.apply {
setTextViewText(R.id.widget_main_temp, weather.currentTemp)
setTextViewText(R.id.widget_feel_temp, "°C")
setTextViewText(R.id.widget_current_location, weather.location)
setImageViewResource(R.id.location_icon, R.drawable.location_flag)
setImageView(weather.icon, this, R.id.widget_current_icon, widgetId)
setOnClickPendingIntent(R.id.widget_current_icon, clickUpdate)
setOnClickPendingIntent(R.id.widget_current_location, clickUpdate)
loadCells(widgetId, this, collection.forecast, clickToMain)
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(widgetId, views)
}
}
private fun loadCells(
appWidgetId: Int,
remoteViews: RemoteViews,
weather: List<InnerWidgetCellData>,
clickIntent: PendingIntent
) {
(0..4).forEach { i ->
val containerId: Int = resources.getIdentifier("widget_item_$i", "id", packageName)
val dayId: Int = resources.getIdentifier("widget_item_day_$i", "id", packageName)
val imageId: Int = resources.getIdentifier("widget_item_image_$i", "id", packageName)
val tempId: Int = resources.getIdentifier("widget_item_temp_high_$i", "id", packageName)
val it = weather[i]
remoteViews.setTextViewText(dayId, it.date)
remoteViews.setTextViewText(tempId, it.highTemp)
setImageView(it.icon, remoteViews, imageId, appWidgetId)
remoteViews.setOnClickPendingIntent(containerId, clickIntent)
}
}
private fun setLastUpdated(views: RemoteViews, timeStamp: Long?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && timeStamp != null) {
val difference = System.currentTimeMillis().minus(timeStamp)
val status = if (difference > HALF_DAY) {
"12hrs ago"
} else {
val date = Date(timeStamp)
val sdf = SimpleDateFormat("HH:mm", Locale.getDefault())
sdf.format(date)
}
views.setTextViewText(R.id.widget_current_status, "last updated: $status")
}
}
companion object {
/**
* Unique job ID for this service.
*/
private const val JOB_ID = 1000
/**
* Convenience method for enqueuing work in to this service.
*/
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, WidgetJobServiceIntent::class.java, JOB_ID, work)
}
}
}

View File

@@ -1,28 +0,0 @@
package com.appttude.h_mal.atlas_weather.monoWeather.widget
enum class WidgetState {
NO_LOCATION,
SCREEN_ON_CONNECTION_AVAILABLE,
SCREEN_ON_CONNECTION_UNAVAILABLE,
SCREEN_OFF_CONNECTION_AVAILABLE,
SCREEN_OFF_CONNECTION_UNAVAILABLE;
companion object {
fun getWidgetState(
locationAvailable: Boolean,
screenOn: Boolean,
connectionAvailable: Boolean
): WidgetState {
return if (!locationAvailable)
NO_LOCATION
else if (screenOn && connectionAvailable)
SCREEN_ON_CONNECTION_AVAILABLE
else if (screenOn && !connectionAvailable)
SCREEN_ON_CONNECTION_UNAVAILABLE
else if (!screenOn && connectionAvailable)
SCREEN_OFF_CONNECTION_AVAILABLE
else
SCREEN_OFF_CONNECTION_UNAVAILABLE
}
}
}

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.atlas_weather.ui
import com.appttude.h_mal.atlas_weather.R
val tabs = setOf(R.id.nav_home, R.id.nav_world)

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.dialog
package com.appttude.h_mal.monoWeather.dialog
import android.content.Context
import android.content.res.Resources
@@ -14,7 +14,6 @@ interface DeclarationBuilder{
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"

View File

@@ -1,13 +1,10 @@
package com.appttude.h_mal.atlas_weather.monoWeather.dialog
package com.appttude.h_mal.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) {

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui
package com.appttude.h_mal.monoWeather.ui
import android.view.View
import android.widget.ImageView

View File

@@ -1,14 +1,12 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui
package com.appttude.h_mal.monoWeather.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import kotlinx.android.synthetic.main.fragment_home.*

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.details
package com.appttude.h_mal.monoWeather.ui.details
import android.os.Bundle
import android.view.LayoutInflater

View File

@@ -1,22 +1,21 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home
package com.appttude.h_mal.monoWeather.ui.home
import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.observe
import androidx.navigation.Navigation.findNavController
import androidx.navigation.ui.onNavDestinationSelected
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.application.LOCATION_PERMISSION_REQUEST
import com.appttude.h_mal.atlas_weather.model.forecast.Forecast
import com.appttude.h_mal.atlas_weather.monoWeather.dialog.PermissionsDeclarationDialog
import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.atlas_weather.utils.displayToast
import com.appttude.h_mal.monoWeather.dialog.PermissionsDeclarationDialog
import com.appttude.h_mal.monoWeather.ui.BaseFragment
import com.appttude.h_mal.monoWeather.ui.home.adapter.WeatherRecyclerAdapter
import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.MainViewModel
import kotlinx.android.synthetic.main.fragment_home.*
@@ -35,6 +34,7 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) {
@SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
val recyclerAdapter = WeatherRecyclerAdapter(itemClick = {
navigateToFurtherDetails(it)
@@ -86,4 +86,14 @@ class HomeFragment : BaseFragment(R.layout.fragment_home) {
.actionHomeFragmentToFurtherDetailsFragment(forecast)
navigateTo(directions)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate the menu; this adds items to the action bar if it is present.
inflater.inflate(R.menu.menu_main, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController = findNavController(requireActivity(), R.id.container)
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter
package com.appttude.h_mal.monoWeather.ui.home.adapter
import android.view.View
import android.widget.ImageView

View File

@@ -1,14 +1,14 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter
package com.appttude.h_mal.monoWeather.ui.home.adapter
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.monoWeather.ui.EmptyViewHolder
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecast.ViewHolderForecast
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
import com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
import com.appttude.h_mal.monoWeather.ui.EmptyViewHolder
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecast.ViewHolderForecast
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
import com.appttude.h_mal.atlas_weather.utils.generateView
class WeatherRecyclerAdapter(

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecast
package com.appttude.h_mal.monoWeather.ui.home.adapter.forecast
import android.view.View
import android.widget.ImageView

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecast
package com.appttude.h_mal.monoWeather.ui.home.adapter.forecast
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecast
package com.appttude.h_mal.monoWeather.ui.home.adapter.forecast
import android.view.View
import androidx.recyclerview.widget.RecyclerView

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.forecastDaily
package com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily
import android.view.View
import android.widget.ImageView

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.further
package com.appttude.h_mal.monoWeather.ui.home.adapter.further
import android.content.Context
import android.view.View

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.home.adapter.further
package com.appttude.h_mal.monoWeather.ui.home.adapter.further
import android.view.View
import android.widget.GridView

View File

@@ -0,0 +1,41 @@
package com.appttude.h_mal.monoWeather.ui.settings
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.widget.NewAppWidget
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.prefs_screen, rootKey)
//listener on changed sort order preference:
val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
prefs.registerOnSharedPreferenceChangeListener { _, key ->
if (key == "temp_units") {
val intent = Intent(requireContext(), NewAppWidget::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
val ids = AppWidgetManager.getInstance(requireContext())
.getAppWidgetIds(ComponentName(requireContext(), NewAppWidget::class.java))
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
requireContext().sendBroadcast(intent)
}
if (key == "widget_black_background") {
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
val widgetManager = AppWidgetManager.getInstance(requireContext())
val ids =
widgetManager.getAppWidgetIds(ComponentName(requireContext(), NewAppWidget::class.java))
AppWidgetManager.getInstance(requireContext())
.notifyAppWidgetViewDataChanged(ids, R.id.whole_widget_view)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
requireContext().sendBroadcast(intent)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.widget
package com.appttude.h_mal.monoWeather.ui.widget
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.app.Activity
@@ -11,7 +11,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat.checkSelfPermission
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.monoWeather.dialog.DeclarationBuilder
import com.appttude.h_mal.monoWeather.dialog.DeclarationBuilder
import com.appttude.h_mal.atlas_weather.utils.displayToast
import kotlinx.android.synthetic.monoWeather.permissions_declaration_dialog.*

View File

@@ -1,12 +1,10 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.world
package com.appttude.h_mal.monoWeather.ui.world
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.observe
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment
import com.appttude.h_mal.monoWeather.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.utils.hideKeyboard

View File

@@ -1,15 +1,13 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.world
package com.appttude.h_mal.monoWeather.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.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager
import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.monoWeather.ui.BaseFragment
import com.appttude.h_mal.atlas_weather.monoWeather.ui.world.WorldFragmentDirections.actionWorldFragmentToWorldItemFragment
import com.appttude.h_mal.monoWeather.ui.BaseFragment
import com.appttude.h_mal.monoWeather.ui.world.WorldFragmentDirections.actionWorldFragmentToWorldItemFragment
import com.appttude.h_mal.atlas_weather.utils.navigateTo
import com.appttude.h_mal.atlas_weather.viewmodel.WorldViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.atlas_weather.monoWeather.ui.world
package com.appttude.h_mal.monoWeather.ui.world
import android.view.View
import android.view.ViewGroup
@@ -7,7 +7,7 @@ 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.monoWeather.ui.EmptyViewHolder
import com.appttude.h_mal.monoWeather.ui.EmptyViewHolder
import com.appttude.h_mal.atlas_weather.utils.generateView
import com.appttude.h_mal.atlas_weather.utils.loadImage
@@ -93,13 +93,9 @@ class WorldRecyclerAdapter(
avgTempTV.text = data?.forecast?.get(0)?.mainTemp
tempUnit.text = itemView.context.getString(R.string.degrees)
}
}
abstract class BaseViewHolder<T : Any>(cellView: View) : RecyclerView.ViewHolder(cellView) {
abstract fun bindData(data : T?)
}
}

View File

@@ -7,68 +7,57 @@
<fragment
android:id="@+id/nav_home"
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.home.HomeFragment"
android:name="com.appttude.h_mal.monoWeather.ui.home.HomeFragment"
android:label="Home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_homeFragment_to_furtherDetailsFragment"
app:destination="@id/furtherDetailsFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
app:destination="@id/furtherDetailsFragment"/>
</fragment>
<fragment
android:id="@+id/furtherDetailsFragment"
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.details.FurtherInfoFragment"
android:name="com.appttude.h_mal.monoWeather.ui.details.FurtherInfoFragment"
android:label="Further Details">
<argument
android:name="forecast"
app:argType="com.appttude.h_mal.atlas_weather.model.forecast.Forecast" />
app:argType="com.appttude.h_mal.atlas_weather.model.forecast.Forecast"
app:nullable="true"/>
</fragment>
<fragment
android:id="@+id/nav_world"
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.world.WorldFragment"
android:name="com.appttude.h_mal.monoWeather.ui.world.WorldFragment"
android:label="World"
tools:layout="@layout/fragment__two">
<action
android:id="@+id/action_worldFragment_to_addLocationFragment"
app:destination="@id/addLocationFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
app:destination="@id/addLocationFragment"/>
<action
android:id="@+id/action_worldFragment_to_worldItemFragment"
app:destination="@id/worldItemFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
app:destination="@id/worldItemFragment"/>
</fragment>
<fragment
android:id="@+id/addLocationFragment"
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.world.AddLocationFragment"
android:name="com.appttude.h_mal.monoWeather.ui.world.AddLocationFragment"
android:label="Add Weather Location"
tools:layout="@layout/activity_add_forecast" />
<fragment
android:id="@+id/worldItemFragment"
android:name="com.appttude.h_mal.atlas_weather.monoWeather.ui.WorldItemFragment"
android:name="com.appttude.h_mal.monoWeather.ui.WorldItemFragment"
android:label="Overview"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_worldItemFragment_to_furtherDetailsFragment"
app:destination="@id/furtherDetailsFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_open_enter"
app:popExitAnim="@anim/fragment_open_exit" />
app:destination="@id/furtherDetailsFragment" />
<argument
android:name="locationName"
app:argType="string"
app:nullable="true" />
</fragment>
<fragment
android:id="@+id/settings_fragment"
android:name="com.appttude.h_mal.monoWeather.ui.settings.SettingsFragment"
android:label="SettingsFragment" />
</navigation>

View File

@@ -0,0 +1,12 @@
<resources>
<!-- Reply Preference -->
<string-array name="reply_entries">
<item>Reply</item>
<item>Reply to all</item>
</string-array>
<string-array name="reply_values">
<item>reply</item>
<item>reply_all</item>
</string-array>
</resources>

View File

@@ -10,4 +10,18 @@
<string name="title_world">World</string>
<string name="widget_declaration">When using the app widget with app will require the use of background location services. Just to confirm the location data is used just to provide the end user with widget data. The use of background location permission is to update the widget at 30 minute intervals. </string>
<!-- Preference Titles -->
<string name="messages_header">Messages</string>
<string name="sync_header">Sync</string>
<!-- Messages Preferences -->
<string name="signature_title">Your signature</string>
<string name="reply_title">Default reply action</string>
<!-- Sync Preferences -->
<string name="sync_title">Sync email periodically</string>
<string name="attachment_title">Download incoming attachments</string>
<string name="attachment_summary_on">Automatically download attachments for incoming emails
</string>
<string name="attachment_summary_off">Only download attachments when manually requested</string>
</resources>

View File

@@ -66,12 +66,4 @@
<item name="android:textSize">32sp</item>
</style>
<style name="icon_style__further_deatils">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">48dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:layout_gravity">center</item>
<item name="android:tint">@color/colorAccent</item>
</style>
</resources>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.appttude.h_mal.monoWeather.ui.widget.WidgetLocationPermissionActivity"
android:initialKeyguardLayout="@layout/weather_app_widget"
android:initialLayout="@layout/weather_app_widget"
android:minHeight="110.0dp"
android:minWidth="320.0dp"
android:minResizeWidth="320.0dp"
android:minResizeHeight="110.0dp"
android:previewImage="@drawable/widget_screenshot"
android:updatePeriodMillis="1800000"
android:resizeMode="vertical"
android:widgetCategory="home_screen">
</appwidget-provider>

View File

@@ -0,0 +1,35 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/messages_header">
<EditTextPreference
app:key="signature"
app:title="@string/signature_title"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="reply"
app:entries="@array/reply_entries"
app:entryValues="@array/reply_values"
app:key="reply"
app:title="@string/reply_title"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/sync_header">
<SwitchPreferenceCompat
app:key="sync"
app:title="@string/sync_title" />
<SwitchPreferenceCompat
app:dependency="sync"
app:key="attachment"
app:summaryOff="@string/attachment_summary_off"
app:summaryOn="@string/attachment_summary_on"
app:title="@string/attachment_title" />
</PreferenceCategory>
</PreferenceScreen>