Compare commits

...

34 Commits

Author SHA1 Message Date
4ab12e33c7 Beta release v2.0.14 2020-10-11 22:09:27 +02:00
364198ef08 Update the providers system 2020-10-11 22:05:20 +02:00
2c81c7cfd2 Merge pull request #197 from d-l-n/master
Translations fix
2020-10-11 22:04:06 +02:00
3b723e5a1b Merge pull request #200 from Drumber/translation
Update German translation
2020-10-11 22:03:45 +02:00
6a912ee003 Add a few more providers and fix multiple bugs 2020-10-11 21:59:44 +02:00
1644fb7682 Update the providers 2020-10-06 18:11:53 +02:00
bd4869540b Connect weather.gov api 2020-10-05 19:07:52 +02:00
31103303be Update German translation 2020-10-05 18:06:46 +02:00
e9c0cdd61c Merge 2020-10-05 14:36:56 +02:00
59e42029e8 Merge and move google sans in the custom font activity 2020-10-05 12:31:49 +02:00
b2ef2adb2a Update the weather icons 2020-10-05 11:36:33 +02:00
bab92f9169 Add music players filter, fix #188 2020-10-05 11:09:11 +02:00
cb480ed4ee Fix the events order, fix #198 2020-10-05 09:43:31 +02:00
6f83a45865 Merge branch 'master' of github.com:tommasoberlose/another-widget into font 2020-10-05 00:59:47 +02:00
9e528e1f6f Update the icons 2020-10-05 00:59:37 +02:00
20d3ae0e32 Bugfix 2020-10-05 00:46:29 +02:00
ecea1265e7 Show the variant 2020-10-04 21:23:51 +02:00
c38b7a335c Add the font variant 2020-10-04 21:19:22 +02:00
6ab8e40d45 Add downloadable fonts 2020-10-04 20:39:52 +02:00
d14dfee980 Update the provider activity 2020-10-04 18:02:08 +02:00
0a454f0b5f Merge branch 'master' of github.com:tommasoberlose/another-widget into providers 2020-10-04 11:44:21 +02:00
b7bc93e174 Change the reset of the google steps 2020-10-04 11:44:05 +02:00
1dee4cc8e5 Add some providers 2020-10-04 11:43:36 +02:00
02b521c0b8 Fixed some translations 2020-10-03 21:42:15 -03:00
c04040e242 Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-10-03 17:36:41 +02:00
a208aa97b2 Merge pull request #196 from CoreyVidal/master
Replaced Product Sans with Google Sans
2020-10-03 17:36:28 +02:00
e3fda9457e Merge branch 'master' of github.com:tommasoberlose/another-widget 2020-10-03 17:32:18 +02:00
b84631a2b2 Add privacy policy link 2020-10-03 17:32:03 +02:00
bad06c5762 Replaced Product Sans with Google Sans
Product Sans is designed to be used for Google product logos.
Google Sans is designed to be used for title, header, and body text.
2020-10-03 11:06:08 -04:00
74d8966f0a Merge pull request #195 from kaprijela/master
Add Slovak translation
2020-10-03 01:27:25 +02:00
d8a76936a6 Remove tos 2020-10-03 01:24:40 +02:00
e72ca4fc6f Create the privacy policy 2020-10-03 01:23:40 +02:00
dd62212b06 Add Slovak translation 2020-10-02 20:26:45 +02:00
8dfc12e412 Update the README 2020-10-02 19:50:09 +02:00
367 changed files with 3477 additions and 552 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@
.externalNativeBuild
/tasksintegration/build
/app/google-services.json
apikey.properties

Binary file not shown.

View File

@ -17,6 +17,7 @@
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">

View File

@ -11,7 +11,7 @@ While respecting the design of the application, there is a great opportunity to
Also, as much as possible, there are always updates and new features in the short run.
Help me developing with feedback and support me on how you can.
<div style="text-align:center"><a href="https://play.google.com/store/apps/details?id=com.tommasoberlose.anotherwidget" target="_blank"><img src="google-play-badge.png" height="100" /></a></div>
<div style="text-align:center"><a href='https://play.google.com/store/apps/details?id=com.tommasoberlose.anotherwidget&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height='100px' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png'/></a></div>
Help with translations

View File

@ -10,7 +10,12 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'realm-android'
def apikeyPropertiesFile = rootProject.file("apikey.properties")
def apikeyProperties = new Properties()
apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
@ -18,10 +23,11 @@ android {
applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23
targetSdkVersion 29
versionCode 105
versionName "2.0.13"
versionCode 106
versionName "2.0.14"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
}
buildTypes {
@ -112,6 +118,16 @@ dependencies {
implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2'
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.0.1"
//Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
@ -122,4 +138,7 @@ dependencies {
// Permissions
implementation 'com.karumi:dexter:6.1.0'
// Fonts
implementation 'com.github.firatkarababa:downloadable-font-list-library:1.0.2'
}

Binary file not shown.

View File

@ -22,6 +22,7 @@
android:name=".AWApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.MainActivity" android:launchMode="singleInstance" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
@ -32,11 +33,13 @@
</intent-filter>
</activity>
<activity android:name=".ui.activities.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,6 +2,7 @@ package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -9,6 +10,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.tommasoberlose.anotherwidget.R
import kotlinx.android.synthetic.main.bottom_sheet_menu.view.*
import kotlinx.android.synthetic.main.bottom_sheet_menu_item.view.*
import org.w3c.dom.Text
/**
* [BottomSheetDialogFragment] that uses a custom
@ -32,8 +34,8 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
return this
}
fun addItem(title: String, value: T? = null): BottomSheetMenu<T> {
items.add(MenuItem(title, value))
fun addItem(title: String, value: T? = null, renderCallback: ((view: TextView) -> Unit)? = null): BottomSheetMenu<T> {
items.add(MenuItem(title, value, renderCallback))
return this
}
@ -94,7 +96,10 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
) else ContextCompat.getColor(context, R.color.colorSecondaryText)
)
}
item.renderCallback?.invoke(itemView.label)
}
view.menu.addView(itemView)
} else {
val itemView = View.inflate(context, R.layout.bottom_sheet_menu_divider, null)
@ -106,6 +111,6 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
super.show()
}
class MenuItem<T>(val title: String, val value: T? = null)
class MenuItem<T>(val title: String, val value: T? = null, val renderCallback: ((view: TextView) -> Unit)? = null)
}

View File

@ -0,0 +1,64 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.view.View
import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.utils.openURI
import kotlinx.android.synthetic.main.weather_provider_settings_layout.view.*
class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
init {
val view = View.inflate(context, R.layout.weather_provider_settings_layout, null)
view.api_key_container.isVisible = WeatherHelper.isKeyRequired()
view.action_save_key.isVisible = WeatherHelper.isKeyRequired()
WeatherHelper.getProviderInfoTitle(context).let { title ->
view.info_title.text = title
view.info_title.isVisible = title != ""
}
WeatherHelper.getProviderInfoSubtitle(context).let { subtitle ->
view.info_subtitle.text = subtitle
view.info_subtitle.isVisible = subtitle != ""
}
view.info_provider.text = WeatherHelper.getProviderName(context)
view.api_key.editText?.setText(when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather
Constants.WeatherProvider.WEATHER_GOV,
Constants.WeatherProvider.YR,
null -> ""
})
view.action_open_provider.setOnClickListener {
context.openURI(WeatherHelper.getProviderLink())
}
view.action_save_key.setOnClickListener {
val key = view.api_key.editText?.text.toString()
when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen = key
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit = key
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi = key
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere = key
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather = key
else -> {}
}
callback.invoke()
dismiss()
}
setContentView(view)
}
}

View File

@ -32,10 +32,10 @@ class IconPackSelector(context: Context, private val header: String? = null) : B
itemView.label.text = context.getString(R.string.settings_weather_icon_pack_default).format(item.value + 1)
itemView.isSelected = item.value == Preferences.weatherIconPack
itemView.icon_1.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("01d", item.value)))
itemView.icon_2.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("01n", item.value)))
itemView.icon_3.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("10d", item.value)))
itemView.icon_4.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource("09n", item.value)))
itemView.icon_1.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01d", item.value)))
itemView.icon_2.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "01n", item.value)))
itemView.icon_3.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "10d", item.value)))
itemView.icon_4.setImageDrawable(ContextCompat.getDrawable(context, WeatherHelper.getWeatherIconResource(context, "09n", item.value)))
listOf<ImageView>(itemView.icon_1, itemView.icon_2, itemView.icon_3, itemView.icon_4).forEach {
if (item == Constants.WeatherIconPack.MINIMAL) {

View File

@ -7,7 +7,10 @@ object Constants {
const val RESULT_APP_NAME = "RESULT_APP_NAME"
const val RESULT_APP_PACKAGE = "RESULT_APP_PACKAGE"
const val CUSTOM_FONT_PRODUCT_SANS = 1
const val CUSTOM_FONT_GOOGLE_SANS = 1
const val CUSTOM_FONT_DOWNLOADED = 2
const val CUSTOM_FONT_DOWNLOAD_NEW = 3
enum class ClockBottomMargin(val value: Int) {
NONE(0),
SMALL(1),
@ -36,6 +39,21 @@ object Constants {
HIGH(2)
}
enum class WeatherProvider(val value: Int) {
OPEN_WEATHER(0),
WEATHER_BIT(1),
WEATHER_API(2),
HERE(3),
ACCUWEATHER(4),
WEATHER_GOV(5),
YR(6);
companion object {
private val map = WeatherProvider.values().associateBy(WeatherProvider::value)
fun fromInt(type: Int) = map[type]
}
}
enum class WeatherIconPack(val value: Int) {
DEFAULT(0),
MINIMAL(1),

View File

@ -38,7 +38,14 @@ object Preferences : KotprefModel() {
var calendarAppPackage by stringPref(key = "PREF_CALENDAR_APP_PACKAGE", default = "")
var weatherAppName by stringPref(key = "PREF_WEATHER_APP_NAME", default = "")
var weatherAppPackage by stringPref(key = "PREF_WEATHER_APP_PACKAGE", default = "")
var weatherProviderApi by stringPref(key = "PREF_WEATHER_PROVIDER_API_KEY", default = "")
var weatherProviderApiOpen by stringPref(key = "PREF_WEATHER_PROVIDER_API_KEY", default = "")
var weatherProviderApiHere by stringPref(default = "")
var weatherProviderApiWeatherApi by stringPref(default = "")
var weatherProviderApiWeatherBit by stringPref(default = "")
var weatherProviderApiAccuweather by stringPref(default = "")
var weatherProvider by intPref(default = Constants.WeatherProvider.OPEN_WEATHER.value)
var weatherProviderError by stringPref(default = "")
var weatherProviderLocationError by stringPref(default = "")
var eventAppName by stringPref(key = "PREF_EVENT_APP_NAME", default = "")
var eventAppPackage by stringPref(key = "PREF_EVENT_APP_PACKAGE", default = "")
var openEventDetails by booleanPref(default = true)
@ -91,8 +98,10 @@ object Preferences : KotprefModel() {
var showAcceptedEvents by booleanPref(default = true)
var showOnlyBusyEvents by booleanPref(default = false)
var secondRowInformation by intPref(key = "PREF_SECOND_ROW_INFORMATION", default = 0)
var customFont by intPref(key = "PREF_CUSTOM_FONT", default = Constants.CUSTOM_FONT_PRODUCT_SANS)
var customFontFile by stringPref(key = "PREF_CUSTOM_FONT_FILE")
var customFont by intPref(key = "PREF_CUSTOM_FONT", default = Constants.CUSTOM_FONT_GOOGLE_SANS)
var customFontFile by stringPref(default = "")
var customFontName by stringPref(default = "")
var customFontVariant by stringPref(default = "regular")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showDividers by booleanPref(default = true)
@ -121,6 +130,7 @@ object Preferences : KotprefModel() {
var mediaPlayerAlbum by stringPref(default = "")
var mediaPlayerArtist by stringPref(default = "")
var mediaPlayerPackage by stringPref(default = "")
var musicPlayersFilter by stringPref(default = "")
// Integrations
var installedIntegrations by intPref(default = 0)

View File

@ -38,6 +38,8 @@ object MediaPlayerHelper {
)
} catch (ex: Exception) {
emptyList<MediaController>()
}.filter {
Preferences.musicPlayersFilter == "" || isMusicPlayerAccepted(it.packageName)
}
if (list.isNotEmpty()) {
@ -89,4 +91,14 @@ object MediaPlayerHelper {
remove(Preferences::mediaPlayerPackage)
}
}
fun isMusicPlayerAccepted(appPkg: String): Boolean = Preferences.musicPlayersFilter.contains(appPkg)
fun toggleMusicPlayerFilter(appPkg: String) {
if (Preferences.musicPlayersFilter == "" || !Preferences.musicPlayersFilter.contains(appPkg)) {
Preferences.musicPlayersFilter = Preferences.musicPlayersFilter.split(",").union(listOf(appPkg)).joinToString(separator = ",")
} else {
Preferences.musicPlayersFilter = Preferences.musicPlayersFilter.split(",").filter { it != appPkg }.joinToString(separator = ",")
}
}
}

View File

@ -54,14 +54,27 @@ object SettingsStringHelper {
}
}
fun getCustomFontLabel(shadow: Int): Int {
return when (shadow) {
0 -> R.string.custom_font_subtitle_0
1 -> R.string.custom_font_subtitle_1
else -> R.string.custom_font_subtitle_1
fun getCustomFontLabel(context: Context, font: Int): String {
return when (font) {
Constants.CUSTOM_FONT_GOOGLE_SANS -> context.getString(R.string.custom_font_subtitle_1) + " - ${getVariantLabel(context, Preferences.customFontVariant)}"
Constants.CUSTOM_FONT_DOWNLOADED -> Preferences.customFontName + " - ${getVariantLabel(context, Preferences.customFontVariant)}"
else -> context.getString(R.string.custom_font_subtitle_0)
}
}
fun getVariantLabel(context: Context, variant: String): String = when {
variant.contains("100") -> context.getString(R.string.font_100)
variant.contains("200") -> context.getString(R.string.font_200)
variant.contains("300") -> context.getString(R.string.font_300)
variant.contains("regular") || variant.contains("400") -> context.getString(R.string.font_400)
variant.contains("500") -> context.getString(R.string.font_500)
variant.contains("600") -> context.getString(R.string.font_600)
variant.contains("700") -> context.getString(R.string.font_700)
variant.contains("800") -> context.getString(R.string.font_800)
variant.contains("900") -> context.getString(R.string.font_900)
else -> context.getString(R.string.font_400)
}
fun getDifferenceText(context: Context, now: Long, start: Long): String {
val nowDate = DateTime(now)
val eventDate = DateTime(start)

View File

@ -3,6 +3,7 @@ package com.tommasoberlose.anotherwidget.helpers
import android.Manifest
import android.content.Context
import android.os.Build
import android.util.Log
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
@ -11,6 +12,10 @@ import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
@ -20,7 +25,7 @@ import org.greenrobot.eventbus.EventBus
object WeatherHelper {
fun updateWeather(context: Context) {
suspend fun updateWeather(context: Context) {
val networkApi = WeatherNetworkApi(context)
if (Preferences.customLocationAdd != "") {
networkApi.updateWeather()
@ -31,10 +36,17 @@ object WeatherHelper {
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
networkApi.updateWeather()
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
@ -43,17 +55,92 @@ object WeatherHelper {
fun removeWeather(context: Context) {
Preferences.remove(Preferences::weatherTemp)
Preferences.remove(Preferences::weatherRealTempUnit)
Preferences.remove(Preferences::weatherIcon)
MainWidget.updateWidget(context)
}
fun getWeatherIconResource(icon: String, style: Int = Preferences.weatherIconPack): Int {
fun getProviderName(context: Context, provider: Constants.WeatherProvider = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.settings_weather_provider_open_weather
Constants.WeatherProvider.WEATHER_BIT -> R.string.settings_weather_provider_weatherbit
Constants.WeatherProvider.WEATHER_API -> R.string.settings_weather_provider_weather_api
Constants.WeatherProvider.HERE -> R.string.settings_weather_provider_here
Constants.WeatherProvider.ACCUWEATHER -> R.string.settings_weather_provider_accuweather
Constants.WeatherProvider.WEATHER_GOV -> R.string.settings_weather_provider_weather_gov
Constants.WeatherProvider.YR -> R.string.settings_weather_provider_yr
})
}
fun getProviderInfoTitle(context: Context, provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.weather_provider_info_open_weather_title
Constants.WeatherProvider.WEATHER_BIT -> R.string.weather_provider_info_weatherbit_title
Constants.WeatherProvider.WEATHER_API -> R.string.weather_provider_info_weatherapi_title
Constants.WeatherProvider.HERE -> R.string.weather_provider_info_here_title
Constants.WeatherProvider.ACCUWEATHER -> R.string.weather_provider_info_accuweather_title
Constants.WeatherProvider.WEATHER_GOV -> R.string.weather_provider_info_weather_gov_title
Constants.WeatherProvider.YR -> R.string.weather_provider_info_yr_title
else -> R.string.nothing
})
}
fun getProviderInfoSubtitle(context: Context, provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return context.getString(when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> R.string.weather_provider_info_open_weather_subtitle
Constants.WeatherProvider.WEATHER_BIT -> R.string.weather_provider_info_weatherbit_subtitle
Constants.WeatherProvider.WEATHER_API -> R.string.weather_provider_info_weatherapi_subtitle
Constants.WeatherProvider.HERE -> R.string.weather_provider_info_here_subtitle
Constants.WeatherProvider.ACCUWEATHER -> R.string.weather_provider_info_accuweather_subtitle
Constants.WeatherProvider.WEATHER_GOV -> R.string.weather_provider_info_weather_gov_subtitle
Constants.WeatherProvider.YR -> R.string.weather_provider_info_yr_subtitle
else -> R.string.nothing
})
}
fun getProviderLink(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String {
return when(provider) {
Constants.WeatherProvider.OPEN_WEATHER -> "https://home.openweathermap.org/users/sign_in"
Constants.WeatherProvider.WEATHER_BIT -> "https://www.weatherbit.io/account/login"
Constants.WeatherProvider.WEATHER_API -> "https://www.weatherapi.com/login.aspx"
Constants.WeatherProvider.HERE -> "https://developer.here.com/login"
Constants.WeatherProvider.ACCUWEATHER -> "https://developer.accuweather.com/user/login"
Constants.WeatherProvider.WEATHER_GOV -> "http://www.weather.gov/"
Constants.WeatherProvider.YR -> "https://www.yr.no/"
else -> ""
}
}
fun isKeyRequired(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): Boolean = when (provider) {
Constants.WeatherProvider.OPEN_WEATHER,
Constants.WeatherProvider.WEATHER_BIT,
Constants.WeatherProvider.WEATHER_API,
Constants.WeatherProvider.HERE,
Constants.WeatherProvider.ACCUWEATHER -> true
Constants.WeatherProvider.WEATHER_GOV,
Constants.WeatherProvider.YR -> false
else -> true
}
fun getApiKey(provider: Constants.WeatherProvider? = Constants.WeatherProvider.fromInt(Preferences.weatherProvider)!!): String = when (provider) {
Constants.WeatherProvider.OPEN_WEATHER -> Preferences.weatherProviderApiOpen
Constants.WeatherProvider.WEATHER_BIT -> Preferences.weatherProviderApiWeatherBit
Constants.WeatherProvider.WEATHER_API -> Preferences.weatherProviderApiWeatherApi
Constants.WeatherProvider.HERE -> Preferences.weatherProviderApiHere
Constants.WeatherProvider.ACCUWEATHER -> Preferences.weatherProviderApiAccuweather
Constants.WeatherProvider.WEATHER_GOV -> ""
Constants.WeatherProvider.YR -> ""
else -> ""
}
fun getWeatherIconResource(context: Context, icon: String, style: Int = Preferences.weatherIconPack): Int {
return when (icon) {
"01d" -> {
when (style) {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_day_4
else -> R.drawable.clear_day
else -> if (context.isDarkTheme()) R.drawable.clear_day_5 else R.drawable.clear_day_5_light
}
}
"02d" -> {
@ -61,7 +148,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_4
else -> R.drawable.partly_cloudy
else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_5 else R.drawable.partly_cloudy_5_light
}
}
"03d" -> {
@ -69,7 +156,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_4
else -> R.drawable.mostly_cloudy
else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_5 else R.drawable.mostly_cloudy_5_light
}
}
"04d" -> {
@ -77,7 +164,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
else -> R.drawable.cloudy_weather
else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light
}
}
"09d" -> {
@ -85,7 +172,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_day_4
else -> R.drawable.storm_weather_day
else -> if (context.isDarkTheme()) R.drawable.storm_weather_day_5 else R.drawable.storm_weather_day_5_light
}
}
"10d" -> {
@ -93,7 +180,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_day_4
else -> R.drawable.rainy_day
else -> if (context.isDarkTheme()) R.drawable.rainy_day_5 else R.drawable.rainy_day_5_light
}
}
"11d" -> {
@ -101,7 +188,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_day_4
else -> R.drawable.thunder_day
else -> if (context.isDarkTheme()) R.drawable.thunder_day_5 else R.drawable.thunder_day_5_light
}
}
"13d" -> {
@ -109,7 +196,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_day_4
else -> R.drawable.snow_day
else -> if (context.isDarkTheme()) R.drawable.snow_day_5 else R.drawable.snow_day_5_light
}
}
"50d" -> {
@ -117,7 +204,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_day_4
else -> R.drawable.haze_day
else -> if (context.isDarkTheme()) R.drawable.haze_day_5 else R.drawable.haze_day_5_light
}
}
"80d" -> {
@ -125,7 +212,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_day_4
else -> R.drawable.windy_day
else -> if (context.isDarkTheme()) R.drawable.windy_day_5 else R.drawable.windy_day_5_light
}
}
"81d" -> {
@ -133,7 +220,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_day_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_day_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_day_4
else -> R.drawable.rain_snow_day
else -> if (context.isDarkTheme()) R.drawable.rain_snow_day_5 else R.drawable.rain_snow_day_5_light
}
}
"82d" -> {
@ -141,7 +228,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
else -> R.drawable.haze_weather
else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light
}
}
@ -152,7 +239,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.clear_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.clear_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.clear_night_4
else -> R.drawable.clear_night
else -> if (context.isDarkTheme()) R.drawable.clear_night_5 else R.drawable.clear_night_5_light
}
}
"02n" -> {
@ -160,7 +247,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.partly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.partly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.partly_cloudy_night_4
else -> R.drawable.partly_cloudy_night
else -> if (context.isDarkTheme()) R.drawable.partly_cloudy_night_5 else R.drawable.partly_cloudy_night_5_light
}
}
"03n" -> {
@ -168,7 +255,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.mostly_cloudy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.mostly_cloudy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.mostly_cloudy_night_4
else -> R.drawable.mostly_cloudy_night
else -> if (context.isDarkTheme()) R.drawable.mostly_cloudy_night_5 else R.drawable.mostly_cloudy_night_5_light
}
}
"04n" -> {
@ -176,7 +263,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.cloudy_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.cloudy_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.cloudy_weather_4
else -> R.drawable.cloudy_weather
else -> if (context.isDarkTheme()) R.drawable.cloudy_weather_5 else R.drawable.cloudy_weather_5_light
}
}
"09n" -> {
@ -184,7 +271,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.storm_weather_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.storm_weather_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.storm_weather_night_4
else -> R.drawable.storm_weather_night
else -> if (context.isDarkTheme()) R.drawable.storm_weather_night_5 else R.drawable.storm_weather_night_5_light
}
}
"10n" -> {
@ -192,7 +279,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.rainy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rainy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rainy_night_4
else -> R.drawable.rainy_night
else -> if (context.isDarkTheme()) R.drawable.rainy_night_5 else R.drawable.rainy_night_5_light
}
}
"11n" -> {
@ -200,7 +287,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.thunder_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.thunder_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.thunder_night_4
else -> R.drawable.thunder_night
else -> if (context.isDarkTheme()) R.drawable.thunder_night_5 else R.drawable.thunder_night_5_light
}
}
"13n" -> {
@ -208,7 +295,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.snow_night_4
else -> R.drawable.snow_night
else -> if (context.isDarkTheme()) R.drawable.snow_night_5 else R.drawable.snow_night_5_light
}
}
"50n" -> {
@ -216,7 +303,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_night_4
else -> R.drawable.haze_night
else -> if (context.isDarkTheme()) R.drawable.haze_night_5 else R.drawable.haze_night_5_light
}
}
"80n" -> {
@ -224,7 +311,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.windy_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.windy_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.windy_night_4
else -> R.drawable.windy_night
else -> if (context.isDarkTheme()) R.drawable.windy_night_5 else R.drawable.windy_night_5_light
}
}
"81n" -> {
@ -232,7 +319,7 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.rain_snow_night_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.rain_snow_night_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.rain_snow_night_4
else -> R.drawable.rain_snow_night
else -> if (context.isDarkTheme()) R.drawable.rain_snow_night_5 else R.drawable.rain_snow_night_5_light
}
}
"82n" -> {
@ -240,12 +327,183 @@ object WeatherHelper {
Constants.WeatherIconPack.COOL.value -> R.drawable.haze_weather_3
Constants.WeatherIconPack.MINIMAL.value -> R.drawable.haze_weather_2
Constants.WeatherIconPack.GOOGLE_NEWS.value -> R.drawable.haze_weather_4
else -> R.drawable.haze_weather
else -> if (context.isDarkTheme()) R.drawable.haze_weather_5 else R.drawable.haze_weather_5_light
}
}
else -> {
return R.drawable.unknown
return if (context.isDarkTheme()) R.drawable.unknown_dark else R.drawable.unknown_light
}
}
}
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when {
iconString.contains("skc") -> "01"
iconString.contains("few") -> "02"
iconString.contains("sct") -> "03"
iconString.contains("bkn") -> "04"
iconString.contains("ovc") -> "04"
iconString.contains("wind_skc") -> "01"
iconString.contains("wind_few") -> "02"
iconString.contains("wind_sct") -> "03"
iconString.contains("wind_bkn") -> "04"
iconString.contains("wind_ovc") -> "04"
iconString.contains("snow") -> "13"
iconString.contains("rain_snow") -> "81"
iconString.contains("rain_sleet") -> "81"
iconString.contains("snow_sleet") -> "81"
iconString.contains("fzra") -> "81"
iconString.contains("rain_fzra") -> "81"
iconString.contains("snow_fzra") -> "81"
iconString.contains("sleet") -> "81"
iconString.contains("rain") -> "10"
iconString.contains("rain_showers") -> "10"
iconString.contains("rain_showers_hi") -> "10"
iconString.contains("tsra") -> "82"
iconString.contains("tsra_sct") -> "82"
iconString.contains("tsra_hi") -> "82"
iconString.contains("tornado") -> "80"
iconString.contains("hurricane") -> "80"
iconString.contains("tropical_storm") -> "09"
iconString.contains("dust") -> "Dust"
iconString.contains("smoke") -> "Smoke"
iconString.contains("haze") -> "50"
iconString.contains("hot") -> "01"
iconString.contains("cold") -> "13"
iconString.contains("blizzard") -> "80"
iconString.contains("fog") -> "82"
else -> ""
} + if (isDaytime) "d" else "n"
fun getWeatherBitIcon(iconString: String): String = when {
iconString.contains("t01") -> "11"
iconString.contains("t02") -> "09"
iconString.contains("t03") -> "09"
iconString.contains("t04") -> "09"
iconString.contains("t05") -> "09"
iconString.contains("d01") -> "10"
iconString.contains("d02") -> "10"
iconString.contains("d03") -> "10"
iconString.contains("r01") -> "10"
iconString.contains("r02") -> "10"
iconString.contains("r03") -> "10"
iconString.contains("f01") -> "10"
iconString.contains("r04") -> "10"
iconString.contains("r05") -> "10"
iconString.contains("r06") -> "10"
iconString.contains("s01") -> "13"
iconString.contains("s02") -> "13"
iconString.contains("s03") -> "13"
iconString.contains("s04") -> "81"
iconString.contains("s05") -> "90"
iconString.contains("s06") -> "13"
iconString.contains("a01") -> "82"
iconString.contains("a02") -> "82"
iconString.contains("a03") -> "82"
iconString.contains("a04") -> "82"
iconString.contains("a05") -> "82"
iconString.contains("a06") -> "82"
iconString.contains("c01") -> "01"
iconString.contains("c02") -> "02"
iconString.contains("c03") -> "04"
iconString.contains("c04") -> "04"
else -> ""
} + if (iconString.contains("d")) "d" else "n"
fun getWeatherApiIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1000 -> "01"
1003 -> "02"
1006 -> "03"
1009 -> "04"
1030 -> "82"
1063 -> "10"
1066 -> "10"
1069 -> "10"
1072 -> "81"
1087 -> "11"
1114 -> "13"
1117 -> "09"
1135 -> "82"
1147 -> "82"
1150 -> "10"
1153 -> "10"
1168 -> "10"
1171 -> "10"
1180 -> "10"
1183 -> "10"
1186 -> "10"
1189 -> "10"
1192 -> "10"
1195 -> "10"
1198 -> "81"
1201 -> "81"
1204 -> "13"
1207 -> "13"
1210 -> "13"
1213 -> "13"
1216 -> "13"
1219 -> "13"
1222 -> "13"
1225 -> "13"
1237 -> "13"
1240 -> "10"
1243 -> "10"
1246 -> "10"
1249 -> "13"
1252 -> "13"
1255 -> "13"
1258 -> "13"
1261 -> "13"
1264 -> "13"
1273 -> "09"
1276 -> "09"
1279 -> "13"
1282 -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
fun getYRIcon(iconCode: String, isDaytime: Boolean): String = when {
iconCode.contains("clearsky") -> "01"
iconCode.contains("cloudy") -> "04"
iconCode.contains("fair") -> "02"
iconCode.contains("fog") -> "82"
iconCode.contains("heavyrain") -> "10"
iconCode.contains("heavyrainandthunder") -> "11"
iconCode.contains("heavyrainshowers") -> "10"
iconCode.contains("heavyrainshowersandthunder") -> "11"
iconCode.contains("heavysleet") -> "10"
iconCode.contains("heavysleetandthunder") -> "11"
iconCode.contains("heavysleetshowers") -> "10"
iconCode.contains("heavysleetshowersandthunder") -> "11"
iconCode.contains("heavysnow") -> "13"
iconCode.contains("heavysnowandthunder") -> "13"
iconCode.contains("heavysnowshowers") -> "13"
iconCode.contains("heavysnowshowersandthunder") -> "13"
iconCode.contains("lightrain") -> "10"
iconCode.contains("lightrainandthunder") -> "11"
iconCode.contains("lightrainshowers") -> "10"
iconCode.contains("lightrainshowersandthunder") -> "11"
iconCode.contains("lightsleet") -> "10"
iconCode.contains("lightsleetandthunder") -> "11"
iconCode.contains("lightsleetshowers") -> "10"
iconCode.contains("lightsnow") -> "13"
iconCode.contains("lightsnowandthunder") -> "13"
iconCode.contains("lightsnowshowers") -> "13"
iconCode.contains("lightssleetshowersandthunder") -> "81"
iconCode.contains("lightssnowshowersandthunder") -> "81"
iconCode.contains("partlycloudy") -> "03"
iconCode.contains("rain") -> "10"
iconCode.contains("rainandthunder") -> "11"
iconCode.contains("rainshowers") -> "10"
iconCode.contains("rainshowersandthunder") -> "11"
iconCode.contains("sleet") -> "10"
iconCode.contains("sleetandthunder") -> "11"
iconCode.contains("sleetshowers") -> "10"
iconCode.contains("sleetshowersandthunder") -> "11"
iconCode.contains("snow") -> "13"
iconCode.contains("snowandthunder") -> "13"
iconCode.contains("snowshowers") -> "13"
iconCode.contains("snowshowersandthunder") -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
}

View File

@ -3,10 +3,20 @@ package com.tommasoberlose.anotherwidget.helpers
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Typeface
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.toPixel
import kotlin.math.min
object WidgetHelper {
class WidgetSizeProvider(
@ -44,4 +54,37 @@ object WidgetHelper {
width to second * factor
}
}
fun runWithCustomTypeface(context: Context, function: (typeface: Typeface?) -> Unit) {
if (Preferences.customFontFile != "") {
val request = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
Preferences.customFontFile,
R.array.com_google_android_gms_fonts_certs
)
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
function.invoke(typeface)
}
override fun onTypefaceRequestFailed(reason: Int) {
function.invoke(null)
}
}
val handlerThread = HandlerThread("generateView")
handlerThread.start()
if (Looper.myLooper() == null) {
Looper.prepare()
}
Handler(handlerThread.looper).run {
FontsContractCompat.requestFont(context, request, callback, this)
}
} else {
function.invoke(null)
}
}
}

View File

@ -1,20 +1,56 @@
package com.tommasoberlose.anotherwidget.network
import android.content.Context
import android.util.Log
import com.google.gson.internal.LinkedTreeMap
import com.haroldadmin.cnradapter.NetworkResponse
import com.haroldadmin.cnradapter.executeWithRetry
import com.kwabenaberko.openweathermaplib.constants.Units
import com.kwabenaberko.openweathermaplib.implementation.OpenWeatherMapHelper
import com.kwabenaberko.openweathermaplib.implementation.callbacks.CurrentWeatherCallback
import com.kwabenaberko.openweathermaplib.models.currentweather.CurrentWeather
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.repository.*
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
class WeatherNetworkApi(val context: Context) {
fun updateWeather() {
if (Preferences.showWeather && Preferences.weatherProviderApi != "" && Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApi)
suspend fun updateWeather() {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
if (Preferences.showWeather && Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> useOpenWeatherMap(context)
Constants.WeatherProvider.WEATHER_GOV -> useWeatherGov(context)
Constants.WeatherProvider.WEATHER_BIT -> useWeatherBitProvider(context)
Constants.WeatherProvider.WEATHER_API -> useWeatherApiProvider(context)
Constants.WeatherProvider.HERE -> useHereProvider(context)
Constants.WeatherProvider.ACCUWEATHER -> useAccuweatherProvider(context)
Constants.WeatherProvider.YR -> useYrProvider(context)
}
} else {
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private fun useOpenWeatherMap(context: Context) {
if (Preferences.weatherProviderApiOpen != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen)
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC)
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback {
@ -24,19 +60,383 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherIcon = currentWeather.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
override fun onFailure(throwable: Throwable?) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
})
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherGov(context: Context) {
val repository = WeatherGovRepository()
val pointsResponse = executeWithRetry(times = 5) {
repository.getGridPoints(
Preferences.customLocationLat,
Preferences.customLocationLon
)
}
when (pointsResponse) {
is NetworkResponse.Success -> {
try {
val pp = pointsResponse.body["properties"] as LinkedTreeMap<*, *>
val gridId = pp["gridId"] as String
val gridX = pp["gridX"] as Double
val gridY = pp["gridY"] as Double
when (val weatherResponse = repository.getWeather(
gridId,
gridX,
gridY,
if (Preferences.weatherTempUnit == "F") "us" else "si"
)) {
is NetworkResponse.Success -> {
try {
val props =
weatherResponse.body["properties"] as LinkedTreeMap<*, *>
val periods = props["periods"] as List<*>
val now = periods[0] as LinkedTreeMap<*, *>
val temp = now["temperature"] as Double
val fullIcon = now["icon"] as String
val isDaytime = now["isDaytime"] as Boolean
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherGovIcon(fullIcon, isDaytime)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
MainWidget.updateWidget(context)
} catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
if (pointsResponse.body?.containsKey("status") == true && (pointsResponse.body?.get("status") as Double).toInt() == 404) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_wrong_location)
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
private suspend fun useHereProvider(context: Context) {
if (Preferences.weatherProviderApiHere != "") {
val repository = HereRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
Log.d("ciao - here", response.body.toString())
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherBitProvider(context: Context) {
if (Preferences.weatherProviderApiWeatherBit != "") {
val repository = WeatherbitRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val data = response.body["data"] as List<LinkedTreeMap<String, Any>>?
data?.first()?.let {
val temp = it["temp"] as Double
val weatherInfo = it["weather"] as LinkedTreeMap<String, Any>
val iconCode = weatherInfo["icon"] as String
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherBitIcon(iconCode)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
403 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useWeatherApiProvider(context: Context) {
if (Preferences.weatherProviderApiWeatherApi != "") {
val repository = WeatherApiRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val current = response.body["current"] as LinkedTreeMap<String, Any>?
current?.let {
val tempC = current["temp_c"] as Double
val tempF = current["temp_f"] as Double
val isDay = current["is_day"] as Double
val condition = current["condition"] as LinkedTreeMap<String, Any>
val iconCode = condition["code"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherApiIcon(iconCode.toInt(), isDay.toInt() == 1)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
403 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_expired_key)
Preferences.weatherProviderLocationError = ""
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") {
val repository = AccuweatherRepository()
// when (val response = repository.getWeather()) {
// is NetworkResponse.Success -> {
// try {
// Log.d("ciao", response.body.toString())
// } catch(ex: Exception) {
//
// Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
// Preferences.weatherProviderLocationError = ""
// }
// }
// is NetworkResponse.ServerError -> {
// WeatherHelper.removeWeather(
// context
// )
// }
// Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
// Preferences.weatherProviderLocationError = ""
// EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
// }
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useYrProvider(context: Context) {
val repository = YrRepository()
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val pp = response.body["properties"] as LinkedTreeMap<*, *>
val data = pp["timeseries"] as List<LinkedTreeMap<String, Any>>?
data?.let {
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
for (item in data) {
val time = Calendar.getInstance().apply { time = format.parse(item["time"] as String)!! }
val now = Calendar.getInstance()
if (time.timeInMillis >= now.timeInMillis) {
val dd = item["data"] as LinkedTreeMap<*, *>
val instant = dd["instant"] as LinkedTreeMap<*, *>
val next = dd["next_1_hours"] as LinkedTreeMap<*, *>
val details = instant["details"] as LinkedTreeMap<*, *>
val temp = details["air_temperature"] as Double
val summary = next["summary"] as LinkedTreeMap<*, *>
val iconCode = summary["symbol_code"] as String
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getYRIcon(iconCode, now.get(Calendar.HOUR_OF_DAY) >= 22 || now.get(Calendar.HOUR_OF_DAY) <= 8)
Preferences.weatherTempUnit = "C"
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
break
}
}
}
} catch(ex: Exception) {
ex.printStackTrace()
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
} finally {
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
is NetworkResponse.ServerError -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
}
}

View File

@ -0,0 +1,74 @@
package com.tommasoberlose.anotherwidget.network.api
import com.haroldadmin.cnradapter.NetworkResponse
import retrofit2.http.*
object ApiServices {
interface WeatherGovApiService {
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("points/{latitude},{longitude}")
suspend fun getGridPoints(
@Path("latitude") latitude: String,
@Path("longitude") longitude: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("gridpoints/{gridId}/{gridX},{gridY}/forecast")
suspend fun getWeather(
@Path("gridId") gridId: String,
@Path("gridX") gridX: Int,
@Path("gridY") gridY: Int,
@Query("units") unit: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface WeatherBitService {
@GET("current")
suspend fun getWeather(
@Query("key") key: String,
@Query("lat") lat: String,
@Query("lon") lon: String,
@Query("units") units: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface WeatherApiService {
@Headers("Accept: application/json")
@GET("current.json")
suspend fun getWeather(
@Query("key") key: String,
@Query("q") location: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface HereService {
@GET("report.json")
suspend fun getWeather(
@Query("apiKey") apiKey: String,
@Query("latitude") latitude: String,
@Query("longitude") longitude: String,
@Query("product") product: String,
@Query("oneobservation") oneobservation: Boolean,
@Query("metric") metric: Boolean,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface AccuweatherService {
@GET("")
suspend fun getWeather(
@Path("gridId") gridId: String,
@Path("gridX") gridX: Int,
@Path("gridY") gridY: Int,
@Query("units") unit: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
interface YrService {
@Headers("User-Agent: AnotherWidget")
@GET("compact.json")
suspend fun getWeather(
@Query("lat") lat: String,
@Query("lon") lon: String,
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
}
}

View File

@ -0,0 +1,25 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class AccuweatherRepository {
/* ACCUWEATHER */
private val apiServiceAccu: ApiServices.AccuweatherService = getRetrofit().create(ApiServices.AccuweatherService::class.java)
suspend fun getWeather(): Nothing = TODO()
companion object {
private const val BASE_URL_ACCU = ""
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_ACCU)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class HereRepository {
/* HERE */
private val apiServiceHere: ApiServices.HereService = getRetrofit().create(ApiServices.HereService::class.java)
suspend fun getWeather() = apiServiceHere.getWeather(Preferences.weatherProviderApiHere, Preferences.customLocationLat, Preferences.customLocationLon, "observation", true, Preferences.weatherTempUnit != "F")
companion object {
private const val BASE_URL_HERE = "https://weather.ls.hereapi.com/weather/1.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_HERE)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class WeatherApiRepository {
/* WEATHER API*/
private val apiServiceApi: ApiServices.WeatherApiService = getRetrofit().create(ApiServices.WeatherApiService::class.java)
suspend fun getWeather() = apiServiceApi.getWeather(Preferences.weatherProviderApiWeatherApi, "${Preferences.customLocationLat},${Preferences.customLocationLon}")
companion object {
private const val BASE_URL_API = "http://api.weatherapi.com/v1/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_API)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class WeatherGovRepository {
/* WEATHER GOV*/
private val apiServiceGov: ApiServices.WeatherGovApiService = getRetrofit().create(ApiServices.WeatherGovApiService::class.java)
suspend fun getGridPoints(latitude: String, longitude: String) = apiServiceGov.getGridPoints(latitude, longitude)
suspend fun getWeather(gridId: String, gridX: Double, gridY: Double, unit: String) = apiServiceGov.getWeather(gridId, gridX.toInt(), gridY.toInt(), unit)
companion object {
private const val BASE_URL_GOV = "https://api.weather.gov/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_GOV)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class WeatherbitRepository {
/* BIT */
private val apiServiceBit: ApiServices.WeatherBitService = getRetrofit().create(ApiServices.WeatherBitService::class.java)
suspend fun getWeather() = apiServiceBit.getWeather(Preferences.weatherProviderApiWeatherBit, Preferences.customLocationLat, Preferences.customLocationLon, if (Preferences.weatherTempUnit == "F") "I" else "M")
companion object {
private const val BASE_URL_BIT = "https://api.weatherbit.io/v2.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_BIT)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -0,0 +1,26 @@
package com.tommasoberlose.anotherwidget.network.repository
import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.api.ApiServices
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class YrRepository {
/* YR */
private val apiServiceYr: ApiServices.YrService = getRetrofit().create(ApiServices.YrService::class.java)
suspend fun getWeather() = apiServiceYr.getWeather(Preferences.customLocationLat, Preferences.customLocationLon)
companion object {
private const val BASE_URL_YR = "https://api.met.no/weatherapi/locationforecast/2.0/"
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL_YR)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.build()
}
}
}

View File

@ -9,6 +9,7 @@ import android.content.Intent
import android.os.Build
import android.util.Log
import com.chibatching.kotpref.Kotpref
import com.chibatching.kotpref.blockingBulk
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.fitness.Fitness
@ -46,7 +47,9 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
private fun resetDailySteps(context: Context) {
Kotpref.init(context)
Preferences.googleFitSteps = -1
Preferences.blockingBulk {
remove(Preferences::googleFitSteps)
}
}
companion object {

View File

@ -5,10 +5,12 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
@ -23,7 +25,9 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_TIME_CHANGED -> setUpdates(context)
Actions.ACTION_WEATHER_UPDATE -> {
WeatherHelper.updateWeather(context)
GlobalScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(context)
}
}
}
}
@ -33,7 +37,7 @@ class WeatherReceiver : BroadcastReceiver() {
fun setUpdates(context: Context) {
removeUpdates(context)
if (Preferences.showWeather && Preferences.weatherProviderApi != "") {
if (Preferences.showWeather) {
val interval = MINUTE * when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
@ -55,7 +59,7 @@ class WeatherReceiver : BroadcastReceiver() {
}
fun setOneTimeUpdate(context: Context) {
if (Preferences.showWeather && Preferences.weatherProviderApi != "") {
if (Preferences.showWeather) {
listOf(10, 20, 30).forEach {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactAndAllowWhileIdle(

View File

@ -108,19 +108,19 @@ class UpdateCalendarJob : JobIntentService() {
if (date.get(Calendar.DAY_OF_YEAR) == date1.get(Calendar.DAY_OF_YEAR) && date.get(Calendar.YEAR) == date1.get(Calendar.YEAR)) {
if (event.allDay && event1.allDay) {
event1.startDate.compareTo(event.startDate)
event.startDate.compareTo(event1.startDate)
} else if (event.allDay) {
-1
} else if (event1.allDay) {
1
} else if (event1.allDay) {
-1
} else {
event1.startDate.compareTo(event.startDate)
event.startDate.compareTo(event1.startDate)
}
} else {
event1.startDate.compareTo(event.startDate)
event.startDate.compareTo(event1.startDate)
}
})
eventList.reverse()
eventRepository.saveEvents(
eventList
)

View File

@ -0,0 +1,239 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.chibatching.kotpref.blockingBulk
import com.koolio.library.Font
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.ActivityCustomFontBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.CustomFontViewModel
import kotlinx.android.synthetic.main.activity_choose_application.*
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import net.idik.lib.slimadapter.diff.DefaultDiffCallback
class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityCustomFontBinding>(
this,
R.layout.activity_custom_font
)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter.enableDiff(object: DefaultDiffCallback() {
override fun areItemsTheSame(oldItem: Any?, newItem: Any?): Boolean {
return oldItem is Font && newItem is Font && oldItem.fontFamily == newItem.fontFamily
}
override fun areContentsTheSame(oldItem: Any?, newItem: Any?): Boolean {
return oldItem is Font && newItem is Font && oldItem.fontFamily == newItem.fontFamily
}
})
adapter
.register<String>(R.layout.list_item) { item, injector ->
injector
.text(R.id.text, item)
.with<TextView>(R.id.text) {
val googleSans: Typeface = when (Preferences.customFontVariant) {
"100" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_thin.ttf")
"200" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_light.ttf")
"500" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_medium.ttf")
"700" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_bold.ttf")
"800" -> Typeface.createFromAsset(this.assets, "fonts/google_sans_black.ttf")
else -> Typeface.createFromAsset(this.assets, "fonts/google_sans_regular.ttf")
}
it.typeface = googleSans
}
injector.clicked(R.id.text) {
val dialog = BottomSheetMenu<String>(this, header = item)
listOf("100", "200", "regular", "500", "700", "800").forEachIndexed { index, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), s)
}
dialog.addOnSelectItemListener { value ->
saveGoogleSansFont(value)
}.show()
}
}
.register<Font>(R.layout.list_item) { item, injector ->
injector
.text(R.id.text, item.fontFamily)
.with<TextView>(R.id.text) {
val request = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
item.queryString,
R.array.com_google_android_gms_fonts_certs
)
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
it.typeface = typeface
it.isVisible = true
it.measure(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
override fun onTypefaceRequestFailed(reason: Int) {
it.isVisible = false
it.layoutParams = it.layoutParams.apply {
height = 0
}
}
}
val handlerThread = HandlerThread(item.fontFamily)
handlerThread.start()
val mHandler = Handler(handlerThread.looper)
FontsContractCompat.requestFont(this, request, callback, mHandler)
}
injector.clicked(R.id.text) {
val dialog = BottomSheetMenu<Int>(this, header = item.fontFamily)
if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
} else {
item.fontVariants.filter { !it.contains("italic") }
.forEachIndexed { index, s ->
dialog.addItem(SettingsStringHelper.getVariantLabel(this, s), index)
}
}
dialog.addOnSelectItemListener { value ->
saveFont(item, value)
}.show()
}
}
.attachTo(list_view)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityCustomFontBinding, viewModel: CustomFontViewModel) {
binding.viewModel = viewModel
viewModel.fontList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
})
}
private fun updateList(
list: ArrayList<Font>? = viewModel.fontList.value,
search: String? = viewModel.searchInput.value
) {
loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<Any> = if (search == null || search == "") {
listOf(getString(R.string.custom_font_subtitle_1)) + list.distinctBy { it.fontFamily }
} else {
(listOf(getString(R.string.custom_font_subtitle_1)) + list.distinctBy { it.fontFamily }).filter {
when (it) {
is Font -> {
it.fontFamily.contains(search, true)
}
is String -> {
it.contains(search, ignoreCase = true)
}
else -> {
true
}
}
}
}.sortedWith { el1, el2 ->
if (el1 is Font && el2 is Font) {
el1.fontFamily.compareTo(el2.fontFamily)
} else if (el1 is Font && el2 is String) {
el1.fontFamily.compareTo(el2)
} else if (el1 is String && el2 is Font) {
el1.compareTo(el2.fontFamily)
} else {
1
}
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
}
private fun saveFont(font: Font, variantPos: Int? = null) {
val resultIntent = Intent()
Preferences.blockingBulk {
customFont = Constants.CUSTOM_FONT_DOWNLOADED
customFontName = font.fontFamily
customFontFile = if (variantPos != null && variantPos > -1) font.getQueryString(variantPos) else font.queryString
customFontVariant = if (variantPos != null && variantPos > -1) font.fontVariants[variantPos] else "regular"
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
private fun saveGoogleSansFont(variant: String) {
val resultIntent = Intent()
Preferences.blockingBulk {
customFont = Constants.CUSTOM_FONT_GOOGLE_SANS
customFontName = ""
customFontFile = ""
customFontVariant = variant
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
}
}

View File

@ -0,0 +1,141 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.os.Bundle
import com.tommasoberlose.anotherwidget.R
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ResolveInfo
import android.util.Log
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityMusicPlayersFilterBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.MusicPlayersFilterViewModel
import kotlinx.android.synthetic.main.activity_choose_application.*
import kotlinx.android.synthetic.main.activity_choose_application.list_view
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import kotlin.Comparator as Comparator1
class MusicPlayersFilterActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: MusicPlayersFilterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MusicPlayersFilterViewModel::class.java)
val binding = DataBindingUtil.setContentView<ActivityMusicPlayersFilterBinding>(this, R.layout.activity_music_players_filter)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<ResolveInfo>(R.layout.application_info_layout) { item, injector ->
injector
.text(R.id.text, item.loadLabel(viewModel.pm))
.with<ImageView>(R.id.icon) {
Glide
.with(this)
.load(item.loadIcon(viewModel.pm))
.centerCrop()
.into(it)
}
.visible(R.id.checkBox)
.clicked(R.id.item) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.clicked(R.id.checkBox) {
toggleApp(item)
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
.checked(R.id.checkBox, MediaPlayerHelper.isMusicPlayerAccepted(item.activityInfo.packageName))
}
.attachTo(list_view)
setupListener()
subscribeUi(binding, viewModel)
search.requestFocus()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityMusicPlayersFilterBinding, viewModel: MusicPlayersFilterViewModel) {
binding.viewModel = viewModel
viewModel.appList.observe(this, Observer {
updateList(list = it)
loader.visibility = View.INVISIBLE
})
viewModel.searchInput.observe(this, Observer { search ->
updateList(search = search)
})
viewModel.musicPlayersFilter.observe(this, {
updateList()
})
}
private fun updateList(list: List<ResolveInfo>? = viewModel.appList.value, search: String? = viewModel.searchInput.value) {
loader.visibility = View.VISIBLE
filterJob?.cancel()
filterJob = lifecycleScope.launch(Dispatchers.IO) {
if (list != null && list.isNotEmpty()) {
delay(200)
val filteredList: List<ResolveInfo> = if (search == null || search == "") {
list
} else {
list.filter {
it.loadLabel(viewModel.pm).contains(search, true)
}
}.sortedWith { app1, app2 ->
if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName) && MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app1.activityInfo.packageName)) {
-1
} else if (MediaPlayerHelper.isMusicPlayerAccepted(app2.activityInfo.packageName)) {
1
} else {
app1.loadLabel(viewModel.pm).toString().compareTo(app2.loadLabel(viewModel.pm).toString(), ignoreCase = true)
}
}
withContext(Dispatchers.Main) {
adapter.updateData(filteredList)
loader.visibility = View.INVISIBLE
}
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
}
private fun toggleApp(app: ResolveInfo) {
MediaPlayerHelper.toggleMusicPlayerFilter(app.activityInfo.packageName)
}
}

View File

@ -1,37 +1,183 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.app.Activity
import android.app.AlertDialog
import android.os.Build
import android.content.Intent
import android.content.pm.ResolveInfo
import android.os.Bundle
import android.text.Html
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.BottomSheetWeatherProviderSettings
import com.tommasoberlose.anotherwidget.databinding.ActivityChooseApplicationBinding
import com.tommasoberlose.anotherwidget.databinding.ActivityWeatherProviderBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.viewmodels.ChooseApplicationViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.viewmodels.WeatherProviderViewModel
import com.tommasoberlose.anotherwidget.utils.collapse
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.reveal
import kotlinx.android.synthetic.main.activity_weather_provider.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.idik.lib.slimadapter.SlimAdapter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class WeatherProviderActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: WeatherProviderViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_weather_provider)
viewModel = ViewModelProvider(this).get(WeatherProviderViewModel::class.java)
list_view.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
list_view.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<Constants.WeatherProvider>(R.layout.weather_provider_list_item) { provider, injector ->
injector
.text(R.id.text, WeatherHelper.getProviderName(this, provider))
.clicked(R.id.item) {
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.value
updateListItem(oldValue)
updateListItem()
loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.clicked(R.id.radioButton) {
val oldValue = Preferences.weatherProvider
Preferences.weatherProvider = provider.value
updateListItem(oldValue)
updateListItem()
loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.checked(R.id.radioButton, provider.value == Preferences.weatherProvider)
.with<TextView>(R.id.text2) {
if (WeatherHelper.isKeyRequired(provider)) {
it.text = getString(R.string.api_key_required_message)
}
if (provider == Constants.WeatherProvider.WEATHER_GOV) {
it.text = getString(R.string.us_only_message)
}
if (provider == Constants.WeatherProvider.YR) {
it.text = getString(R.string.celsius_only_message)
}
}
.clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}.show()
}
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.value == Preferences.weatherProvider) View.VISIBLE else View.GONE)
.with<TextView>(R.id.provider_error) {
if (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") {
it.text = Preferences.weatherProviderError
it.isVisible = provider.value == Preferences.weatherProvider
} else if (Preferences.weatherProviderLocationError != "") {
it.text = Preferences.weatherProviderLocationError
it.isVisible = provider.value == Preferences.weatherProvider
} else {
it.isVisible = false
}
}
.image(R.id.action_configure, ContextCompat.getDrawable(this, if (WeatherHelper.isKeyRequired(provider)) R.drawable.round_settings else R.drawable.outline_info_white))
}.attachTo(list_view)
adapter.updateData(
Constants.WeatherProvider.values().asList()
.filter { it != Constants.WeatherProvider.HERE }
.filter { it != Constants.WeatherProvider.ACCUWEATHER }
)
setupListener()
subscribeUi(viewModel)
}
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
updateListItem()
}
viewModel.weatherProviderLocationError.observe(this) {
updateListItem()
}
}
private fun updateListItem(provider: Int = Preferences.weatherProvider) {
(adapter.data).forEachIndexed { index, item ->
if (item is Constants.WeatherProvider && item.value == provider) {
adapter.notifyItemChanged(index)
}
}
}
private fun setupListener() {
action_back.setOnClickListener {
onBackPressed()
}
}
action_save.setOnClickListener {
Preferences.weatherProviderApi = api_key.editText?.text.toString()
setResult(Activity.RESULT_OK)
finish()
override fun onBackPressed() {
setResult(Activity.RESULT_OK)
finish()
}
override fun onResume() {
super.onResume()
EventBus.getDefault().register(this)
}
override fun onPause() {
EventBus.getDefault().unregister(this)
super.onPause()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(ignore: MainFragment.UpdateUiMessageEvent?) {
loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(list_view, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()
}
action_open_provider.setOnClickListener {
openURI("https://home.openweathermap.org/users/sign_up")
}
api_key.editText?.setText(Preferences.weatherProviderApi)
}
}

View File

@ -3,16 +3,24 @@ package com.tommasoberlose.anotherwidget.ui.fragments
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.chibatching.kotpref.blockingBulk
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
@ -25,9 +33,13 @@ import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toHexValue
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.DateHelper
import com.tommasoberlose.anotherwidget.helpers.SettingsStringHelper
import com.tommasoberlose.anotherwidget.helpers.WidgetHelper
import com.tommasoberlose.anotherwidget.ui.activities.ChooseApplicationActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomDateActivity
import com.tommasoberlose.anotherwidget.ui.activities.CustomFontActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import kotlinx.android.synthetic.main.fragment_clock_settings.*
import kotlinx.android.synthetic.main.fragment_general_settings.*
@ -271,7 +283,29 @@ class GeneralTabFragment : Fragment() {
viewModel.customFont.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = getString(SettingsStringHelper.getCustomFontLabel(it))
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), it)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontFile.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontName.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
viewModel.customFontVariant.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
custom_font_label?.text = SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont)
MainWidget.updateWidget(requireContext())
}
})
@ -454,24 +488,31 @@ class GeneralTabFragment : Fragment() {
action_custom_font.setOnClickListener {
val dialog = BottomSheetMenu<Int>(requireContext(), header = getString(R.string.settings_custom_font_title)).setSelectedValue(Preferences.customFont)
(0..1).forEach {
dialog.addItem(getString(SettingsStringHelper.getCustomFontLabel(it)), it)
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), 0), 0)
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Constants.CUSTOM_FONT_GOOGLE_SANS), Constants.CUSTOM_FONT_GOOGLE_SANS)
}
if (Preferences.customFontFile != "") {
dialog.addItem(SettingsStringHelper.getCustomFontLabel(requireContext(), Preferences.customFont), Constants.CUSTOM_FONT_DOWNLOADED)
}
dialog.addItem(getString(R.string.action_custom_font_to_search), Constants.CUSTOM_FONT_DOWNLOAD_NEW)
dialog.addOnSelectItemListener { value ->
Preferences.customFont = value
if (value == Constants.CUSTOM_FONT_DOWNLOAD_NEW) {
startActivityForResult(
Intent(requireContext(), CustomFontActivity::class.java),
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code
)
} else if (value != Constants.CUSTOM_FONT_DOWNLOADED) {
Preferences.bulk {
customFont = value
customFontFile = ""
customFontName = ""
customFontVariant = ""
}
}
}.show()
/*
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "* / *" TO FIX WITHOUT SPACE
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(Intent.createChooser(intent, "Select a File to Upload"), Constants.CUSTOM_FONT_CHOOSER_REQUEST_CODE)
} catch (ex: android.content.ActivityNotFoundException) {
Toast.makeText(this, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
}
*/
}
action_show_dividers.setOnClickListener {
@ -483,25 +524,6 @@ class GeneralTabFragment : Fragment() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code -> {
/*val uri = data.data
Log.d("AW", "File Uri: " + uri.toString())
val path = Util.getPath(this, uri)
Log.d("AW", "File Path: " + path)
SP.edit()
.putString(Constants.PREF_CUSTOM_FONT_FILE, path)
.commit()
sendBroadcast(Intent(Constants.ACTION_TIME_UPDATE))
updateSettings()*/
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) {
scrollView.isScrollable = false
callback.invoke()

View File

@ -44,6 +44,7 @@ import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver.Companion.FITNESS_OPTIONS
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.MusicPlayersFilterActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.checkIfFitInstalled
@ -136,6 +137,9 @@ class GlanceTabFragment : Fragment() {
}
})
viewModel.musicPlayersFilter.observe(viewLifecycleOwner, Observer {
})
}
private fun setupListener() {
@ -242,6 +246,10 @@ class GlanceTabFragment : Fragment() {
CustomNotesDialog(requireContext()).show()
}
}
action_filter_music_players.setOnClickListener {
startActivity(Intent(requireContext(), MusicPlayersFilterActivity::class.java))
}
}
private fun updateNextAlarmWarningUi() {

View File

@ -2,8 +2,6 @@ package com.tommasoberlose.anotherwidget.ui.fragments
import android.Manifest
import android.animation.ValueAnimator
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
@ -11,7 +9,6 @@ import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@ -36,14 +33,13 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.BitmapHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.helpers.WidgetHelper
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.adapters.ViewPagerAdapter
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.getCurrentWallpaper
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.android.synthetic.main.fragment_app_main.*
import kotlinx.android.synthetic.main.the_widget_sans.*
import kotlinx.coroutines.*
@ -77,6 +73,7 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUi(viewModel)
// Viewpager
@ -147,133 +144,136 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
ColorHelper.getBackgroundColor(activity?.isDarkTheme() == true)
)
)
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.generateWidgetView(requireContext())
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.generateWidgetView(requireContext(), typeface)
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview?.measure(0, 0)
}
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
preview?.measure(0, 0)
}
val bitmap = if (preview != null) {
BitmapHelper.getBitmapFromView(
generatedView,
if (preview.width > 0) preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
} else {
null
}
withContext(Dispatchers.Main) {
// Clock
time?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
time_am_pm?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
time_am_pm?.isVisible = Preferences.showAMPMIndicator
val bitmap = if (preview != null) {
BitmapHelper.getBitmapFromView(
generatedView,
if (preview.width > 0) preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
} else {
null
}
withContext(Dispatchers.Main) {
// Clock
time?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time_am_pm?.setTextColor(ColorHelper.getClockFontColor(activity?.isDarkTheme() == true))
time?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
time_am_pm?.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
time_am_pm?.isVisible = Preferences.showAMPMIndicator
// Clock bottom margin
clock_bottom_margin_none?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.value
clock_bottom_margin_small?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value
clock_bottom_margin_medium?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value
clock_bottom_margin_large?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value
// Clock bottom margin
clock_bottom_margin_none?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.value
clock_bottom_margin_small?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.value
clock_bottom_margin_medium?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.value
clock_bottom_margin_large?.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.value
if ((Preferences.showClock && (time?.alpha ?: 1f < 1f)) || (!Preferences.showClock && (time?.alpha ?: 0f > 0f))) {
if (Preferences.showClock) {
if ((Preferences.showClock && (time?.alpha ?: 1f < 1f)) || (!Preferences.showClock && (time?.alpha ?: 0f > 0f))) {
if (Preferences.showClock) {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
val initialHeight = time_container?.measuredHeight ?: 0
ValueAnimator.ofFloat(
if (Preferences.showClock) 0f else 1f,
if (Preferences.showClock) 1f else 0f
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
time_container?.layoutParams =
time_container.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
time?.alpha = animatedValue
}
addListener(
onStart = {
if (Preferences.showClock) {
time_container?.isVisible = true
}
},
onEnd = {
if (!Preferences.showClock) {
time_container?.isVisible = false
}
}
)
}.start()
if (preview != null) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 500L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
}
}
}.start()
}
} else {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
time_container?.measure(0, 0)
}
val initialHeight = time_container?.measuredHeight ?: 0
ValueAnimator.ofFloat(
if (Preferences.showClock) 0f else 1f,
if (Preferences.showClock) 1f else 0f
).apply {
duration = 500L
addUpdateListener {
val animatedValue = animatedValue as Float
time_container?.layoutParams =
time_container.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
time?.alpha = animatedValue
}
addListener(
onStart = {
if (Preferences.showClock) {
time_container?.isVisible = true
}
},
onEnd = {
if (!Preferences.showClock) {
time_container?.isVisible = false
}
}
)
}.start()
if (preview != null) {
if (preview != null && preview.height == 0) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 500L
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview.layoutParams = layoutParams
preview?.layoutParams = layoutParams
}
}
}.start()
}
} else {
time_container?.layoutParams = time_container.layoutParams.apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
widget_loader?.animate()?.scaleX(0f)?.scaleY(0f)?.alpha(0f)
?.setDuration(200L)?.start()
bitmap_container?.apply {
setImageBitmap(bitmap)
scaleX = 0.9f
scaleY = 0.9f
}
time_container?.measure(0, 0)
widget?.animate()?.alpha(1f)?.start()
}
if (preview != null && preview.height == 0) {
ValueAnimator.ofInt(
preview.height,
PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
).apply {
duration = 300L
addUpdateListener {
if (preview != null) {
val animatedValue = animatedValue as Int
val layoutParams = preview.layoutParams
layoutParams.height = animatedValue
preview?.layoutParams = layoutParams
}
}
}.start()
}
widget_loader?.animate()?.scaleX(0f)?.scaleY(0f)?.alpha(0f)?.setDuration(200L)?.start()
bitmap_container?.apply {
setImageBitmap(bitmap)
scaleX = 0.9f
scaleY = 0.9f
}
widget?.animate()?.alpha(1f)?.start()
}
}
} else {
@ -349,8 +349,15 @@ class MainFragment : Fragment(), SharedPreferences.OnSharedPreferenceChangeList
tabs?.getTabAt(2)?.orCreateBadge?.apply {
backgroundColor = ContextCompat.getColor(requireContext(), R.color.errorColorText)
badgeGravity = BadgeDrawable.TOP_END
}?.isVisible = Preferences.showWeather && (Preferences.weatherProviderApi == "" || (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) != true))
}?.isVisible = if (Preferences.showWeather) {
(WeatherHelper.isKeyRequired() && WeatherHelper.getApiKey() == "")
|| (Preferences.customLocationAdd == "" && activity?.checkGrantedPermission(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION
) != true)
|| (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-")
} else {
false
}
// Music error indicator
tabs?.getTabAt(4)?.orCreateBadge?.apply {

View File

@ -184,12 +184,18 @@ class SettingsFragment : Fragment() {
activity?.openURI("https://github.com/tommasoberlose/another-widget/issues")
}
action_privacy_policy.setOnClickListener {
activity?.openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md")
}
action_help_dev.setOnClickListener {
startActivity(Intent(requireContext(), SupportDevActivity::class.java))
}
action_refresh_widget.setOnClickListener {
WeatherHelper.updateWeather(requireContext())
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
}

View File

@ -102,11 +102,19 @@ class WeatherTabFragment : Fragment() {
checkLocationPermission()
})
viewModel.weatherProviderApi.observe(viewLifecycleOwner, Observer {
viewModel.weatherProvider.observe(viewLifecycleOwner, Observer {
maintainScrollPosition {
label_weather_provider.text = WeatherHelper.getProviderName(requireContext(), Constants.WeatherProvider.fromInt(it)!!)
checkWeatherProviderConfig()
}
checkLocationPermission()
})
viewModel.weatherProviderError.observe(viewLifecycleOwner, Observer {
checkWeatherProviderConfig()
})
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner, Observer {
checkWeatherProviderConfig()
})
viewModel.customLocationAdd.observe(viewLifecycleOwner, Observer {
@ -162,9 +170,11 @@ class WeatherTabFragment : Fragment() {
if (activity?.checkGrantedPermission(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACCESS_BACKGROUND_LOCATION else Manifest.permission.ACCESS_FINE_LOCATION) == true) {
location_permission_alert?.isVisible = false
background_location_warning.isVisible = Preferences.customLocationAdd == ""
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
location_permission_alert?.isVisible = true
background_location_warning.isVisible = false
location_permission_alert?.setOnClickListener {
MaterialBottomSheetDialog(requireContext(), message = getString(R.string.background_location_warning))
.setPositiveButton(getString(android.R.string.ok)) {
@ -178,11 +188,11 @@ class WeatherTabFragment : Fragment() {
}
private fun checkWeatherProviderConfig() {
label_weather_provider_api_key?.text =
if (Preferences.weatherProviderApi == "") getString(R.string.settings_weather_provider_api_key_subtitle_not_set) else getString(
R.string.settings_weather_provider_api_key_subtitle_all_set
)
label_weather_provider_api_key?.setTextColor(ContextCompat.getColor(requireContext(), if (Preferences.weatherProviderApi == "" && Preferences.showWeather) R.color.errorColorText else R.color.colorSecondaryText))
weather_provider_error.isVisible = Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
weather_provider_error?.text = Preferences.weatherProviderError
weather_provider_location_error.isVisible = Preferences.weatherProviderLocationError != ""
weather_provider_location_error?.text = Preferences.weatherProviderLocationError
}
private fun setupListener() {
@ -198,7 +208,7 @@ class WeatherTabFragment : Fragment() {
Preferences.showWeather = enabled
}
action_weather_provider_api_key.setOnClickListener {
action_weather_provider.setOnClickListener {
if (Preferences.showWeather) {
startActivityForResult(
Intent(requireContext(), WeatherProviderActivity::class.java),
@ -223,7 +233,9 @@ class WeatherTabFragment : Fragment() {
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
WeatherHelper.updateWeather(requireContext())
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
Preferences.weatherTempUnit = value
}.show()
@ -275,7 +287,7 @@ class WeatherTabFragment : Fragment() {
MainWidget.updateWidget(requireContext())
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
WeatherReceiver.setOneTimeUpdate(requireContext())
checkLocationPermission()
}
}
}

View File

@ -0,0 +1,40 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import android.content.Intent
import android.content.pm.ResolveInfo
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.koolio.library.DownloadableFontList
import com.koolio.library.Font
import com.koolio.library.FontList
import com.tommasoberlose.anotherwidget.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CustomFontViewModel(application: Application) : AndroidViewModel(application) {
val fontList: MutableLiveData<ArrayList<Font>> = MutableLiveData()
val searchInput: MutableLiveData<String> = MutableLiveData("")
init {
viewModelScope.launch(Dispatchers.IO) {
val fontListCallback: DownloadableFontList.FontListCallback =
object : DownloadableFontList.FontListCallback {
override fun onFontListRetrieved(downloadedList: FontList?) {
fontList.postValue(downloadedList?.fontArrayList)
}
override fun onTypefaceRequestFailed(reason: Int) {
}
}
DownloadableFontList.requestDownloadableFontList(fontListCallback, BuildConfig.GOOGLE_API_KEY)
}
}
}

View File

@ -24,6 +24,9 @@ class MainViewModel : ViewModel() {
val textShadow = Preferences.asLiveData(Preferences::textShadow)
val textShadowDark = Preferences.asLiveData(Preferences::textShadowDark)
val customFont = Preferences.asLiveData(Preferences::customFont)
val customFontFile = Preferences.asLiveData(Preferences::customFontFile)
val customFontName = Preferences.asLiveData(Preferences::customFontName)
val customFontVariant = Preferences.asLiveData(Preferences::customFontVariant)
val secondRowInformation = Preferences.asLiveData(Preferences::secondRowInformation)
val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
@ -61,12 +64,15 @@ class MainViewModel : ViewModel() {
val weatherRefreshPeriod = Preferences.asLiveData(Preferences::weatherRefreshPeriod)
val weatherAppName = Preferences.asLiveData(Preferences::weatherAppName)
val weatherProviderApi = Preferences.asLiveData(Preferences::weatherProviderApi)
val weatherProviderApi = Preferences.asLiveData(Preferences::weatherProviderApiOpen)
val customLocationAdd = Preferences.asLiveData(Preferences::customLocationAdd)
val showWeatherWarning = Preferences.asLiveData(Preferences::showWeatherWarning)
val weatherIconPack = Preferences.asLiveData(Preferences::weatherIconPack)
val weatherProvider = Preferences.asLiveData(Preferences::weatherProvider)
val weatherProviderError = Preferences.asLiveData(Preferences::weatherProviderError)
val weatherProviderLocationError = Preferences.asLiveData(Preferences::weatherProviderLocationError)
// Glance
val showGlance = Preferences.asLiveData(Preferences::showGlance)
@ -75,6 +81,7 @@ class MainViewModel : ViewModel() {
val showBatteryCharging = Preferences.asLiveData(Preferences::showBatteryCharging)
val showDailySteps = Preferences.asLiveData(Preferences::showDailySteps)
val customInfo = Preferences.asLiveData(Preferences::customNotes)
val musicPlayersFilter = Preferences.asLiveData(Preferences::musicPlayersFilter)
// Advanced Settings
val darkThemePreference = Preferences.asLiveData(Preferences::darkThemePreference)

View File

@ -0,0 +1,38 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.util.Log
import androidx.lifecycle.*
import com.chibatching.kotpref.livedata.asLiveData
import com.tommasoberlose.anotherwidget.global.Preferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MusicPlayersFilterViewModel(application: Application) : AndroidViewModel(application) {
val pm: PackageManager by lazy { application.packageManager }
val appList: MutableLiveData<List<ResolveInfo>> = MutableLiveData()
val searchInput: MutableLiveData<String> = MutableLiveData("")
var musicPlayersFilter = Preferences.asLiveData(Preferences::musicPlayersFilter)
init {
viewModelScope.launch(Dispatchers.IO) {
val mainIntent = Intent(Intent.ACTION_MAIN, null).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}
val app = application.packageManager.queryIntentActivities(mainIntent, 0)
val sortedApp = app.sortedWith(Comparator { app1: ResolveInfo, app2: ResolveInfo ->
app1.loadLabel(pm).toString().compareTo(app2.loadLabel(pm).toString())
})
withContext(Dispatchers.Main) {
appList.postValue(sortedApp)
}
}
}
}

View File

@ -0,0 +1,19 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.chibatching.kotpref.livedata.asLiveData
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
class WeatherProviderViewModel(application: Application) : AndroidViewModel(application) {
val weatherProvider = Preferences.asLiveData(Preferences::weatherProvider)
val weatherProviderError = Preferences.asLiveData(Preferences::weatherProviderError)
val weatherProviderLocationError = Preferences.asLiveData(Preferences::weatherProviderLocationError)
}

View File

@ -11,12 +11,18 @@ import android.content.res.Resources
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
@ -26,10 +32,7 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.receivers.*
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.getCapWordString
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.android.synthetic.main.the_widget.view.*
import java.lang.Exception
import java.text.DateFormat
@ -90,10 +93,14 @@ class MainWidget : AppWidgetProvider() {
val height = displayMetrics.heightPixels
val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId)
generateWidgetView(context, appWidgetId, appWidgetManager, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)))
WidgetHelper.runWithCustomTypeface(context) {
generateWidgetView(context, appWidgetId, appWidgetManager, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
}
}
private fun generateWidgetView(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager, w: Int) {
private fun generateWidgetView(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager, w: Int, typeface: Typeface? = null) {
var views = RemoteViews(context.packageName, R.layout.the_widget_sans)
try {
@ -125,7 +132,8 @@ class MainWidget : AppWidgetProvider() {
// Setup listener
try {
val generatedView = generateWidgetView(context)
val generatedView = generateWidgetView(context, typeface)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedView, width = w)
@ -492,10 +500,12 @@ class MainWidget : AppWidgetProvider() {
// Generates the widget bitmap from the view
fun generateWidgetView(context: Context): View {
fun generateWidgetView(context: Context, typeface: Typeface? = null): View {
val eventRepository = EventRepository(context)
val v = View.inflate(context, R.layout.the_widget, null)
v.loader.isVisible = false
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
@ -514,8 +524,10 @@ class MainWidget : AppWidgetProvider() {
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null) {
// Multiple counter
v.action_next.isVisible = Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.action_previous.isVisible = Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.action_next.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.action_previous.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
v.next_event.text = nextEvent.title
@ -528,7 +540,11 @@ class MainWidget : AppWidgetProvider() {
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(context, now.timeInMillis, nextEvent.startDate).toLowerCase(Locale.getDefault())
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
v.next_event_difference_time.visibility = View.VISIBLE
} else {
@ -536,15 +552,30 @@ class MainWidget : AppWidgetProvider() {
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
v.second_row_icon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.round_place))
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place
)
)
v.next_event_date.text = nextEvent.address
} else {
v.second_row_icon.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.round_today))
v.second_row_icon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today
)
)
if (!nextEvent.allDay) {
val startHour = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(nextEvent.startDate)
val endHour = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(nextEvent.endDate)
val startHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.startDate)
val endHour =
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault())
.format(nextEvent.endDate)
var dayDiff = TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
@ -554,31 +585,46 @@ class MainWidget : AppWidgetProvider() {
if (startCal.get(Calendar.HOUR_OF_DAY) > endCal.get(Calendar.HOUR_OF_DAY)) {
dayDiff++
} else if (startCal.get(Calendar.HOUR_OF_DAY) == endCal.get(Calendar.HOUR_OF_DAY) && startCal.get(Calendar.MINUTE) >= endCal.get(Calendar.MINUTE)) {
} else if (startCal.get(Calendar.HOUR_OF_DAY) == endCal.get(Calendar.HOUR_OF_DAY) && startCal.get(
Calendar.MINUTE
) >= endCal.get(Calendar.MINUTE)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(" (+%s%s)", dayDiff, context.getString(R.string.day_char))
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
v.next_event_date.text = String.format("%s - %s%s", startHour, endHour, multipleDay)
v.next_event_date.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
val flags: Int = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
v.next_event_date.text = DateUtils.formatDateTime(context, nextEvent.startDate, flags)
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
v.next_event_date.text =
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
v.second_row_top_margin_small.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
v.second_row_icon.isVisible = true
var showSomething = false
loop@ for (provider:Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
@ -612,10 +658,11 @@ class MainWidget : AppWidgetProvider() {
if (Preferences.isCharging) {
v.second_row_icon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel == 100) {
v.next_event_date.text = "%s - %d%%".format(context.getString(R.string.charging), batteryLevel)
} else {
if (batteryLevel != 100) {
v.next_event_date.text = context.getString(R.string.charging)
} else {
v.next_event_date.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
@ -641,7 +688,9 @@ class MainWidget : AppWidgetProvider() {
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
v.second_row_icon.isVisible = false
v.next_event_date.text = context.getString(R.string.daily_steps_counter).format(Preferences.googleFitSteps)
v.next_event_date.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
@ -654,9 +703,12 @@ class MainWidget : AppWidgetProvider() {
v.empty_layout.visibility = View.GONE
v.calendar_layout.visibility = View.VISIBLE
v.second_row_top_margin_small.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility = if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
v.second_row_top_margin_small.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.value) View.VISIBLE else View.GONE
v.second_row_top_margin_medium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.value) View.VISIBLE else View.GONE
v.second_row_top_margin_large.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.value) View.VISIBLE else View.GONE
} else {
v.second_row_icon.isVisible = false
}
@ -664,17 +716,33 @@ class MainWidget : AppWidgetProvider() {
// Color
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.divider3, v.special_temp).forEach {
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.divider3,
v.special_temp
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.value) {
listOf<ImageView>(v.action_next, v.action_previous)
} else {
listOf<ImageView>(v.action_next, v.action_previous, v.empty_weather_icon, v.special_weather_icon)
listOf<ImageView>(
v.action_next,
v.action_previous,
v.empty_weather_icon,
v.special_weather_icon
)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha = (if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue().toFloat() else Preferences.textGlobalAlpha.toIntValue().toFloat()) / 100
it.alpha =
(if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue()
.toFloat() else Preferences.textGlobalAlpha.toIntValue()
.toFloat()) / 100
}
listOf<TextView>(v.next_event_date, v.divider2, v.calendar_temp).forEach {
@ -687,7 +755,10 @@ class MainWidget : AppWidgetProvider() {
listOf<ImageView>(v.second_row_icon, v.weather_icon)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha = (if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue().toFloat() else Preferences.textSecondaryAlpha.toIntValue().toFloat()) / 100
it.alpha =
(if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue()
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
// Text Size
@ -727,34 +798,82 @@ class MainWidget : AppWidgetProvider() {
// Shadows
val shadowRadius = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> R.color.black_50
2 -> Color.BLACK
else -> R.color.black_50
}
val shadowDy = when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 1f
else -> 0f
}
val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> R.color.black_50
2 -> Color.BLACK
else -> R.color.black_50
}
val shadowDy =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 1f
else -> 0f
}
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp, v.divider3, v.special_temp).forEach {
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_PRODUCT_SANS) {
val productSans: Typeface = Typeface.createFromAsset(context.assets, "fonts/product_sans_regular.ttf")
listOf<TextView>(v.empty_date, v.divider1, v.temp, v.next_event, v.next_event_difference_time, v.next_event_date, v.divider2, v.calendar_temp, v.divider3, v.special_temp).forEach {
it.typeface = productSans
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
val googleSans: Typeface = when (Preferences.customFontVariant) {
"100" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_thin.ttf")
"200" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_light.ttf")
"500" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_medium.ttf")
"700" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_bold.ttf")
"800" -> Typeface.createFromAsset(context.assets, "fonts/google_sans_black.ttf")
else -> Typeface.createFromAsset(context.assets, "fonts/google_sans_regular.ttf")
}
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
v.empty_date,
v.divider1,
v.temp,
v.next_event,
v.next_event_difference_time,
v.next_event_date,
v.divider2,
v.calendar_temp,
v.divider3,
v.special_temp
).forEach {
it.typeface = typeface
}
}
@ -763,7 +882,12 @@ class MainWidget : AppWidgetProvider() {
v.weather.visibility = View.VISIBLE
v.calendar_weather.visibility = View.VISIBLE
v.special_weather.visibility = View.VISIBLE
val currentTemp = String.format(Locale.getDefault(), "%d °%s", Preferences.weatherTemp.roundToInt(), Preferences.weatherRealTempUnit)
val currentTemp = String.format(
Locale.getDefault(),
"%d °%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
@ -771,9 +895,9 @@ class MainWidget : AppWidgetProvider() {
v.empty_weather_icon.visibility = View.GONE
v.special_weather_icon.visibility = View.GONE
} else {
v.weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.empty_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.special_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(icon))
v.weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.empty_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.special_weather_icon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
v.weather_icon.visibility = View.VISIBLE
v.empty_weather_icon.visibility = View.VISIBLE
v.special_weather_icon.visibility = View.VISIBLE

View File

@ -95,7 +95,7 @@ fun View.expand() {
}
}
fun View.collapse() {
fun View.collapse(duration: Long = 500L) {
if (visibility != View.GONE) {
val initialHeight = measuredHeight
@ -114,7 +114,7 @@ fun View.collapse() {
}
}
a.duration = 500L //(initialHeight / v.context.resources.displayMetrics.density).toLong()
a.duration = duration //(initialHeight / v.context.resources.displayMetrics.density).toLong()
startAnimation(a)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Some files were not shown because too many files have changed in this diff Show More