Compare commits

..

No commits in common. "patch-develop" and "v2.3.1" have entirely different histories.

346 changed files with 3981 additions and 4934 deletions

View File

@ -19,29 +19,6 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
@ -68,47 +45,14 @@
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="$USER_HOME$/Desktop/logo-white.png" />
<entry key="scalingPercent" value="70" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="trimmed" value="true" />
</map>
</option>
</PersistentState>
@ -120,6 +64,7 @@
<map>
<entry key="backgroundAssetType" value="COLOR" />
<entry key="backgroundColor" value="ffffff" />
<entry key="foregroundImage" value="$USER_HOME$/Desktop/Artboard Copy 3.png" />
<entry key="legacyIconShape" value="CIRCLE" />
</map>
</option>
@ -132,29 +77,6 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
@ -176,104 +98,6 @@
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvBanner">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvChannel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/private/var/folders/cw/tkrg0g5j6lzcqwr0tfkph8w80000gn/T/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">

Binary file not shown.

5
.idea/gradle.xml generated
View File

@ -4,16 +4,17 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

2
.idea/misc.xml generated
View File

@ -61,7 +61,7 @@
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

3
.idea/modules.xml generated
View File

@ -4,9 +4,6 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Another_Widget.iml" filepath="$PROJECT_DIR$/.idea/modules/Another_Widget.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.androidTest.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Another_Widget.app.unitTest.iml" />
</modules>
</component>
</project>

12
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -1,20 +1,32 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
// Apply the Crashlytics Gradle plugin
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
def apikeyPropertiesFile = rootProject.file("apikey.properties")
def apikeyProperties = new Properties()
apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.tommasoberlose.anotherwidget"
minSdkVersion 23
targetSdkVersion 30
versionCode 139
versionName "2.3.3"
versionCode 130
versionName "2.3.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "GOOGLE_API_KEY", apikeyProperties['GOOGLE_API_KEY'])
renderscriptSupportModeEnabled true
}
@ -58,7 +70,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.3.0'
@ -76,10 +88,6 @@ dependencies {
// EventBus
implementation 'org.greenrobot:eventbus:3.2.0'
// Room
implementation "androidx.room:room-runtime:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
@ -94,14 +102,23 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'
// Fitness
implementation 'com.google.android.gms:play-services-fitness:20.0.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
//Weather
implementation 'com.github.KwabenBerko:OpenWeatherMap-Android-Library:2.0.2'
implementation 'com.google.android.gms:play-services-location:18.0.0'
// Billing
implementation 'com.android.billingclient:billing:3.0.3'
implementation 'com.android.billingclient:billing-ktx:3.0.3'
// KTX
implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.core:core-ktx:1.3.2'
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
@ -113,6 +130,9 @@ dependencies {
//Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
// Preferences
implementation 'com.chibatching.kotpref:kotpref:2.13.1'
implementation 'com.chibatching.kotpref:livedata-support:2.13.1'

View File

@ -6,15 +6,13 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
@ -26,28 +24,30 @@
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.SplashActivity" android:exported="true" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<activity android:name=".ui.activities.MainActivity" android:launchMode="singleInstance" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.WeatherProviderActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.SupportDevActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomDateActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.settings.IntegrationsActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MusicPlayersFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.AppNotificationsFilterActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:launchMode="singleInstance" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget" android:exported="true">
<receiver android:name=".ui.widgets.MainWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/the_widget_info" />
@ -73,8 +73,8 @@
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.TIME_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_TIME_UPDATE" />
<action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
@ -89,7 +89,8 @@
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_WEATHER_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
@ -101,13 +102,26 @@
<receiver
android:name=".receivers.WidgetClickListenerReceiver"
android:enabled="true"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_OPEN_WEATHER_INTENT" />
</intent-filter>
</receiver>
<service android:name=".receivers.NotificationListener" android:exported="true"
<receiver
android:name=".receivers.CrashlyticsReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_REPORT_CRASH" />
</intent-filter>
</receiver>
<service android:name=".services.EventListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".services.BatteryListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
<service android:name=".receivers.NotificationListener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
@ -125,6 +139,27 @@
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name=".receivers.ActivityDetectionReceiver"
android:exported="false"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
<intent-filter>
<action android:name="com.mypackage.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".services.UpdateCalendarService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application>
<queries>

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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,18 +1,52 @@
package com.tommasoberlose.anotherwidget
import android.Manifest
import android.app.Application
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import com.chibatching.kotpref.Kotpref
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import io.realm.Realm
import io.realm.RealmConfiguration
import net.danlew.android.joda.JodaTimeAndroid
class AWApplication : Application() {
override fun onCreate() {
super.onCreate()
// Firebase crashlitycs
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
// Preferences
Kotpref.init(this)
// Dark theme
AppCompatDelegate.setDefaultNightMode(Preferences.darkThemePreference)
// Realm
Realm.init(this)
val config = RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)
calibrateVersions()
}
private fun calibrateVersions() {
// 2.0 Tolerance
if (Preferences.clockTextSize > 50f) {
Preferences.clockTextSize = 32f
}
if (Preferences.textMainSize > 36f) {
Preferences.textMainSize = 32f
}
if (Preferences.textSecondSize > 28f) {
Preferences.textSecondSize = 24f
}
}
}

View File

@ -3,30 +3,40 @@ package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.GridLayout
import android.widget.ImageView
import android.widget.SeekBar
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuHorBinding
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuListBinding
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.copyToClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isClipboardColor
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.pasteFromClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.utils.expand
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.reveal
import com.tommasoberlose.anotherwidget.utils.toPixel
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
import java.lang.Exception
import java.util.prefs.Preferences
class BottomSheetColorPicker(
context: Context,
@ -36,45 +46,20 @@ class BottomSheetColorPicker(
private val onColorSelected: ((selectedValue: Int) -> Unit)? = null,
private val showAlphaSelector: Boolean = false,
private val alpha: Int = 0,
private val onAlphaChangeListener: ((alpha: Int) -> Unit)? = null,
private val hideCopyPaste: Boolean = false,
private val onAlphaChangeListener: ((alpha: Int) -> Unit)? = null
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter
private var alphaDebouncing: Job? = null
private var binding: BottomSheetMenuHorBinding = BottomSheetMenuHorBinding.inflate(LayoutInflater.from(context))
private var listBinding: BottomSheetMenuListBinding = BottomSheetMenuListBinding.inflate(LayoutInflater.from(context))
override fun show() {
window?.setDimAmount(0f)
// Header
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
if (hideCopyPaste) {
binding.actionContainer.isVisible = false
} else {
binding.actionContainer.isVisible = true
binding.actionCopy.setOnClickListener {
context.copyToClipboard(getSelected?.invoke(), alpha)
}
binding.actionPaste.setOnClickListener {
context.pasteFromClipboard { color, alpha ->
binding.alphaSelector.setProgress(alpha.toIntValue().toFloat())
adapter.notifyItemChanged(adapter.data.indexOf(getSelected?.invoke()))
onColorSelected?.invoke(Color.parseColor(color))
val idx = colors.toList().indexOf(getSelected?.invoke())
adapter.notifyItemChanged(idx)
(listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(idx,0)
}
}
binding.actionPaste.isVisible = context.isClipboardColor()
}
// Alpha
binding.alphaSelectorContainer.isVisible = showAlphaSelector
binding.alphaSelector.setProgress(alpha.toFloat())
@ -82,17 +67,10 @@ class BottomSheetColorPicker(
binding.alphaSelector.onSeekChangeListener = object : OnSeekChangeListener {
override fun onSeeking(seekParams: SeekParams?) {
seekParams?.let {
binding.textAlpha.text =
"%s: %s%%".format(context.getString(R.string.alpha), it.progress)
alphaDebouncing?.cancel()
alphaDebouncing = GlobalScope.launch(Dispatchers.IO) {
delay(150)
withContext(Dispatchers.Main) {
binding.textAlpha.text = "%s: %s%%".format(context.getString(R.string.alpha), it.progress)
onAlphaChangeListener?.invoke(it.progress)
}
}
}
}
override fun onStartTrackingTouch(seekBar: IndicatorSeekBar?) {
}
override fun onStopTrackingTouch(seekBar: IndicatorSeekBar?) {
@ -100,6 +78,7 @@ class BottomSheetColorPicker(
}
// List
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
@ -141,20 +120,14 @@ class BottomSheetColorPicker(
adapter.updateData(colors.toList())
withContext(Dispatchers.Main) {
binding.loader.isVisible = false
binding.colorLoader.isVisible = false
binding.listContainer.addView(listBinding.root)
this@BottomSheetColorPicker.behavior.state = BottomSheetBehavior.STATE_EXPANDED
binding.listContainer.isVisible = true
val idx = colors.toList().indexOf(getSelected?.invoke())
(listBinding.root.layoutManager as GridLayoutManager).scrollToPositionWithOffset(idx,0)
}
})
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -6,7 +6,6 @@ import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.tommasoberlose.anotherwidget.R
@ -108,10 +107,6 @@ open class BottomSheetMenu<T>(context: Context, private val header: String? = nu
}
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -1,111 +0,0 @@
package com.tommasoberlose.anotherwidget.components
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.card.MaterialCardView
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuHorBinding
import com.tommasoberlose.anotherwidget.databinding.BottomSheetMenuListBinding
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.copyToClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isClipboardColor
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.pasteFromClipboard
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.warkiz.widget.IndicatorSeekBar
import com.warkiz.widget.OnSeekChangeListener
import com.warkiz.widget.SeekParams
import kotlinx.coroutines.*
import net.idik.lib.slimadapter.SlimAdapter
class BottomSheetPicker<T>(
context: Context,
private val items: List<MenuItem<T>> = arrayListOf(),
private val getSelected: (() -> T)? = null,
private val header: String? = null,
private val onItemSelected: ((selectedValue: T?) -> Unit)? = null,
) : BottomSheetDialog(context, R.style.BottomSheetDialogTheme) {
private var loadingJobs: ArrayList<Job> = ArrayList()
private lateinit var adapter: SlimAdapter
private var binding: BottomSheetMenuHorBinding = BottomSheetMenuHorBinding.inflate(
LayoutInflater.from(context))
private var listBinding: BottomSheetMenuListBinding = BottomSheetMenuListBinding.inflate(
LayoutInflater.from(context))
override fun show() {
window?.setDimAmount(0f)
// Header
binding.header.isVisible = header != null
binding.headerText.text = header ?: ""
// Alpha
binding.alphaSelectorContainer.isVisible = false
binding.actionContainer.isVisible = false
// List
adapter = SlimAdapter.create()
loadingJobs.add(GlobalScope.launch(Dispatchers.IO) {
listBinding.root.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(context)
listBinding.root.layoutManager = mLayoutManager
adapter
.register<Int>(R.layout.bottom_sheet_menu_item) { position, injector ->
val item = items[position]
val isSelected = item.value == getSelected?.invoke()
injector
.text(R.id.label, item.title)
.textColor(R.id.label, ContextCompat.getColor(context, if (isSelected) R.color.colorAccent else R.color.colorSecondaryText))
.selected(R.id.item, isSelected)
.clicked(R.id.item) {
val oldIdx = items.toList().indexOfFirst { it.value == getSelected?.invoke() }
onItemSelected?.invoke(item.value)
adapter.notifyItemChanged(position)
adapter.notifyItemChanged(oldIdx)
(listBinding.root.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position,0)
}
}
.attachTo(listBinding.root)
adapter.updateData((items.indices).toList())
withContext(Dispatchers.Main) {
binding.loader.isVisible = false
binding.listContainer.addView(listBinding.root)
binding.listContainer.isVisible = true
val idx = items.toList().indexOfFirst { it.value == getSelected?.invoke() }
(listBinding.root.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(idx,0)
}
})
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
override fun onStop() {
loadingJobs.forEach { it.cancel() }
super.onStop()
}
class MenuItem<T>(val title: String, val value: T? = null)
}

View File

@ -61,9 +61,5 @@ class BottomSheetWeatherProviderSettings(context: Context, callback: () -> Unit)
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -26,9 +26,5 @@ class CustomNotesDialog(context: Context, callback: (() -> Unit)?) : BottomSheet
binding.notes.requestFocus()
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -18,7 +18,7 @@ class FixedFocusScrollView @JvmOverloads constructor(
var isScrollable = true
override fun scrollTo(x: Int, y: Int) {
if (isScrollable || !isLaidOut) {
if (isScrollable) {
super.scrollTo(x, y)
}
}

View File

@ -6,9 +6,18 @@ import android.app.AlarmManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.view.LayoutInflater
import androidx.core.view.isVisible
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.GlanceProviderSettingsLayoutBinding
import com.tommasoberlose.anotherwidget.global.Constants
@ -17,6 +26,7 @@ import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GreetingsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.ui.activities.tabs.AppNotificationsFilterActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.MediaInfoFormatActivity
import com.tommasoberlose.anotherwidget.ui.activities.tabs.MusicPlayersFilterActivity
@ -41,7 +51,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_title)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_title)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_title)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_title)
}
/* SUBTITLE*/
@ -54,7 +63,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> context.getString(R.string.settings_show_notifications_subtitle)
Constants.GlanceProviderId.GREETINGS -> context.getString(R.string.settings_show_greetings_subtitle)
Constants.GlanceProviderId.EVENTS -> context.getString(R.string.settings_show_events_as_glance_provider_subtitle)
Constants.GlanceProviderId.WEATHER -> context.getString(R.string.settings_show_weather_as_glance_provider_subtitle)
}
/* SONG */
@ -80,6 +88,14 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
checkNextAlarm()
}
/* GOOGLE STEPS */
binding.actionToggleGoogleFit.isVisible = provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS
if (provider == Constants.GlanceProviderId.GOOGLE_FIT_STEPS) {
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
/* BATTERY INFO */
if (provider == Constants.GlanceProviderId.BATTERY_LEVEL_LOW) {
binding.warningContainer.isVisible = false
@ -124,13 +140,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
checkCalendarConfig()
}
/* WEATHER */
if (provider == Constants.GlanceProviderId.WEATHER) {
binding.header.isVisible = false
binding.divider.isVisible = false
checkWeatherConfig()
}
/* TOGGLE */
binding.providerSwitch.setCheckedImmediatelyNoEvent(when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> Preferences.showMusic
@ -141,7 +150,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> Preferences.showNotifications
Constants.GlanceProviderId.GREETINGS -> Preferences.showGreetings
Constants.GlanceProviderId.EVENTS -> Preferences.showEventsAsGlanceProvider
Constants.GlanceProviderId.WEATHER -> Preferences.showWeatherAsGlanceProvider
})
var job: Job? = null
@ -159,8 +167,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked
checkNextAlarm()
if (!isChecked)
AlarmHelper.clearTimeout(context)
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked
@ -168,24 +174,39 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked
checkLastNotificationsPermission()
if (!isChecked)
ActiveNotificationsHelper.clearLastNotification(context)
}
Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked
GreetingsHelper.toggleGreetings(context)
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (isChecked) {
val account: GoogleSignInAccount? =
GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS
)
) {
val mGoogleSignInClient =
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS
).build())
context.startActivityForResult(mGoogleSignInClient.signInIntent,
2)
} else {
Preferences.showDailySteps = true
}
} else {
Preferences.showDailySteps = false
}
binding.warningContainer.isVisible = false
checkFitnessPermission()
checkGoogleFitConnection()
}
Constants.GlanceProviderId.EVENTS -> {
Preferences.showEventsAsGlanceProvider = isChecked
if (isChecked) {
com.tommasoberlose.anotherwidget.db.EventRepository(context).run {
resetNextEventData()
close()
}
}
}
Constants.GlanceProviderId.WEATHER -> {
Preferences.showWeatherAsGlanceProvider = isChecked
}
else -> {
}
@ -196,10 +217,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
@ -238,19 +255,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
}
private fun checkWeatherConfig() {
if (!Preferences.showWeather || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || Preferences.weatherProviderLocationError != "") {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_show_weather_as_glance_provider_error)
binding.warningContainer.setOnClickListener {
dismiss()
EventBus.getDefault().post(MainFragment.ChangeTabEvent(1))
}
} else {
binding.warningContainer.isVisible = false
}
}
private fun checkNotificationPermission() {
when {
ActiveNotificationsHelper.checkNotificationAccess(context) -> {
@ -289,4 +293,89 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
statusCallback?.invoke()
}
private fun checkFitnessPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION)
) {
if (Preferences.showDailySteps) {
ActivityDetectionReceiver.registerFence(context)
} else {
ActivityDetectionReceiver.unregisterFence(context)
}
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(context)
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
requireFitnessPermission()
}
} else {
ActivityDetectionReceiver.unregisterFence(context)
}
statusCallback?.invoke()
}
private fun checkGoogleFitConnection() {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (!GoogleSignIn.hasPermissions(account,
ActivityDetectionReceiver.FITNESS_OPTIONS
)) {
binding.warningContainer.isVisible = true
binding.warningTitle.text = context.getString(R.string.settings_request_fitness_access)
binding.warningContainer.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
binding.actionConnectToGoogleFit.isVisible = true
binding.actionDisconnectToGoogleFit.isVisible = false
binding.actionConnectToGoogleFit.setOnClickListener {
GoogleSignIn.requestPermissions(
context,
1,
account,
ActivityDetectionReceiver.FITNESS_OPTIONS)
}
binding.actionDisconnectToGoogleFit.setOnClickListener(null)
binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_not_connected)
} else {
binding.actionConnectToGoogleFit.isVisible = false
binding.actionDisconnectToGoogleFit.isVisible = true
binding.actionConnectToGoogleFit.setOnClickListener(null)
binding.actionDisconnectToGoogleFit.setOnClickListener {
GoogleSignIn.getClient(context, GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN).addExtension(
ActivityDetectionReceiver.FITNESS_OPTIONS
).build()).signOut().addOnCompleteListener {
show()
}
}
binding.googleFitStatusLabel.text = context.getString(R.string.google_fit_account_connected)
}
}
private fun requireFitnessPermission() {
Dexter.withContext(context)
.withPermissions(
"com.google.android.gms.permission.ACTIVITY_RECOGNITION",
"android.gms.permission.ACTIVITY_RECOGNITION",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Manifest.permission.ACTIVITY_RECOGNITION else "com.google.android.gms.permission.ACTIVITY_RECOGNITION"
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
checkFitnessPermission()
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
}
})
.check()
}
}

View File

@ -50,10 +50,6 @@ class IconPackSelector(context: Context, private val header: String? = null) : B
binding.menu.addView(itemBinding.root)
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}
}

View File

@ -57,10 +57,6 @@ class MaterialBottomSheetDialog(
}
setContentView(binding.root)
behavior.run {
skipCollapsed = true
state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
}
super.show()
}

View File

@ -3,51 +3,52 @@ package com.tommasoberlose.anotherwidget.db
import android.content.Context
import android.provider.CalendarContract
import android.util.Log
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase
import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import io.realm.Realm
import io.realm.RealmResults
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
class EventRepository(val context: Context) {
private val db by lazy { EventDatabase.getDatabase(context) }
private val realm by lazy { Realm.getDefaultInstance() }
fun saveEvents(eventList: List<Event>) {
db.runInTransaction{
db.dao().run {
deleteAll()
insert(eventList)
}
realm.executeTransaction { realm ->
realm.where(Event::class.java).findAll().deleteAllFromRealm()
realm.copyToRealm(eventList)
}
}
fun clearEvents() {
db.dao().deleteAll()
realm.executeTransaction { realm ->
realm.where(Event::class.java).findAll().deleteAllFromRealm()
}
}
fun resetNextEventData() {
Preferences.bulk {
remove(Preferences::nextEventId)
remove(Preferences::nextEventName)
remove(Preferences::nextEventStartDate)
remove(Preferences::nextEventAllDay)
remove(Preferences::nextEventLocation)
remove(Preferences::nextEventEndDate)
remove(Preferences::nextEventCalendarId)
}
}
fun saveNextEventData(event: Event) {
Preferences.nextEventId = event.id
Preferences.nextEventId = event.eventID
}
fun getNextEvent(): Event? {
val nextEvent = getEventById(Preferences.nextEventId)
val nextEvent = getEventByEventId(Preferences.nextEventId)
val now = Calendar.getInstance().timeInMillis
val limit = Calendar.getInstance().apply {
timeInMillis = now
@ -63,63 +64,75 @@ class EventRepository(val context: Context) {
else -> add(Calendar.HOUR, 6)
}
}
return if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate <= limit.timeInMillis) {
val event = if (nextEvent != null && nextEvent.endDate > now && nextEvent.startDate < limit.timeInMillis) {
nextEvent
} else {
val events = getEvents()
if (events.isNotEmpty()) {
val newNextEvent = events.first()
saveNextEventData(newNextEvent)
Preferences.nextEventId = newNextEvent.eventID
newNextEvent
} else {
resetNextEventData()
null
}
}
return try {
realm.copyFromRealm(event!!)
} catch (ex: Exception) {
event
}
}
fun getEventById(id: Long): Event? {
return db.dao().findById(id)
fun getEventByEventId(id: Long): Event? {
val event = realm.where(Event::class.java).equalTo("eventID", id).findFirst()
return try {
realm.copyFromRealm(event!!)
} catch (ex: Exception) {
event
}
}
fun goToNextEvent() {
val eventList = getEvents()
if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId }
if (index > -1 && index < eventList.size - 1) {
saveNextEventData(eventList[index + 1])
Preferences.nextEventId = eventList[index + 1].eventID
} else {
saveNextEventData(eventList.first())
Preferences.nextEventId = eventList.first().eventID
}
} else {
resetNextEventData()
}
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
}
fun goToPreviousEvent() {
val eventList = getEvents()
if (eventList.isNotEmpty()) {
val index = eventList.indexOfFirst { it.id == Preferences.nextEventId }
val index = eventList.indexOfFirst { it.eventID == Preferences.nextEventId }
if (index > 0) {
saveNextEventData(eventList[index - 1])
Preferences.nextEventId = eventList[index - 1].eventID
} else {
saveNextEventData(eventList.last())
Preferences.nextEventId = eventList.last().eventID
}
} else {
resetNextEventData()
}
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
}
fun getFutureEvents(): List<Event> {
return db.dao().find(Calendar.getInstance().timeInMillis).sortEvents()
val now = Calendar.getInstance().timeInMillis
realm.refresh()
return realm
.where(Event::class.java)
.greaterThan("endDate", now)
.findAll()
.applyFilters()
}
private fun getEvents(): List<Event> {
@ -138,54 +151,18 @@ class EventRepository(val context: Context) {
else -> add(Calendar.HOUR, 6)
}
}
return db.dao().find(now, limit.timeInMillis).sortEvents()
realm.refresh()
return realm
.where(Event::class.java)
.greaterThan("endDate", now)
.lessThanOrEqualTo("startDate", limit.timeInMillis)
.findAll()
.applyFilters()
}
fun getEventsCount(): Int = getEvents().size
fun close() {
// db.close()
}
@Dao
interface EventDao {
@Query("SELECT * FROM events WHERE id = :id LIMIT 1")
fun findById(id: Long): Event?
@Query("SELECT * FROM events WHERE end_date > :from")
fun find(from: Long): List<Event>
@Query("SELECT * FROM events WHERE end_date > :from AND start_date <= :to")
fun find(from: Long, to: Long): List<Event>
@Insert
fun insert(events: List<Event>)
@Query("DELETE FROM events")
fun deleteAll()
}
@Database(entities = arrayOf(Event::class), version = 1, exportSchema = false)
abstract class EventDatabase : RoomDatabase() {
abstract fun dao(): EventDao
companion object {
private var INSTANCE: EventDatabase? = null
fun getDatabase(context: Context): EventDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
EventDatabase::class.java,
"events"
).allowMainThreadQueries().build()
INSTANCE = instance
// return instance
instance
}
}
}
realm.close()
}
}

View File

@ -10,6 +10,7 @@ object Actions {
const val ACTION_OPEN_WEATHER_INTENT = "com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT"
const val ACTION_GO_TO_NEXT_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_NEXT_EVENT"
const val ACTION_GO_TO_PREVIOUS_EVENT = "com.tommasoberlose.anotherwidget.action.GO_TO_PREVIOUS_EVENT"
const val ACTION_REPORT_CRASH = "com.tommasoberlose.anotherwidget.action.REPORT_CRASH"
const val ACTION_CLEAR_NOTIFICATION = "com.tommasoberlose.anotherwidget.action.CLEAR_NOTIFICATION"
const val ACTION_UPDATE_GREETINGS = "com.tommasoberlose.anotherwidget.action.UPDATE_GREETINGS"

View File

@ -23,13 +23,6 @@ object Constants {
LARGE(3)
}
enum class Dimension(val rawValue: Float) {
NONE(0f),
SMALL(8f),
MEDIUM(16f),
LARGE(24f)
}
enum class GlanceProviderId(val id: String) {
PLAYING_SONG("PLAYING_SONG"),
NEXT_CLOCK_ALARM("NEXT_CLOCK_ALARM"),
@ -38,8 +31,7 @@ object Constants {
GOOGLE_FIT_STEPS("GOOGLE_FIT_STEPS"),
NOTIFICATIONS("NOTIFICATIONS"),
GREETINGS("GREETINGS"),
EVENTS("EVENTS"),
WEATHER("WEATHER");
EVENTS("EVENTS");
companion object {
private val map = GlanceProviderId.values().associateBy(GlanceProviderId::id)
@ -91,7 +83,7 @@ object Constants {
enum class WidgetAlign(val rawValue: Int) {
LEFT(0),
RIGHT(1),
// RIGHT(1),
CENTER(2)
}
}

View File

@ -23,6 +23,12 @@ object Preferences : KotprefModel() {
var calendarFilter by stringPref(key = "PREF_CALENDAR_FILTER", default = "")
var nextEventId by longPref(key = "PREF_NEXT_EVENT_ID", default = -1)
var nextEventName by stringPref(key = "PREF_NEXT_EVENT_NAME")
var nextEventStartDate by longPref(key = "PREF_NEXT_EVENT_START_DATE")
var nextEventAllDay by booleanPref(key = "PREF_NEXT_EVENT_ALL_DAY")
var nextEventLocation by stringPref(key = "PREF_NEXT_EVENT_LOCATION")
var nextEventEndDate by longPref(key = "PREF_NEXT_EVENT_END_DATE")
var nextEventCalendarId by intPref(key = "PREF_NEXT_EVENT_CALENDAR_ID")
var customLocationLat by stringPref(key = "PREF_CUSTOM_LOCATION_LAT", default = "")
var customLocationLon by stringPref(key = "PREF_CUSTOM_LOCATION_LON", default = "")
var customLocationAdd by stringPref(key = "PREF_CUSTOM_LOCATION_ADD", default = "")
@ -78,20 +84,16 @@ object Preferences : KotprefModel() {
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// UI
var widgetMargin by floatPref(default = Constants.Dimension.NONE.rawValue)
var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue)
// Clock
var altTimezoneLabel by stringPref(default = "")
var altTimezoneId by stringPref(default = "")
// Global
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 24f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 16f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 72f)
var textMainSize by floatPref(key = "PREF_TEXT_MAIN_SIZE", default = 26f)
var textSecondSize by floatPref(key = "PREF_TEXT_SECOND_SIZE", default = 18f)
var clockTextSize by floatPref(key = "PREF_TEXT_CLOCK_SIZE", default = 90f)
var clockBottomMargin by intPref(default = Constants.ClockBottomMargin.MEDIUM.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.SMALL.rawValue)
var secondRowTopMargin by intPref(default = Constants.SecondRowTopMargin.NONE.rawValue)
var showClock by booleanPref(key = "PREF_SHOW_CLOCK", default = false)
var clockAppName by stringPref(key = "PREF_CLOCK_APP_NAME", default = "")
var clockAppPackage by stringPref(key = "PREF_CLOCK_APP_PACKAGE", default = "")
@ -108,7 +110,6 @@ object Preferences : KotprefModel() {
var customFontName by stringPref(default = "")
var customFontVariant by stringPref(default = "regular")
var showNextEvent by booleanPref(key = "PREF_SHOW_NEXT_EVENT", default = true)
var showNextEventOnMultipleLines by booleanPref(default = false)
var showDividers by booleanPref(default = true)
@ -147,7 +148,6 @@ object Preferences : KotprefModel() {
var appNotificationsFilter by stringPref(default = "")
var showEventsAsGlanceProvider by booleanPref(default = false)
var showWeatherAsGlanceProvider by booleanPref(default = false)
// Integrations
var installedIntegrations by intPref(default = 0)

View File

@ -25,7 +25,6 @@ object ActiveNotificationsHelper {
remove(Preferences::lastNotificationIcon)
}
MainWidget.updateWidget(context)
NotificationListener.clearTimeout(context)
}
fun checkNotificationAccess(context: Context): Boolean {

View File

@ -5,9 +5,10 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.text.format.DateFormat
import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.text.SimpleDateFormat
import java.util.*
@ -43,27 +44,19 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
setExactIfCanSchedule(
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, 0))
setExact(
AlarmManager.RTC,
trigger,
PendingIntent.getBroadcast(
context,
ALARM_UPDATE_ID,
intent,
PendingIntent.FLAG_IMMUTABLE
0
)
)
}
}
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
private const val ALARM_UPDATE_ID = 24953
}

View File

@ -4,9 +4,11 @@ import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
object BitmapHelper {
@ -37,6 +39,15 @@ object BitmapHelper {
1
}
if (draw) {
FirebaseCrashlytics.getInstance().setCustomKey("WIDTH SPEC", measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("HEIGHT SPEC", measuredHeight)
FirebaseCrashlytics.getInstance().setCustomKey("VIEW measuredWidth", view.measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("VIEW measuredHeight", view.measuredHeight)
FirebaseCrashlytics.getInstance().setCustomKey("WIDGET final width", measuredWidth)
FirebaseCrashlytics.getInstance().setCustomKey("WIDGET final height", view.measuredHeight)
}
return try {
val btm = Bitmap.createBitmap(
widgetWidth,
@ -47,12 +58,13 @@ object BitmapHelper {
//Bind a canvas to it
val canvas = Canvas(btm)
// draw the view on the canvas
view.layout(0, 0, widgetWidth, widgetHeight)
view.layout(0, 0, measuredWidth, measuredHeight)
view.draw(canvas)
//return the bitmap
}
btm
} catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8)
}
}

View File

@ -4,9 +4,10 @@ import android.Manifest
import android.content.Context
import android.content.Intent
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.services.EventListenerJob
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
import com.tommasoberlose.anotherwidget.services.UpdateCalendarService
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider
import java.util.*
@ -18,7 +19,7 @@ import kotlin.collections.ArrayList
object CalendarHelper {
fun updateEventList(context: Context) {
UpdateCalendarWorker.enqueue(context)
UpdateCalendarService.enqueueWork(context)
}
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -49,11 +50,11 @@ object CalendarHelper {
}
fun setEventUpdatesAndroidN(context: Context) {
UpdateCalendarWorker.enqueueTrigger(context)
EventListenerJob.schedule(context)
}
fun removeEventUpdatesAndroidN(context: Context) {
UpdateCalendarWorker.cancelTrigger(context)
EventListenerJob.remove(context)
}
fun List<Event>.applyFilters() : List<Event> {

View File

@ -1,20 +1,10 @@
package com.tommasoberlose.anotherwidget.helpers
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color
import android.util.Log
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toast
import kotlin.math.roundToInt
object ColorHelper {
fun getFontColor(isDark: Boolean): Int {
return try {
@ -154,40 +144,4 @@ object ColorHelper {
false
}
}
@SuppressLint("DefaultLocale")
fun Context.copyToClipboard(color: Int?, alpha: Int) {
if (color == null) return toast(getString(R.string.error_copy_color))
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
try {
val colorString = Integer.toHexString(color)
val clip = "#%s%s".format(
alpha.toHexValue(),
if (colorString.length > 6) colorString.substring(2) else colorString
).toUpperCase()
setPrimaryClip(ClipData.newPlainText(clip, clip))
toast(getString(R.string.color_copied))
} catch (ex: Exception) {
ex.printStackTrace()
toast(getString(R.string.error_copy_color))
}
}
}
fun Context.isClipboardColor(): Boolean {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
return try { primaryClip?.getItemAt(0)?.text?.toString()?.isColor() ?: false } catch (ex: Exception) { false }
}
}
fun Context.pasteFromClipboard(pasteColor: (color: String, alpha: String) -> Unit) {
with(getSystemService(CLIPBOARD_SERVICE) as ClipboardManager) {
primaryClip?.let {
val item = it.getItemAt(0).text.toString().replace("#", "")
val color = if (item.length > 6) item.substring(2) else item
val alpha = if (item.length > 6) item.substring(0, 2) else "00"
pasteColor("#$color", alpha)
}
}
}
}

View File

@ -90,12 +90,6 @@ object GlanceProviderHelper {
R.drawable.round_event_note_24
)
}
Constants.GlanceProviderId.WEATHER -> {
GlanceProvider(providerId.id,
context.getString(R.string.settings_show_weather_as_glance_provider_title),
R.drawable.round_brightness_5_24
)
}
}
}
@ -114,7 +108,6 @@ object GlanceProviderHelper {
(MediaPlayerHelper.isSomeonePlaying(context)) ||
(Preferences.showBatteryCharging && Preferences.isCharging || Preferences.isBatteryLevelLow) ||
(Preferences.customNotes.isNotEmpty()) ||
(Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") ||
(Preferences.showDailySteps && Preferences.googleFitSteps > 0) ||
(Preferences.showGreetings && GreetingsHelper.showGreetings()) ||
(Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(

View File

@ -37,7 +37,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
PendingIntent.FLAG_IMMUTABLE)
0)
)
setRepeating(
@ -51,7 +51,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
PendingIntent.FLAG_IMMUTABLE)
0)
)
setRepeating(
@ -65,7 +65,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
PendingIntent.FLAG_IMMUTABLE)
0)
)
setRepeating(
@ -79,14 +79,14 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
PendingIntent.FLAG_IMMUTABLE)
0)
)
} else {
listOf(MORNING_TIME, MORNING_TIME_END, EVENING_TIME, NIGHT_TIME).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context,
UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
}, PendingIntent.FLAG_IMMUTABLE))
}, 0))
}
}
}
@ -102,7 +102,7 @@ object GreetingsHelper {
val array = when {
hour in 5..8 -> context.resources.getStringArray(R.array.morning_greetings)
hour in 19..21 -> context.resources.getStringArray(R.array.evening_greetings)
hour >= 22 || hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
hour >= 22 && hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
else -> emptyArray()
}
return if (array.isNotEmpty()) array[Random().nextInt(array.size)] else ""

View File

@ -17,7 +17,7 @@ object ImageHelper {
0 -> 0f * factor
1 -> 8f * factor
2 -> 16f * factor
else -> 8f * factor
else -> 0f * factor
}, resources.displayMetrics)
if (originalView.drawable != null && originalView.drawable.intrinsicWidth > 0 && originalView.drawable.intrinsicHeight > 0) {
@ -58,7 +58,7 @@ object ImageHelper {
0 -> 0f * factor
1 -> 0.8f * factor
2 -> 1f * factor
else -> 0.8f * factor
else -> 0f
}))
colorMatrixScript.setColorMatrix(matrix)
@ -73,9 +73,6 @@ object ImageHelper {
allocationIn.destroy()
allocationOut.destroy()
colorMatrixScript.destroy()
blurScript.destroy()
//rs.destroy()
return bitmap
}

View File

@ -1,6 +1,5 @@
package com.tommasoberlose.anotherwidget.helpers
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.ContentUris
@ -28,13 +27,6 @@ object IntentHelper {
const val DO_NOTHING_OPTION = "DO_NOTHING"
const val REFRESH_WIDGET_OPTION = "REFRESH_WIDGET"
fun getPendingIntent(context: Context, requestCode: Int, intent: Intent, flags: Int): PendingIntent {
return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
PendingIntent.getActivity(context, requestCode, intent, flags)
else
PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
fun getWidgetUpdateIntent(context: Context): Intent {
val widgetManager = AppWidgetManager.getInstance(context)
val widgetComponent = ComponentName(context, MainWidget::class.java)
@ -48,19 +40,21 @@ object IntentHelper {
private fun getWidgetRefreshIntent(context: Context): Intent {
return Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_REFRESH
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
fun getGoogleMapsIntentFromAddress(context: Context, address: String): Intent {
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=${Uri.encode(address)}")
val gmmIntentUri: Uri = Uri.parse("geo:0,0?q=$address")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
//mapIntent.`package` = "com.google.android.apps.maps"
mapIntent.`package` = "com.google.android.apps.maps"
return if (mapIntent.resolveActivity(context.packageManager) != null) {
mapIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
mapIntent
} else {
val map = "https://www.google.com/maps/search/?api=1&query=${Uri.encode(address)}"
Intent(Intent.ACTION_VIEW, Uri.parse(map)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val map = "http://maps.google.co.in/maps?q=$address"
val i = Intent(Intent.ACTION_VIEW, Uri.parse(map));
i
}
}
@ -68,6 +62,7 @@ object IntentHelper {
return when (Preferences.weatherAppPackage) {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = Uri.parse("dynact://velour/weather/ProxyActivity")
component = ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline")
@ -94,16 +89,15 @@ object IntentHelper {
}
}
fun getCalendarIntent(context: Context, time: Long? = null): Intent {
fun getCalendarIntent(context: Context): Intent {
val calendarUri = CalendarContract.CONTENT_URI
.buildUpon()
.appendPath("time")
.appendPath((time ?: Calendar.getInstance().timeInMillis).toString())
.appendPath(Calendar.getInstance().timeInMillis.toString())
.build()
return when (Preferences.calendarAppPackage) {
DEFAULT_OPTION -> {
Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
data = calendarUri
}
}
@ -117,8 +111,6 @@ object IntentHelper {
val pm: PackageManager = context.packageManager
try {
pm.getLaunchIntentForPackage(Preferences.calendarAppPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Intent.ACTION_VIEW
data = calendarUri
}
@ -185,7 +177,7 @@ object IntentHelper {
}
}
false -> {
getCalendarIntent(context, e.startDate)
getCalendarIntent(context)
}
}
}
@ -217,7 +209,7 @@ object IntentHelper {
}
fun getBatteryIntent(): Intent {
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
return Intent(Intent.ACTION_POWER_USAGE_SUMMARY)
}
fun getMusicIntent(context: Context): Intent {
@ -230,7 +222,6 @@ object IntentHelper {
try {
pm.getLaunchIntentForPackage(Preferences.mediaPlayerPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
@ -244,7 +235,6 @@ object IntentHelper {
return try {
pm.getLaunchIntentForPackage("com.google.android.apps.fitness")!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()
@ -256,7 +246,6 @@ object IntentHelper {
return try {
pm.getLaunchIntentForPackage(Preferences.lastNotificationPackage)!!.apply {
addCategory(Intent.CATEGORY_LAUNCHER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
} catch (e: Exception) {
Intent()

View File

@ -12,7 +12,6 @@ import com.chibatching.kotpref.bulk
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.receivers.NotificationListener
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.ignoreExceptions
import java.lang.Exception
object MediaPlayerHelper {
@ -70,26 +69,17 @@ object MediaPlayerHelper {
isSomeonePlaying = true
if (metadata != null) {
Preferences.bulk {
ignoreExceptions {
mediaPlayerTitle =
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)
?.toString()
metadata.getText(MediaMetadata.METADATA_KEY_TITLE)?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerArtist =
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)
?.toString()
metadata.getText(MediaMetadata.METADATA_KEY_ARTIST)?.toString()
?: ""
}
ignoreExceptions {
mediaPlayerAlbum =
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)
?.toString()
metadata.getText(MediaMetadata.METADATA_KEY_ALBUM)?.toString()
?: ""
}
}
}
Preferences.mediaPlayerPackage = mc.packageName
}

View File

@ -88,17 +88,19 @@ object SettingsStringHelper {
fun getDifferenceText(context: Context, now: Long, start: Long): String {
val nowDate = DateTime(now)
val eventDate = DateTime(start)
val difference = start - now
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
when {
difference <= 0 -> {
return ""
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.HIGH.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 5), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) >= 15 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - TimeUnit.MILLISECONDS.toMinutes(difference) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.DEFAULT.rawValue && TimeUnit.MILLISECONDS.toMinutes(difference) > 5 -> {
return DateUtils.getRelativeTimeSpanString(start, start - 1000 * 60 * (TimeUnit.MILLISECONDS.toMinutes(difference) - 1 - (TimeUnit.MILLISECONDS.toMinutes(difference) - 1) % 15), DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
}
TimeUnit.MILLISECONDS.toHours(difference) < 1 && Preferences.widgetUpdateFrequency == Constants.WidgetUpdateFrequency.LOW.rawValue -> {
return context.getString(R.string.soon)
@ -107,7 +109,22 @@ object SettingsStringHelper {
return context.getString(R.string.now)
}
TimeUnit.MILLISECONDS.toHours(difference) < 12 -> {
return DateUtils.getRelativeTimeSpanString(start, now, DateUtils.HOUR_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE).toString()
val minutes = TimeUnit.MILLISECONDS.toMinutes(difference) - 60 * TimeUnit.MILLISECONDS.toHours(difference)
return if (minutes < 1 || minutes > 30) {
DateUtils.getRelativeTimeSpanString(
start,
now - 1000 * 60 * 40,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
} else {
DateUtils.getRelativeTimeSpanString(
start,
now,
DateUtils.HOUR_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
).toString()
}
}
eventDate.dayOfYear == nowDate.plusDays(1).dayOfYear -> {
return String.format("%s", context.getString(R.string.tomorrow))
@ -126,6 +143,9 @@ object SettingsStringHelper {
val nowDate = DateTime(now)
val eventDate = DateTime(start)
var difference = start - now
difference += 60 * 1000 - (difference % (60 * 1000))
return when (eventDate.dayOfYear) {
nowDate.dayOfYear -> {
""

View File

@ -7,7 +7,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.services.LocationService
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
@ -19,14 +19,13 @@ import com.tommasoberlose.anotherwidget.utils.isDarkTheme
object WeatherHelper {
fun updateWeather(context: Context, force: Boolean = false) {
if (Preferences.showWeather || force)
WeatherWorker.enqueue(context, replace = force)
else {
removeWeather(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
)
suspend fun updateWeather(context: Context) {
Kotpref.init(context)
val networkApi = WeatherNetworkApi(context)
if (Preferences.customLocationAdd != "") {
networkApi.updateWeather()
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
LocationService.requestNewLocation(context)
}
}
@ -314,111 +313,92 @@ object WeatherHelper {
}
}
fun getWeatherLabel(context: Context, icon: String): String {
return when (icon) {
"01d", "01n" -> context.getString(R.string.weather_label_clear)
"02d", "02n" -> context.getString(R.string.weather_label_partly_cloudy)
"03d", "03n" -> context.getString(R.string.weather_label_mostly_cloudy)
"04d", "04n" -> context.getString(R.string.weather_label_cloudy_weather)
"09d", "09n" -> context.getString(R.string.weather_label_storm_weather)
"10d", "10n" -> context.getString(R.string.weather_label_rainy)
"11d", "11n" -> context.getString(R.string.weather_label_thunder)
"13d", "13n" -> context.getString(R.string.weather_label_snow)
"50d", "50n", "82d", "82n" -> context.getString(R.string.weather_label_haze)
"80d", "80n" -> context.getString(R.string.weather_label_windy)
"81d", "81n" -> context.getString(R.string.weather_label_rain_snow)
else -> context.getString(R.string.weather_label_unknown)
}
}
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (
iconString.substringBefore('?').substringAfterLast('/').substringBefore(',')
) {
"skc" -> "01"
"few" -> "02"
"sct" -> "02"
"bkn" -> "03"
"ovc" -> "04"
"wind_skc" -> "01"
"wind_few" -> "02"
"wind_sct" -> "02"
"wind_bkn" -> "03"
"wind_ovc" -> "04"
"snow" -> "13"
"rain_snow" -> "81"
"rain_sleet" -> "81"
"snow_sleet" -> "81"
"fzra" -> "81"
"rain_fzra" -> "81"
"snow_fzra" -> "81"
"sleet" -> "81"
"rain" -> "10"
"rain_showers" -> "10"
"rain_showers_hi" -> "10"
"tsra" -> "09"
"tsra_sct" -> "11"
"tsra_hi" -> "11"
"tornado" -> "80"
"hurricane" -> "80"
"tropical_storm" -> "09"
"dust" -> "50"
"smoke" -> "50"
"haze" -> "50"
"hot" -> "01"
"cold" -> "13"
"blizzard" -> "13"
"fog" -> "82"
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.substring(0, 3)) {
"t01" -> "11"
"t02" -> "11"
"t03" -> "09"
"t04" -> "11"
"t05" -> "11"
"d01" -> "10"
"d02" -> "10"
"d03" -> "10"
"r01" -> "10"
"r02" -> "10"
"r03" -> "10"
"f01" -> "10"
"r04" -> "10"
"r05" -> "10"
"r06" -> "10"
"s01" -> "13"
"s02" -> "13"
"s03" -> "13"
"s04" -> "81"
"s05" -> "81"
"s06" -> "13"
"a01" -> "50"
"a02" -> "50"
"a03" -> "50"
"a04" -> "50"
"a05" -> "82"
"a06" -> "82"
"c01" -> "01"
"c02" -> "02"
"c03" -> "03"
"c04" -> "04"
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 -> ""
} + iconString.substring(3)
} + 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 -> "50"
1030 -> "82"
1063 -> "10"
1066 -> "13"
1069 -> "81"
1066 -> "10"
1069 -> "10"
1072 -> "81"
1087 -> "11"
1114 -> "13"
1117 -> "13"
1117 -> "09"
1135 -> "82"
1147 -> "82"
1150 -> "10"
@ -433,8 +413,8 @@ object WeatherHelper {
1195 -> "10"
1198 -> "81"
1201 -> "81"
1204 -> "81"
1207 -> "81"
1204 -> "13"
1207 -> "13"
1210 -> "13"
1213 -> "13"
1216 -> "13"
@ -445,62 +425,62 @@ object WeatherHelper {
1240 -> "10"
1243 -> "10"
1246 -> "10"
1249 -> "81"
1252 -> "81"
1249 -> "13"
1252 -> "13"
1255 -> "13"
1258 -> "13"
1261 -> "13"
1264 -> "13"
1273 -> "11"
1273 -> "09"
1276 -> "09"
1279 -> "13"
1282 -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
fun getYRIcon(iconCode: String): String = when (iconCode.substringBefore('_')) {
"clearsky" -> "01"
"cloudy" -> "04"
"fair" -> "02"
"fog" -> "82"
"heavyrain" -> "10"
"heavyrainandthunder" -> "09"
"heavyrainshowers" -> "10"
"heavyrainshowersandthunder" -> "09"
"heavysleet" -> "81"
"heavysleetandthunder" -> "81"
"heavysleetshowers" -> "81"
"heavysleetshowersandthunder" -> "81"
"heavysnow" -> "13"
"heavysnowandthunder" -> "13"
"heavysnowshowers" -> "13"
"heavysnowshowersandthunder" -> "13"
"lightrain" -> "10"
"lightrainandthunder" -> "11"
"lightrainshowers" -> "10"
"lightrainshowersandthunder" -> "11"
"lightsleet" -> "81"
"lightsleetandthunder" -> "81"
"lightsleetshowers" -> "81"
"lightsnow" -> "13"
"lightsnowandthunder" -> "13"
"lightsnowshowers" -> "13"
"lightssleetshowersandthunder" -> "81"
"lightssnowshowersandthunder" -> "81"
"partlycloudy" -> "03"
"rain" -> "10"
"rainandthunder" -> "11"
"rainshowers" -> "10"
"rainshowersandthunder" -> "11"
"sleet" -> "81"
"sleetandthunder" -> "81"
"sleetshowers" -> "81"
"sleetshowersandthunder" -> "81"
"snow" -> "13"
"snowandthunder" -> "13"
"snowshowers" -> "13"
"snowshowersandthunder" -> "13"
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 (iconCode.substringAfter('_', "day") == "day") "d" else "n"
} + if (isDaytime) "d" else "n"
}

View File

@ -6,10 +6,17 @@ 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(
@ -18,17 +25,12 @@ object WidgetHelper {
) {
fun getWidgetsSize(widgetId: Int): Pair<Int, Int> {
val portrait = context.resources.configuration.orientation == ORIENTATION_PORTRAIT
val width = getWidgetSizeInDp(
widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH else AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
)
val height = getWidgetSizeInDp(
widgetId,
if (portrait) AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT else AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
)
val width = getWidgetWidth(widgetId)
val height = getWidgetHeight(widgetId)
val widthInPx = context.dip(width)
val heightInPx = context.dip(height)
FirebaseCrashlytics.getInstance().setCustomKey("widthInPx", widthInPx)
FirebaseCrashlytics.getInstance().setCustomKey("heightInPx", heightInPx)
return widthInPx to heightInPx
}
@ -61,23 +63,21 @@ object WidgetHelper {
R.array.com_google_android_gms_fonts_certs
)
val handlerThread = HandlerThread("generateView")
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
handlerThread.quit()
function.invoke(typeface)
}
override fun onTypefaceRequestFailed(reason: Int) {
handlerThread.quit()
function.invoke(null)
}
}
val handlerThread = HandlerThread("generateView")
handlerThread.start()
//if (Looper.myLooper() == null) {
// Looper.prepare()
//}
if (Looper.myLooper() == null) {
Looper.prepare()
}
Handler(handlerThread.looper).run {
FontsContractCompat.requestFont(context, request, callback, this)

View File

@ -1,35 +1,26 @@
package com.tommasoberlose.anotherwidget.models
import android.provider.CalendarContract
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import io.realm.RealmObject
import java.util.Date
/**
* Created by tommaso on 05/10/17.
*/
@Entity(tableName = "events")
data class Event(
@PrimaryKey
val id: Long = 0,
@ColumnInfo(name = "event_id")
val eventID: Long = 0,
val title: String = "",
@ColumnInfo(name = "start_date")
val startDate: Long = 0,
@ColumnInfo(name = "end_date")
val endDate: Long = 0,
@ColumnInfo(name = "calendar_id")
val calendarID: Long = 0,
@ColumnInfo(name = "all_day")
val allDay: Boolean = false,
val address: String = "",
@ColumnInfo(name = "self_attendee_status")
val selfAttendeeStatus: Int = CalendarContract.Attendees.ATTENDEE_STATUS_NONE,
val availability: Int = CalendarContract.EventsEntity.AVAILABILITY_BUSY
)/* {
open class Event(
var id: Long = 0,
var eventID: Long = 0,
var title: String = "",
var startDate: Long = 0,
var endDate: Long = 0,
var calendarID: Int = 0,
var allDay: Boolean = false,
var address: String = "",
var selfAttendeeStatus: Int = CalendarContract.Attendees.ATTENDEE_STATUS_NONE,
var availability: Int = CalendarContract.EventsEntity.AVAILABILITY_BUSY
) : RealmObject() {
override fun toString(): String {
return "Event:\nEVENT ID: " + eventID + "\nTITLE: " + title + "\nSTART DATE: " + Date(startDate) + "\nEND DATE: " + Date(endDate) + "\nCAL ID: " + calendarID + "\nADDRESS: " + address
}
}*/
}

View File

@ -26,7 +26,9 @@ class TimeZonesApi(val context: Context) {
when (val response = repository.getTimeZone(lat, long)) {
is NetworkResponse.Success -> {
try {
Log.d("ciao", response.body.toString())
id = response.body["timezoneId"] as String
} catch(ex: Exception) {
ex.printStackTrace()
}

View File

@ -17,11 +17,13 @@ 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 kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import org.greenrobot.eventbus.EventBus
import java.util.*
class WeatherNetworkApi(val context: Context) {
suspend fun updateWeather() {
@ -29,7 +31,7 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
if (Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
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)
@ -40,67 +42,39 @@ class WeatherNetworkApi(val context: Context) {
Constants.WeatherProvider.YR -> useYrProvider(context)
}
} else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private suspend fun useOpenWeatherMap(context: Context) {
private fun useOpenWeatherMap(context: Context) {
if (Preferences.weatherProviderApiOpen != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen)
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC)
when (val response = suspendCancellableCoroutine<Any?> { continuation ->
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback {
override fun onSuccess(currentWeather: CurrentWeather?) {
continuation.resume(currentWeather)
}
override fun onFailure(throwable: Throwable?) {
continuation.resume(throwable)
}
})
}) {
is CurrentWeather -> {
Preferences.weatherTemp = response.main.temp.toFloat()
Preferences.weatherIcon = response.weather[0].icon
currentWeather?.let {
Preferences.weatherTemp = currentWeather.main.temp.toFloat()
Preferences.weatherIcon = currentWeather.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
is Throwable -> {
if (response.javaClass == Throwable::class.java) {
// server error, see [OpenWeatherMapHelper.handleCurrentWeatherResponse]
if (response.message?.startsWith("UnAuthorized") == true) {
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
)
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
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 = ""
@ -140,17 +114,8 @@ class WeatherNetworkApi(val context: Context) {
val props =
weatherResponse.body["properties"] as LinkedTreeMap<*, *>
val periods = props["periods"] as List<*>
@android.annotation.SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
"yyyy-MM-dd'T'HH:mm:ssXXX"
else
"yyyy-MM-dd'T'HH:mm:ssZ"
)
for (period in periods) {
val now = period as LinkedTreeMap<*, *>
val endTime = format.parse(now["endTime"] as String)!!
if (endTime.time > System.currentTimeMillis()) {
val now = periods[0] as LinkedTreeMap<*, *>
val temp = now["temperature"] as Double
val fullIcon = now["icon"] as String
val isDaytime = now["isDaytime"] as Boolean
@ -158,16 +123,16 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getWeatherGovIcon(fullIcon, isDaytime)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
break
}
}
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 -> {
@ -183,16 +148,14 @@ class WeatherNetworkApi(val context: Context) {
}
}
is NetworkResponse.ServerError -> {
when (pointsResponse.code) {
404 -> {
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 -> {
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
WeatherHelper.removeWeather(
context
)
@ -213,18 +176,7 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val observations = response.body["observations"] as LinkedTreeMap<*, *>
val location = (observations["location"] as List<*>).first() as LinkedTreeMap<*, *>
val observation = (location["observation"] as List<*>).first() as LinkedTreeMap<*, *>
val iconName = observation["iconName"] as String
val daylight = observation["daylight"] as String
val temperature = observation["temperature"] as String
Preferences.weatherTemp = temperature.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(iconName, daylight != "N")
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Log.d("ciao - here", response.body.toString())
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch(ex: Exception) {
@ -235,16 +187,8 @@ class WeatherNetworkApi(val context: Context) {
}
}
is NetworkResponse.ServerError -> {
when (response.code) {
401 -> {
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
)
@ -274,10 +218,10 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val data = response.body["data"] as List<*>?
data?.first()?.let { it as LinkedTreeMap<*, *>
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<*, *>
val weatherInfo = it["weather"] as LinkedTreeMap<String, Any>
val iconCode = weatherInfo["icon"] as String
Preferences.weatherTemp = temp.toFloat()
@ -287,6 +231,8 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
@ -335,12 +281,12 @@ class WeatherNetworkApi(val context: Context) {
when (val response = repository.getWeather()) {
is NetworkResponse.Success -> {
try {
val current = response.body["current"] as LinkedTreeMap<*, *>?
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<*, *>
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()
@ -350,6 +296,8 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
} catch(ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
@ -398,74 +346,28 @@ class WeatherNetworkApi(val context: Context) {
private suspend fun useAccuweatherProvider(context: Context) {
if (Preferences.weatherProviderApiAccuweather != "") {
val repository = AccuweatherRepository()
// val repository = AccuweatherRepository()
when (val locationResponse = repository.getLocation()) {
is NetworkResponse.Success -> {
try {
val key = locationResponse.body["Key"] as String
// 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())
// }
when (val weatherResponse = repository.getWeather(key)) {
is NetworkResponse.Success -> {
try {
weatherResponse.body.first().let {
val temp = it["Temperature"] as LinkedTreeMap<*, *>
val tempC = (temp["Metric"] as LinkedTreeMap<*, *>)["Value"] as Double
val tempF = (temp["Imperial"] as LinkedTreeMap<*, *>)["Value"] as Double
val isDay = it["IsDayTime"] as Boolean
val icon = it["WeatherIcon"] as Double
Preferences.weatherTemp = if (Preferences.weatherTempUnit == "F") tempF.toFloat() else tempC.toFloat()
Preferences.weatherIcon = repository.getWeatherIcon(icon.toInt(), isDay)
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
} catch (ex: Exception) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
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 -> {
when (locationResponse.code) {
401 -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
503 -> {
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 = ""
@ -484,9 +386,14 @@ class WeatherNetworkApi(val context: Context) {
is NetworkResponse.Success -> {
try {
val pp = response.body["properties"] as LinkedTreeMap<*, *>
val data = pp["timeseries"] as List<*>?
data?.first()?.let { it as LinkedTreeMap<*, *>
val dd = it["data"] 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<*, *>
@ -497,14 +404,20 @@ class WeatherNetworkApi(val context: Context) {
val iconCode = summary["symbol_code"] as String
Preferences.weatherTemp = temp.toFloat()
Preferences.weatherIcon = WeatherHelper.getYRIcon(iconCode)
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)

View File

@ -13,7 +13,7 @@ object ApiServices {
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@Headers("User-Agent: (Another Widget, tommaso.berlose@gmail.com)")
@GET("gridpoints/{gridId}/{gridX},{gridY}/forecast/hourly")
@GET("gridpoints/{gridId}/{gridX},{gridY}/forecast")
suspend fun getWeather(
@Path("gridId") gridId: String,
@Path("gridX") gridX: Int,
@ -54,17 +54,13 @@ object ApiServices {
}
interface AccuweatherService {
@GET("locations/v1/cities/geoposition/search")
suspend fun getLocation(
@Query("apikey") apikey: String,
@Query("q") location: String
): NetworkResponse<HashMap<String, Any>, HashMap<String, Any>>
@GET("currentconditions/v1/{locationKey}")
@GET("")
suspend fun getWeather(
@Path("locationKey") locationKey: String,
@Query("apikey") apikey: String
): NetworkResponse<List<HashMap<String, Any>>, HashMap<String, Any>>
@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 {

View File

@ -1,7 +1,6 @@
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
@ -10,11 +9,10 @@ class AccuweatherRepository {
/* ACCUWEATHER */
private val apiServiceAccu: ApiServices.AccuweatherService = getRetrofit().create(ApiServices.AccuweatherService::class.java)
suspend fun getLocation() = apiServiceAccu.getLocation(Preferences.weatherProviderApiAccuweather, "${Preferences.customLocationLat},${Preferences.customLocationLon}")
suspend fun getWeather(locationKey: String) = apiServiceAccu.getWeather(locationKey, Preferences.weatherProviderApiAccuweather)
suspend fun getWeather(): Nothing = TODO()
companion object {
private const val BASE_URL_ACCU = "https://dataservice.accuweather.com/"
private const val BASE_URL_ACCU = ""
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
@ -24,20 +22,4 @@ class AccuweatherRepository {
.build()
}
}
fun getWeatherIcon(icon: Int, isDaytime: Boolean): String = when(icon) {
1, 2, 30, 33, 34 -> "01"
3, 4, 35, 36 -> "02"
5, 37 -> "50"
6, 38 -> "03"
7, 8 -> "04"
11 -> "82"
12, 13, 14, 18, 39, 40 -> "10"
15 -> "09"
16, 17, 41, 42 -> "11"
32 -> "80"
19, 20, 21, 22, 23, 24, 31, 43, 44 -> "13"
25, 26, 29 -> "81"
else -> ""
} + if (isDaytime) "d" else "n"
}

View File

@ -23,157 +23,4 @@ class HereRepository {
.build()
}
}
fun getWeatherIcon(iconName: String, isDaytime: Boolean): String = when(iconName.substringAfter("night_")) {
"sunny" -> "01"
"clear" -> "01"
"mostly_sunny" -> "01"
"mostly_clear" -> "01"
"passing_clounds" -> "02"
"more_sun_than_clouds" -> "02"
"scattered_clouds" -> "02"
"partly_cloudy" -> "02"
"a_mixture_of_sun_and_clouds" -> "03"
"increasing_cloudiness" -> "03"
"breaks_of_sun_late" -> "03"
"afternoon_clouds" -> "03"
"morning_clouds" -> "03"
"partly_sunny" -> "03"
"high_level_clouds" -> "03"
"decreasing_cloudiness" -> "03"
"clearing_skies" -> "01"
"high_clouds" -> "03"
"rain_early" -> "10"
"heavy_rain_early" -> "10"
"strong_thunderstorms" -> "09"
"severe_thunderstorms" -> "09"
"thundershowers" -> "11"
"thunderstorms" -> "11"
"tstorms_early" -> "11"
"isolated_tstorms_late" -> "11"
"scattered_tstorms_late" -> "11"
"tstorms_late" -> "11"
"tstorms" -> "11"
"ice_fog" -> "82"
"more_clouds_than_sun" -> "03"
"broken_clouds" -> "03"
"scattered_showers" -> "10"
"a_few_showers" -> "10"
"light_showers" -> "10"
"passing_showers" -> "10"
"rain_showers" -> "10"
"showers" -> "10"
"widely_scattered_tstorms" -> "11"
"isolated_tstorms" -> "11"
"a_few_tstorms" -> "11"
"scattered_tstorms" -> "11"
"hazy_sunshine" -> "50"
"haze" -> "50"
"smoke" -> "50"
"low_level_haze" -> "50"
"early_fog_followed_by_sunny_skies" -> "50"
"early_fog" -> "82"
"light_fog" -> "82"
"fog" -> "82"
"dense_fog" -> "82"
//"night_haze"
//"night_smoke"
//"night_low_level_haze"
//"night_widely_scattered_tstorms"
//"night_isolated_tstorms"
//"night_a_few_tstorms"
//"night_scattered_tstorms"
//"night_tstorms"
//"night_clear"
"mostly_cloudy" -> "03"
"cloudy" -> "04"
"overcast" -> "04"
"low_clouds" -> "03"
"hail" -> "10"
"sleet" -> "81"
"light_mixture_of_precip" -> "81"
"icy_mix" -> "81"
"mixture_of_precip" -> "81"
"heavy_mixture_of_precip" -> "81"
"snow_changing_to_rain" -> "81"
"snow_changing_to_an_icy_mix" -> "81"
"an_icy_mix_changing_to_snow" -> "81"
"an_icy_mix_changing_to_rain" -> "81"
"rain_changing_to_snow" -> "81"
"rain_changing_to_an_icy_mix" -> "81"
"light_icy_mix_early" -> "81"
"icy_mix_early" -> "81"
"light_icy_mix_late" -> "81"
"icy_mix_late" -> "81"
"snow_rain_mix" -> "81"
"scattered_flurries" -> "13"
"snow_flurries" -> "13"
"light_snow_showers" -> "13"
"snow_showers" -> "13"
"light_snow" -> "13"
"flurries_early" -> "13"
"snow_showers_early" -> "13"
"light_snow_early" -> "13"
"flurries_late" -> "13"
"snow_showers_late" -> "13"
"light_snow_late" -> "13"
//"night_decreasing_cloudiness"
//"night_clearing_skies"
//"night_high_level_clouds"
//"night_high_clouds"
//"night_scattered_showers"
//"night_a_few_showers"
//"night_light_showers"
//"night_passing_showers"
//"night_rain_showers"
//"night_sprinkles"
//"night_showers"
//"night_mostly_clear"
//"night_passing_clouds"
//"night_scattered_clouds"
//"night_partly_cloudy"
//"increasing_cloudiness"
//"night_afternoon_clouds"
//"night_morning_clouds"
//"night_broken_clouds"
//"night_mostly_cloudy"
"light_freezing_rain" -> "81"
"freezing_rain" -> "81"
"heavy_rain" -> "10"
"lots_of_rain" -> "10"
"tons_of_rain" -> "10"
//"heavy_rain_early" -> "10"
"heavy_rain_late" -> "10"
"flash_floods" -> "10"
"flood" -> "10"
"drizzle" -> "10"
"sprinkles" -> "10"
"light_rain" -> "10"
"sprinkles_early" -> "10"
"light_rain_early" -> "10"
"sprinkles_late" -> "10"
"light_rain_late" -> "10"
"rain" -> "10"
"numerous_showers" -> "10"
"showery" -> "10"
"showers_early" -> "10"
//"rain_early" -> "10"
"showers_late" -> "10"
"rain_late" -> "10"
"snow" -> "13"
"moderate_snow" -> "13"
"snow_early" -> "13"
"snow_late" -> "13"
"heavy_snow" -> "13"
"heavy_snow_early" -> "13"
"heavy_snow_late" -> "13"
"tornado" -> "80"
"tropical_storm" -> "09"
"hurricane" -> "80"
"sandstorm" -> "50"
"duststorm" -> "50"
"snowstorm" -> "13"
"blizzard" -> "13"
else -> ""
} + if (isDaytime) "d" else "n"
}

View File

@ -0,0 +1,181 @@
package com.tommasoberlose.anotherwidget.receivers
import android.Manifest
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
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
import com.google.android.gms.fitness.FitnessOptions
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.data.Field.FIELD_STEPS
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.location.*
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.*
import java.util.concurrent.TimeUnit
class ActivityDetectionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ActivityTransitionResult.hasResult(intent)) {
val result = ActivityTransitionResult.extractResult(intent)!!
val lastEvent = result.transitionEvents.last()
if (lastEvent.activityType == DetectedActivity.WALKING || lastEvent.activityType == DetectedActivity.RUNNING && lastEvent.transitionType == ActivityTransition.ACTIVITY_TRANSITION_EXIT) {
requestDailySteps(context)
}
} else {
if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_MY_PACKAGE_REPLACED && Preferences.showDailySteps && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION)) {
resetDailySteps(context)
registerFence(context)
} else {
resetDailySteps(context)
}
}
}
private fun resetDailySteps(context: Context) {
Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::googleFitSteps)
}
}
companion object {
val FITNESS_OPTIONS: FitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
.build()
fun registerFence(context: Context) {
Kotpref.init(context)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION)) {
val transitions = mutableListOf<ActivityTransition>()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
transitions +=
ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
val request = ActivityTransitionRequest(transitions)
// myPendingIntent is the instance of PendingIntent where the app receives callbacks.
val task = ActivityRecognition.getClient(context)
.requestActivityTransitionUpdates(
request,
PendingIntent.getBroadcast(
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
)
)
task.addOnFailureListener { e: Exception ->
e.printStackTrace()
Preferences.showDailySteps = false
}
}
}
fun unregisterFence(context: Context) {
val task = ActivityRecognition.getClient(context)
.removeActivityTransitionUpdates(
PendingIntent.getBroadcast(
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
)
)
task.addOnCompleteListener {
if (it.isSuccessful) {
PendingIntent.getBroadcast(
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
).cancel()
}
}
}
fun requestDailySteps(context: Context) {
Kotpref.init(context)
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(context)
if (account != null && GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
val cal: Calendar = Calendar.getInstance()
cal.set(Calendar.HOUR_OF_DAY, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
val startTime: Long = cal.timeInMillis
cal.add(Calendar.DAY_OF_YEAR, 1)
val endTime: Long = cal.timeInMillis
val readRequest = DataReadRequest.Builder()
.aggregate(DataType.TYPE_STEP_COUNT_DELTA)
.aggregate(DataType.AGGREGATE_STEP_COUNT_DELTA)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.bucketByTime(1, TimeUnit.DAYS)
.build()
Fitness.getHistoryClient(context, account)
.readData(readRequest)
.addOnSuccessListener { response ->
Preferences.googleFitSteps = response.buckets.sumBy {
try {
it.getDataSet(DataType.AGGREGATE_STEP_COUNT_DELTA)?.dataPoints?.get(
0
)?.getValue(FIELD_STEPS)?.asInt() ?: 0
} catch (ex: Exception) {
0
}
}.toLong()
MainWidget.updateWidget(context)
setTimeout(context)
}
}
}
private fun setTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 5, Intent(context, ActivityDetectionReceiver::class.java), 0))
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + 5 * 60 * 1000,
PendingIntent.getBroadcast(
context,
5,
Intent(context, ActivityDetectionReceiver::class.java),
0
)
)
}
}
}
}

View File

@ -0,0 +1,32 @@
package com.tommasoberlose.anotherwidget.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Actions
import java.lang.Exception
class CrashlyticsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_REPORT_CRASH) {
val exception: Exception = intent.getSerializableExtra(EXCEPTION) as Exception
FirebaseCrashlytics.getInstance().recordException(exception)
FirebaseCrashlytics.getInstance().sendUnsentReports()
}
}
companion object {
private const val EXCEPTION = "EXCEPTION"
fun sendCrash(context: Context, exception: Exception) {
context.sendBroadcast(Intent(context, CrashlyticsReceiver::class.java).apply {
action = Actions.ACTION_REPORT_CRASH
putExtra(EXCEPTION, exception)
})
}
}
}

View File

@ -13,7 +13,8 @@ class NewCalendarEventReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val eventRepository = EventRepository(context)
when (intent.action) {
Intent.ACTION_PROVIDER_CHANGED -> {
Intent.ACTION_PROVIDER_CHANGED,
Intent.ACTION_TIME_CHANGED -> {
CalendarHelper.updateEventList(context)
}
Actions.ACTION_GO_TO_NEXT_EVENT -> {

View File

@ -16,7 +16,6 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.lang.Exception
import java.util.*
@ -24,26 +23,19 @@ import java.util.*
class NotificationListener : NotificationListenerService() {
override fun onListenerConnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
MainWidget.updateWidget(this)
super.onListenerConnected()
}
override fun onListenerDisconnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.notification?.extras?.let { bundle ->
bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
} ?: run {
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
if (Preferences.showNotifications && bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
Preferences.lastNotificationId = sbn.id
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
try {
@ -67,13 +59,15 @@ class NotificationListener : NotificationListenerService() {
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
sbn?.let {
if (Preferences.showNotifications && sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
if (sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
ActiveNotificationsHelper.clearLastNotification(this)
}
}
MainWidget.updateWidget(this)
super.onNotificationRemoved(sbn)
}
@ -82,9 +76,10 @@ class NotificationListener : NotificationListenerService() {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 28943, intent, 0))
val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter)
if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) {
setExactIfCanSchedule(
setExact(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + when (timeoutPref) {
Constants.GlanceNotificationTimer.HALF_MINUTE -> 30 * 1000
@ -92,27 +87,16 @@ class NotificationListener : NotificationListenerService() {
Constants.GlanceNotificationTimer.FIVE_MINUTES -> 5 * 60 * 1000
Constants.GlanceNotificationTimer.TEN_MINUTES -> 10 * 60 * 1000
Constants.GlanceNotificationTimer.FIFTEEN_MINUTES -> 15 * 60 * 1000
else -> 60 * 1000
else -> 0
},
PendingIntent.getBroadcast(
context,
5,
intent,
PendingIntent.FLAG_IMMUTABLE
0
)
)
}
}
}
companion object {
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 5, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
}
}

View File

@ -12,9 +12,11 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.joda.time.Period
import java.util.*
class UpdatesReceiver : BroadcastReceiver() {
@ -25,18 +27,17 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED -> {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
ActiveNotificationsHelper.clearLastNotification(context)
GreetingsHelper.toggleGreetings(context)
}
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_DATE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> {
ActiveNotificationsHelper.clearLastNotification(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
CalendarHelper.updateEventList(context)
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_TIME_UPDATE -> {
MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) {
@ -44,60 +45,37 @@ class UpdatesReceiver : BroadcastReceiver() {
}
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
MainWidget.updateWidget(context)
}
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context)
}
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
}
Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context)
}
}
}
}
companion object {
const val EVENT_ID = "EVENT_ID"
fun setUpdates(context: Context, eventId: Long? = null) {
if (!Preferences.showEvents)
return
val eventRepository = EventRepository(context)
if (eventId == null) {
// schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactIfCanSchedule(
AlarmManager.RTC,
Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis,
PendingIntent.getBroadcast(
context,
0,
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
},
PendingIntent.FLAG_IMMUTABLE
)
)
}
removeUpdates(context)
eventRepository.getFutureEvents().forEach { event ->
setEventUpdate(context, event)
}
} else {
val event = eventRepository.getEventById(eventId)
val event = eventRepository.getEventByEventId(eventId)
if (event != null) {
setEventUpdate(context, event)
}
@ -106,11 +84,12 @@ class UpdatesReceiver : BroadcastReceiver() {
}
private fun setEventUpdate(context: Context, event: Event) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val diff = Period(now.timeInMillis, event.startDate, org.joda.time.PeriodType.time())
val diff = Period(now.timeInMillis, event.startDate)
val limit = when (Preferences.showUntil) {
0 -> 1000 * 60 * 60 * 3
1 -> 1000 * 60 * 60 * 6
@ -122,74 +101,92 @@ class UpdatesReceiver : BroadcastReceiver() {
7 -> 1000 * 60 * 60
else -> 1000 * 60 * 60 * 6
}
val fireTime = when {
event.startDate <= now.timeInMillis
-> event.endDate
event.startDate > now.timeInMillis + limit
-> event.startDate - limit
!Preferences.showDiffTime
-> return
event.allDay
-> event.startDate
diff.hours > 12
-> event.startDate - 12 * 1000 * 60 * 60 + 1000 * 60
diff.hours > 0
-> event.startDate - diff.hours * 1000 * 60 * 60 + 1000 * 60
else
-> event.startDate - 1000 * 60 * when (Preferences.widgetUpdateFrequency) {
if (event.startDate <= limit) {
if (event.startDate > now.timeInMillis) {
// Update the widget every hour till the event
if (diff.hours == 0) {
var minutes = 0
when (Preferences.widgetUpdateFrequency) {
Constants.WidgetUpdateFrequency.DEFAULT.rawValue -> {
when {
diff.minutes >= 45 -> 44
diff.minutes >= 30 -> 29
diff.minutes >= 15 -> 14
minutes = when {
diff.minutes > 50 -> 50
diff.minutes > 30 -> 30
diff.minutes > 15 -> 15
else -> 0
}
}
Constants.WidgetUpdateFrequency.HIGH.rawValue -> {
when {
diff.minutes >= 5 -> diff.minutes - diff.minutes % 5 - 1
else -> 0
minutes = diff.minutes - (diff.minutes % 5)
}
}
else -> 0
}
}
// no need to schedule updates after the next ACTION_CALENDAR_UPDATE
if (Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
}.timeInMillis <= fireTime) return
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactIfCanSchedule(
setExact(
AlarmManager.RTC,
fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60),
if (event.startDate - minutes * 1000 * 60 > (now.timeInMillis + 120 * 1000)) event.startDate - 60 * 1000 * minutes else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
event.id.toInt(),
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
if (event.startDate > now.timeInMillis)
putExtra(EVENT_ID, event.id)
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
)
} else {
setExact(
AlarmManager.RTC,
event.startDate - diff.hours * 1000 * 60 * 60 + if (diff.minutes > 30) (-30) else (+30),
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
} else {
// Update the widget one second after the event is finished
val fireTime =
if (event.endDate > now.timeInMillis + 120 * 1000) event.endDate else now.timeInMillis + 120000
setExact(
AlarmManager.RTC,
fireTime,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
},
0
)
)
}
} else {
setExact(
AlarmManager.RTC,
if (event.startDate - limit > now.timeInMillis + 120 * 1000) event.startDate - limit else now.timeInMillis + 120000,
PendingIntent.getBroadcast(
context,
event.eventID.toInt(),
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
putExtra(EVENT_ID, event.eventID)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
}
}
fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
}, PendingIntent.FLAG_IMMUTABLE))
val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.id.toInt(), Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
}, PendingIntent.FLAG_IMMUTABLE))
cancel(PendingIntent.getBroadcast(context, it.eventID.toInt(), Intent(context, UpdatesReceiver::class.java), 0))
}
eventRepository.close()
}

View File

@ -1,12 +1,18 @@
package com.tommasoberlose.anotherwidget.receivers
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
class WeatherReceiver : BroadcastReceiver() {
@ -16,22 +22,63 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIME_CHANGED -> setUpdates(context)
Actions.ACTION_WEATHER_UPDATE -> {
GlobalScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(context)
}
}
}
}
companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) {
removeUpdates(context)
if (Preferences.showWeather) {
WeatherWorker.enqueueTrigger(context)
val interval = MINUTE * when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setRepeating(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis,
interval,
PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
}
fun setOneTimeUpdate(context: Context) {
if (Preferences.showWeather) {
listOf(10, 20, 30).forEach {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactAndAllowWhileIdle(
AlarmManager.RTC,
it * MINUTE,
PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
}
}
fun removeUpdates(context: Context) {
WeatherWorker.cancelTrigger(context)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
listOf(10, 20, 30).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
}
}
}
}
}

View File

@ -16,19 +16,14 @@ class WidgetClickListenerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_OPEN_WEATHER_INTENT) {
try {
IntentHelper.getWeatherIntent(context).run {
if (flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(this)
else
context.sendBroadcast(this)
if (Preferences.weatherAppPackage == IntentHelper.REFRESH_WIDGET_OPTION) {
context.sendBroadcast(IntentHelper.getWeatherIntent(context))
} else {
context.startActivity(IntentHelper.getWeatherIntent(context))
}
} catch (e: Exception) {
e.printStackTrace()
val uri = Uri.parse("https://yandex.ru/pogoda")
.buildUpon()
.appendQueryParameter("lat", Preferences.customLocationLat)
.appendQueryParameter("lon", Preferences.customLocationLon)
.build()
val uri = Uri.parse("http://www.google.com/search?q=weather")
val i = Intent(Intent.ACTION_VIEW, uri)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
try {

View File

@ -0,0 +1,61 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
class BatteryListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
MainWidget.updateWidget(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val chargingJobId = 1006
private const val notChargingJobId = 1007
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
remove(context)
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(chargingJobId, componentName)
.setRequiresCharging(true)
.setPersisted(true)
.build()
)
schedule(
JobInfo.Builder(notChargingJobId, componentName)
.setRequiresCharging(false)
.setPersisted(true)
.build()
)
}
}
}
private fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(chargingJobId)
js?.cancel(notChargingJobId)
}
}
}

View File

@ -0,0 +1,55 @@
package com.tommasoberlose.anotherwidget.services
import android.app.job.JobInfo
import android.app.job.JobInfo.TriggerContentUri
import android.app.job.JobParameters
import android.app.job.JobScheduler
import android.app.job.JobService
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
class EventListenerJob : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
CalendarHelper.updateEventList(this)
schedule(
this
)
return false
}
@Synchronized
override fun onStopJob(params: JobParameters): Boolean {
return false
}
companion object {
private const val jobId = 1005
fun schedule(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val componentName = ComponentName(
context,
EventListenerJob::class.java
)
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
schedule(
JobInfo.Builder(jobId, componentName)
.addTriggerContentUri(TriggerContentUri(
CalendarContract.CONTENT_URI,
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
))
.build()
)
}
}
}
fun remove(context: Context) {
val js = context.getSystemService(JobScheduler::class.java)
js?.cancel(jobId)
}
}
}

View File

@ -0,0 +1,124 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.app.job.JobScheduler
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.IBinder
import android.util.Log
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
class LocationService : Service() {
private var job: Job? = null
override fun onCreate() {
super.onCreate()
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
if (ActivityCompat.checkSelfPermission(
this@LocationService,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
stopSelf()
}
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
companion object {
const val LOCATION_ACCESS_NOTIFICATION_ID = 28465
@JvmStatic
fun requestNewLocation(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, LocationService::class.java))
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun getLocationAccessNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.location_access_notification_channel_id),
getString(R.string.location_access_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.location_access_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@LocationService, getString(R.string.location_access_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.location_access_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@LocationService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@LocationService, 0, Intent(this@LocationService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -0,0 +1,214 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.collections.ArrayList
class UpdateCalendarService : Service() {
companion object {
const val CALENDAR_SYNC_NOTIFICATION_ID = 28468
fun enqueueWork(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, UpdateCalendarService::class.java))
}
}
override fun onCreate() {
super.onCreate()
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
}
private var job: Job? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
val eventRepository = EventRepository(this@UpdateCalendarService)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
val now = Calendar.getInstance()
val begin = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
}
val limit = Calendar.getInstance().apply {
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
if (!checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
} else {
try {
val provider = CalendarProvider(this@UpdateCalendarService)
val data = provider.getInstances(begin.timeInMillis, limit.timeInMillis)
if (data != null) {
val instances = data.list
for (instance in instances) {
try {
val e = provider.getEvent(instance.eventId)
if (e != null && !e.deleted && instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end && !CalendarHelper.getFilteredCalendarIdList()
.contains(e.calendarId)
) {
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
val endDate = Calendar.getInstance()
endDate.timeInMillis = instance.end
val isAllDay = e.allDay || (
startDate.get(Calendar.MILLISECOND) == 0
&& startDate.get(Calendar.SECOND) == 0
&& startDate.get(Calendar.MINUTE) == 0
&& startDate.get(Calendar.HOUR_OF_DAY) == 0
&& endDate.get(Calendar.MILLISECOND) == 0
&& endDate.get(Calendar.SECOND) == 0
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId.toInt(),
allDay = isAllDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventRepository.saveEvents(
sortedEvents
)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
}
UpdatesReceiver.setUpdates(this@UpdateCalendarService)
MainWidget.updateWidget(this@UpdateCalendarService)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
eventRepository.close()
stopSelf()
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
private fun getCalendarSyncNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.calendar_sync_notification_channel_id),
getString(R.string.calendar_sync_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.calendar_sync_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@UpdateCalendarService, getString(R.string.calendar_sync_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.calendar_sync_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@UpdateCalendarService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@UpdateCalendarService, 0, Intent(this@UpdateCalendarService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -1,199 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.os.Build
import android.provider.CalendarContract
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.*
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
class UpdateCalendarWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val context = applicationContext
UpdatesReceiver.removeUpdates(context)
val eventRepository = EventRepository(context)
if (Preferences.showEvents) {
if (!context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
val limit = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
try {
val eventList = ArrayList<Event>()
val provider = CalendarProvider(context)
// apply time zone offset to correctly fetch all-day events
val data = provider.getInstances(
now.timeInMillis + now.timeZone.getOffset(now.timeInMillis).coerceAtMost(0),
limit.timeInMillis + limit.timeZone.getOffset(limit.timeInMillis).coerceAtLeast(0)
)
if (data != null) {
val filteredCalendarIdList = CalendarHelper.getFilteredCalendarIdList()
for (instance in data.list) {
try {
val e = provider.getEvent(instance.eventId)
if (e == null || e.deleted || filteredCalendarIdList.contains(e.calendarId))
continue
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end) {
/* Following check may result in "fake" all-day events with
* non-UTC start/end time, and therefore cannot be found by
* Calendar when tapped to open details.
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
val endDate = Calendar.getInstance()
endDate.timeInMillis = instance.end
val isAllDay = e.allDay || (
startDate.get(Calendar.MILLISECOND) == 0
&& startDate.get(Calendar.SECOND) == 0
&& startDate.get(Calendar.MINUTE) == 0
&& startDate.get(Calendar.HOUR_OF_DAY) == 0
&& endDate.get(Calendar.MILLISECOND) == 0
&& endDate.get(Calendar.SECOND) == 0
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
*/
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId,
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
val first = filteredEventList.first()
if (Preferences.nextEventId != first.id && (
//Preferences.showWeatherAsGlanceProvider || !Preferences.showNextEvent ||
eventRepository.getEventById(first.id)?.startDate != first.startDate))
eventRepository.saveNextEventData(first)
eventRepository.saveEvents(filteredEventList)
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
}
eventRepository.close()
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
if (Preferences.showEvents)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventList",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateEventListTrigger",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<Trigger>().setConstraints(
Constraints.Builder().addContentUriTrigger(
CalendarContract.CONTENT_URI,
true
).build()
).build()
)
}
}
fun cancelTrigger(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateEventListTrigger"
)
}
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showEvents && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -1,93 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class WeatherWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val context = applicationContext
if (Preferences.customLocationAdd == "" &&
context.checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
) {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
var location: Location? = null
for (provider in lm.getProviders(true)) {
lm.getLastKnownLocation(provider)?.let {
if (location == null ||
it.time - location!!.time > 2 * 60 * 1000 ||
(it.time - location!!.time > -2 * 60 * 1000 && it.accuracy < location!!.accuracy))
location = it
}
}
location?.let { location ->
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
}
withContext(Dispatchers.IO) {
WeatherNetworkApi(context).updateWeather()
}
if (Preferences.showWeather)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context, replace: Boolean = false) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeather",
if (replace) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<WeatherWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
val interval = when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeatherTrigger",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<Trigger>().setInitialDelay(
interval, TimeUnit.MINUTES
).build()
)
}
fun cancelTrigger(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateWeatherTrigger"
)
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showWeather && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -46,7 +46,6 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
overridePendingTransition(R.anim.nav_default_enter_anim, R.anim.nav_default_exit_anim)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding = ActivityMainBinding.inflate(layoutInflater)

View File

@ -1,28 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityMainBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import kotlinx.coroutines.delay
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenResumed {
delay(1000)
if (!this@SplashActivity.isDestroyed) {
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}
}
}

View File

@ -0,0 +1,96 @@
package com.tommasoberlose.anotherwidget.ui.activities.settings
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.BillingResponseCode.OK
import com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivitySupportDevBinding
import com.tommasoberlose.anotherwidget.ui.viewmodels.settings.SupportDevViewModel
import com.tommasoberlose.anotherwidget.utils.toast
import net.idik.lib.slimadapter.SlimAdapter
class SupportDevActivity : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var viewModel: SupportDevViewModel
private lateinit var adapter: SlimAdapter
private lateinit var binding: ActivitySupportDevBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(SupportDevViewModel::class.java)
viewModel.billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build()
binding = ActivitySupportDevBinding.inflate(layoutInflater)
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
binding.listView.layoutManager = mLayoutManager
adapter = SlimAdapter.create()
adapter
.register<SkuDetails>(R.layout.inapp_product_layout) { item, injector ->
item.sku
injector
.with<TextView>(R.id.product_title) {
it.text = when (item.sku) {
"donation_coffee" -> getString(R.string.donation_coffee)
"donation_donuts" -> getString(R.string.donation_donuts)
"donation_breakfast" -> getString(R.string.donation_breakfast)
"donation_lunch" -> getString(R.string.donation_lunch)
else -> ""
}
}
.text(R.id.product_price, item.price)
.clicked(R.id.item) {
viewModel.purchase(this, item)
}
}
.attachTo(binding.listView)
viewModel.openConnection()
subscribeUi(viewModel)
binding.actionBack.setOnClickListener {
onBackPressed()
}
setContentView(binding.root)
}
private fun subscribeUi(viewModel: SupportDevViewModel) {
viewModel.products.observe(this, Observer {
if (it.isNotEmpty()) {
binding.loader.isVisible = false
}
adapter.updateData(it.sortedWith(compareBy(SkuDetails::getPriceAmountMicros)))
})
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult.responseCode == OK && purchases != null) {
for (purchase in purchases) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
viewModel.handlePurchase(purchase)
toast(getString(R.string.thanks))
}
}
} else if (billingResult.responseCode == USER_CANCELED) {
// DO nothing
} else {
toast(getString(R.string.error))
}
}
public override fun onDestroy() {
viewModel.closeConnection()
super.onDestroy()
}
}

View File

@ -169,7 +169,6 @@ class CustomDateActivity : AppCompatActivity() {
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(this)
super.onBackPressed()
}

View File

@ -38,15 +38,12 @@ class CustomFontActivity : AppCompatActivity() {
private lateinit var adapter: SlimAdapter
private lateinit var viewModel: CustomFontViewModel
private lateinit var binding: ActivityCustomFontBinding
private lateinit var handlerThread: HandlerThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CustomFontViewModel::class.java)
binding = ActivityCustomFontBinding.inflate(layoutInflater)
handlerThread = HandlerThread("listCustomFonts")
handlerThread.start()
binding.listView.setHasFixedSize(true)
val mLayoutManager = LinearLayoutManager(this)
@ -67,17 +64,14 @@ class CustomFontActivity : AppCompatActivity() {
injector
.text(R.id.text, item)
.with<TextView>(R.id.text) {
val googleSans: Typeface? = androidx.core.content.res.ResourcesCompat.getFont(
this,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
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
}
@ -103,49 +97,32 @@ class CustomFontActivity : AppCompatActivity() {
)
class Callback : FontsContractCompat.FontRequestCallback() {
var handler: Handler? = Handler(handlerThread.looper)
fun cancel() {
if (handler != null) {
handler!!.removeCallbacksAndMessages(null)
handler = null
}
}
protected fun finalize() {
cancel()
}
val callback = object : FontsContractCompat.FontRequestCallback() {
override fun onTypefaceRetrieved(typeface: Typeface) {
if (it.tag == this) {
it.tag = null
it.typeface = typeface
it.setTextColor(getColor(R.color.colorPrimaryText))
}
it.isVisible = true
it.measure(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
override fun onTypefaceRequestFailed(reason: Int) {
if (it.tag == this) {
it.tag = null
//it.text = item.fontFamily + " ($reason)"
it.setTextColor(getColor(R.color.errorColorText))
it.isVisible = false
it.layoutParams = it.layoutParams.apply {
height = 0
}
}
}
(it.tag as Callback?)?.cancel()
val callback = Callback()
it.tag = callback
it.typeface = null
it.setTextColor(getColor(R.color.colorSecondaryText))
val mHandler = callback.handler!!
val handlerThread = HandlerThread(item.fontFamily)
handlerThread.start()
val mHandler = Handler(handlerThread.looper)
FontsContractCompat.requestFont(this, request, callback, mHandler)
}
injector.clicked(R.id.text) {
if ((it as TextView).typeface == null) return@clicked
val dialog = BottomSheetMenu<Int>(this, header = item.fontFamily)
if (item.fontVariants.isEmpty()) {
dialog.addItem(SettingsStringHelper.getVariantLabel(this, "regular"), -1)
@ -170,12 +147,6 @@ class CustomFontActivity : AppCompatActivity() {
setContentView(binding.root)
}
override fun onDestroy() {
handlerThread.quit()
filterJob?.cancel()
super.onDestroy()
}
private var filterJob: Job? = null
private fun subscribeUi(binding: ActivityCustomFontBinding, viewModel: CustomFontViewModel) {
@ -233,13 +204,6 @@ class CustomFontActivity : AppCompatActivity() {
adapter.updateData(filteredList)
binding.loader.visibility = View.INVISIBLE
}
} else {
delay(200)
withContext(Dispatchers.Main) {
adapter.updateData(listOf(getString(R.string.custom_font_subtitle_1)).filter {
it.contains(search ?: "", ignoreCase = true)
})
}
}
}
}

View File

@ -46,29 +46,24 @@ class CustomLocationActivity : AppCompatActivity() {
adapter = SlimAdapter.create()
adapter
.register<String>(R.layout.custom_location_item) { _, injector ->
injector.text(R.id.text, getString(R.string.custom_location_gps))
injector.clicked(R.id.text) {
Preferences.bulk {
remove(Preferences::customLocationLat)
remove(Preferences::customLocationLon)
remove(Preferences::customLocationAdd)
}
setResult(Activity.RESULT_OK)
finish()
injector
.text(R.id.text, getString(R.string.custom_location_gps))
.clicked(R.id.text) {
requirePermission()
}
}
.register<Address>(R.layout.custom_location_item) { item, injector ->
injector.text(R.id.text, item.getAddressLine(0) ?: "")
injector.text(R.id.text, item.getAddressLine(0))
injector.clicked(R.id.item) {
Preferences.bulk {
customLocationLat = item.latitude.toString()
customLocationLon = item.longitude.toString()
customLocationAdd = item.getAddressLine(0) ?: ""
}
customLocationAdd = item.getAddressLine(0)
setResult(Activity.RESULT_OK)
finish()
}
}
}
.attachTo(binding.listView)
@ -120,6 +115,36 @@ class CustomLocationActivity : AppCompatActivity() {
})
}
private fun requirePermission() {
Dexter.withContext(this)
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.areAllPermissionsGranted()){
Preferences.bulk {
remove(Preferences::customLocationLat)
remove(Preferences::customLocationLon)
remove(Preferences::customLocationAdd)
}
setResult(Activity.RESULT_OK)
finish()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
}
})
.check()
}
private fun setupListener() {
binding.actionBack.setOnClickListener {
onBackPressed()

View File

@ -74,11 +74,7 @@ class TimeZoneSelectorActivity : AppCompatActivity() {
if (id != null) {
Preferences.bulk {
altTimezoneId = id
altTimezoneLabel = try {
item.locality
} catch (ex: Exception) {
item.getAddressLine(0)
}
altTimezoneLabel = item.locality
}
MainWidget.updateWidget(this@TimeZoneSelectorActivity)
setResult(Activity.RESULT_OK)

View File

@ -57,7 +57,9 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem()
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.clicked(R.id.radioButton) {
if (Preferences.weatherProvider != provider.rawValue) {
@ -70,7 +72,9 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem()
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}
.checked(R.id.radioButton, provider.rawValue == Preferences.weatherProvider)
.with<TextView>(R.id.text2) {
@ -88,8 +92,10 @@ class WeatherProviderActivity : AppCompatActivity() {
}
.clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
}.show()
}
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.rawValue == Preferences.weatherProvider) View.VISIBLE else View.GONE)
@ -109,6 +115,8 @@ class WeatherProviderActivity : AppCompatActivity() {
adapter.updateData(
Constants.WeatherProvider.values().asList()
.filter { it != Constants.WeatherProvider.HERE }
.filter { it != Constants.WeatherProvider.ACCUWEATHER }
)
setupListener()
@ -119,11 +127,11 @@ class WeatherProviderActivity : AppCompatActivity() {
private fun subscribeUi(viewModel: WeatherProviderViewModel) {
viewModel.weatherProviderError.observe(this) {
binding.listView.postDelayed({ updateListItem() }, 300)
updateListItem()
}
viewModel.weatherProviderLocationError.observe(this) {
binding.listView.postDelayed({ updateListItem() }, 300)
updateListItem()
}
}
@ -157,7 +165,7 @@ class WeatherProviderActivity : AppCompatActivity() {
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(@Suppress("UNUSED_PARAMETER") ignore: MainFragment.UpdateUiMessageEvent?) {
fun onMessageEvent(ignore: MainFragment.UpdateUiMessageEvent?) {
binding.loader.isVisible = Preferences.weatherProviderError == "-"
if (Preferences.weatherProviderError == "" && Preferences.weatherProviderLocationError == "") {
Snackbar.make(binding.listView, getString(R.string.settings_weather_provider_api_key_subtitle_all_set), Snackbar.LENGTH_LONG).show()

View File

@ -8,10 +8,14 @@ import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RemoteViews
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -25,20 +29,23 @@ import com.tommasoberlose.anotherwidget.databinding.FragmentAppMainBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.isColorDark
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.ui.widgets.StandardWidget
import com.tommasoberlose.anotherwidget.utils.*
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
private val PREVIEW_BASE_HEIGHT: Int
get() = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) 120 else 180
}
private lateinit var viewModel: MainViewModel
@ -87,11 +94,16 @@ class MainFragment : Fragment() {
binding.actionSettings.isClickable = !show
binding.actionSettings.isFocusable = !show
binding.fragmentTitle.text = if (show) destination.label.toString() else getString(R.string.app_name)
binding.toolbar.cardElevation = 0f
}
binding.actionSettings.setOnSingleClickListener {
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment)
Navigation.findNavController(it).navigate(R.id.action_appMainFragment_to_appSettingsFragment,)
}
binding.preview.layoutParams = binding.preview.layoutParams.apply {
height = PREVIEW_BASE_HEIGHT.toPixel(requireContext()) + if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0
}
subscribeUi(viewModel)
@ -140,59 +152,187 @@ class MainFragment : Fragment() {
}
viewModel.fragmentScrollY.observe(viewLifecycleOwner) {
binding.toolbar.cardElevation = if (it > 0) 32f else 0f
binding.toolbar.cardElevation = if (it > 0) 24f else 0f
}
viewModel.widgetAlign.observe(viewLifecycleOwner) {
updatePreviewVisibility()
lifecycleScope.launch {
delay(350)
updateClock()
}
}
viewModel.showPreview.observe(viewLifecycleOwner) {
binding.preview.isVisible = it
updatePreviewVisibility()
}
viewModel.clockPreferencesUpdate.observe(viewLifecycleOwner) {
updateClock()
}
viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) {
onUpdateUiEvent(null)
}
viewModel.showClock.observe(viewLifecycleOwner) {
updateClockVisibility(it)
}
}
private var uiJob: Job? = null
private fun updateUI() {
if (Preferences.showPreview) {
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob?.cancel()
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.getWidgetView(
if (Preferences.showPreview) {
lifecycleScope.launch(Dispatchers.IO) {
val bgColor: Int = ContextCompat.getColor(
requireContext(),
binding.widget.width - binding.widget.paddingStart - binding.widget.paddingEnd,
typeface
if (ColorHelper.getFontColor(requireActivity().isDarkTheme())
.isColorDark()
) android.R.color.white else R.color.colorAccent
)
val wallpaperDrawable = BitmapHelper.getTintedDrawable(
requireContext(),
R.drawable.card_background,
ColorHelper.getBackgroundColor(requireActivity().isDarkTheme())
)
if (generatedView != null) {
withContext(Dispatchers.Main) {
val view: View = generatedView.apply(requireActivity().applicationContext, binding.widget)
view.measure(0, 0)
binding.preview.setCardBackgroundColor(bgColor)
binding.widgetDetail.widgetShapeBackground.setImageDrawable(wallpaperDrawable)
}
}
binding.widgetLoader.animate().alpha(0f).setDuration(200L).start()
binding.widget.animate().alpha(0f).setDuration(200L).withEndAction {
updatePreviewVisibility(view.measuredHeight)
binding.widget.removeAllViews()
binding.widget.addView(view)
WidgetHelper.runWithCustomTypeface(requireContext()) { typeface ->
uiJob = lifecycleScope.launch(Dispatchers.IO) {
val generatedView = MainWidget.getWidgetView(requireContext(), typeface).root
binding.widget.animate().setStartDelay(300L).alpha(1f).start()
withContext(Dispatchers.Main) {
generatedView.measure(0, 0)
binding.preview.measure(0, 0)
}
val bitmap = BitmapHelper.getBitmapFromView(
generatedView,
if (binding.preview.width > 0) binding.preview.width else generatedView.measuredWidth,
generatedView.measuredHeight
)
withContext(Dispatchers.Main) {
binding.widgetDetail.bitmapContainer.apply {
setImageBitmap(bitmap)
}
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.widget.animate().alpha(1f).start()
}
}
}
}
}
private fun updateClock() {
// Clock
binding.widgetDetail.time.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.timeAmPm.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.time.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext())
)
binding.widgetDetail.timeAmPm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 5 * 2
)
binding.widgetDetail.timeAmPm.isVisible = Preferences.showAMPMIndicator
// Timezones
if (Preferences.altTimezoneId != "" && Preferences.altTimezoneLabel != "") {
// Clock
binding.widgetDetail.altTimezoneTime.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneTimeAmPm.timeZone = Preferences.altTimezoneId
binding.widgetDetail.altTimezoneLabel.text = Preferences.altTimezoneLabel
binding.widgetDetail.altTimezoneTime.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTimeAmPm.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneLabel.setTextColor(ColorHelper.getClockFontColor(requireActivity().isDarkTheme()))
binding.widgetDetail.altTimezoneTime.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize.toPixel(requireContext()) / 3
)
binding.widgetDetail.altTimezoneTimeAmPm.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.altTimezoneLabel.setTextSize(
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize.toPixel(requireContext()) / 3) / 5 * 2
)
binding.widgetDetail.timezonesContainer.isVisible = true
} else {
binding.widgetDetail.timezonesContainer.isVisible = false
}
// Clock bottom margin
binding.widgetDetail.clockBottomMarginNone.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.NONE.rawValue
binding.widgetDetail.clockBottomMarginSmall.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.SMALL.rawValue
binding.widgetDetail.clockBottomMarginMedium.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.MEDIUM.rawValue
binding.widgetDetail.clockBottomMarginLarge.isVisible =
Preferences.showClock && Preferences.clockBottomMargin == Constants.ClockBottomMargin.LARGE.rawValue
// Align
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
gravity = if (Preferences.widgetAlign == Constants.WidgetAlign.CENTER.rawValue) Gravity.CENTER_HORIZONTAL else Gravity.NO_GRAVITY
}
}
private fun updateClockVisibility(showClock: Boolean) {
binding.widgetDetail.timeContainer.clearAnimation()
binding.widgetDetail.time.clearAnimation()
updatePreviewVisibility()
if (showClock) {
binding.widgetDetail.timeContainer.layoutParams = (binding.widgetDetail.timeContainer.layoutParams as LinearLayout.LayoutParams).apply {
height = RelativeLayout.LayoutParams.WRAP_CONTENT
}
binding.widgetDetail.timeContainer.measure(0, 0)
}
if ((Preferences.showClock && binding.widgetDetail.time.alpha != 1f) || (!Preferences.showClock && binding.widgetDetail.time.alpha != 0f)) {
val initialHeight = binding.widgetDetail.timeContainer.measuredHeight
ValueAnimator.ofFloat(
if (showClock) 0f else 1f,
if (showClock) 1f else 0f
).apply {
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Float
binding.widgetDetail.timeContainer.layoutParams =
binding.widgetDetail.timeContainer.layoutParams.apply {
height = (initialHeight * animatedValue).toInt()
}
binding.widgetDetail.time.alpha = animatedValue
}
}.start()
}
}
}
}
}
}
private fun updatePreviewVisibility(widgetHeight: Int) {
if (isAdded) {
val newHeight = widgetHeight + 32f.convertDpToPixel(requireContext()).toInt()
if (binding.preview.layoutParams.height != newHeight) {
private fun updatePreviewVisibility() {
binding.preview.clearAnimation()
if (binding.preview.layoutParams.height != (if (Preferences.showPreview) PREVIEW_BASE_HEIGHT.toPixel(requireContext()) else 0) + (if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0)) {
ValueAnimator.ofInt(
binding.preview.height,
newHeight
(if (Preferences.showPreview) PREVIEW_BASE_HEIGHT.toPixel(requireContext()) else 0) + (if (Preferences.showClock) 100.toPixel(
requireContext()
) else 0)
).apply {
duration = 300L
addUpdateListener {
@ -204,12 +344,11 @@ class MainFragment : Fragment() {
}.start()
}
}
}
override fun onResume() {
super.onResume()
EventBus.getDefault().register(this)
// updateUI()
updateUI()
}
override fun onPause() {
@ -223,7 +362,7 @@ class MainFragment : Fragment() {
class ChangeTabEvent(val page: Int)
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUpdateUiEvent(@Suppress("UNUSED_PARAMETER") ignore: UpdateUiMessageEvent?) {
fun onUpdateUiEvent(ignore: UpdateUiMessageEvent?) {
delayJob?.cancel()
delayJob = lifecycleScope.launch(Dispatchers.IO) {
delay(300)
@ -234,7 +373,7 @@ class MainFragment : Fragment() {
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onChangeTabEvent(@Suppress("UNUSED_PARAMETER") ignore: ChangeTabEvent) {
fun onChangeTabEvent(ignore: ChangeTabEvent) {
val navHost = childFragmentManager.findFragmentById(R.id.settings_fragment) as? NavHostFragment?
navHost?.navController?.navigateUp()
}

View File

@ -8,10 +8,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDelegate
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import androidx.transition.TransitionInflater
import com.google.android.material.transition.MaterialSharedAxis
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
@ -29,10 +32,12 @@ import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.ui.activities.settings.IntegrationsActivity
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.activities.settings.SupportDevActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.openURI
import com.tommasoberlose.anotherwidget.utils.setOnSingleClickListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -191,18 +196,20 @@ class SettingsFragment : Fragment() {
requireActivity().openURI("https://github.com/tommasoberlose/another-widget/blob/master/privacy-policy.md")
}
binding.actionHelpDev.setOnClickListener {
startActivity(Intent(requireContext(), SupportDevActivity::class.java))
}
binding.actionRefreshWidget.setOnClickListener {
binding.actionRefreshIcon
.animate()
.rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
.withEndAction {
try {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
MediaPlayerHelper.updatePlayingMediaInfo(requireContext())
ActiveNotificationsHelper.clearLastNotification(requireContext())
} catch (ex: Exception) {
ex.printStackTrace()
}
}
.start()

View File

@ -63,7 +63,6 @@ class CalendarFragment : Fragment() {
binding.showAllDayToggle.setCheckedImmediatelyNoEvent(Preferences.calendarAllDay)
binding.showOnlyBusyEventsToggle.setCheckedImmediatelyNoEvent(Preferences.showOnlyBusyEvents)
binding.showDiffTimeToggle.setCheckedImmediatelyNoEvent(Preferences.showDiffTime)
binding.showNextEventOnMultipleLinesToggle.setCheckedImmediatelyNoEvent(Preferences.showNextEventOnMultipleLines)
setupListener()
@ -91,12 +90,6 @@ class CalendarFragment : Fragment() {
}
}
viewModel.showNextEventOnMultipleLines.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showNextEventOnMultipleLinesLabel.text = if (it) getString(R.string.settings_enabled) else getString(R.string.settings_disabled)
}
}
viewModel.showDiffTime.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.showDiffTimeLabel.text = if (it) getString(R.string.settings_visible) else getString(R.string.settings_not_visible)
@ -182,7 +175,7 @@ class CalendarFragment : Fragment() {
binding.showAllDayToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.calendarAllDay = isChecked
updateCalendar()
MainWidget.updateWidget(requireContext())
}
binding.actionChangeAttendeeFilter.setOnClickListener {
@ -227,7 +220,7 @@ class CalendarFragment : Fragment() {
binding.showOnlyBusyEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showOnlyBusyEvents = isChecked
updateCalendar()
MainWidget.updateWidget(requireContext())
}
binding.actionShowDiffTime.setOnClickListener {
@ -236,15 +229,6 @@ class CalendarFragment : Fragment() {
binding.showDiffTimeToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showDiffTime = isChecked
updateCalendar()
}
binding.actionShowNextEventOnMultipleLines.setOnClickListener {
binding.showNextEventOnMultipleLinesToggle.isChecked = !binding.showNextEventOnMultipleLinesToggle.isChecked
}
binding.showNextEventOnMultipleLinesToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showNextEventOnMultipleLines = isChecked
}
binding.actionWidgetUpdateFrequency.setOnClickListener {
@ -255,7 +239,6 @@ class CalendarFragment : Fragment() {
.addItem(getString(R.string.settings_widget_update_frequency_low), Constants.WidgetUpdateFrequency.LOW.rawValue)
.addOnSelectItemListener { value ->
Preferences.widgetUpdateFrequency = value
updateCalendar()
}.show()
}
}

View File

@ -17,7 +17,7 @@ import com.chibatching.kotpref.bulk
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.databinding.FragmentTabClockBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
@ -144,15 +144,16 @@ class ClockFragment : Fragment() {
private fun setupListener() {
binding.actionClockTextSize.setOnClickListener {
BottomSheetPicker(
val dialog = BottomSheetMenu<Float>(
requireContext(),
items = (120 downTo 30).filter { it % 2 == 0 }.map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.clockTextSize },
header = getString(R.string.settings_clock_text_size_title),
onItemSelected = {value ->
if (value != null) Preferences.clockTextSize = value
header = getString(R.string.settings_clock_text_size_title)
).setSelectedValue(Preferences.clockTextSize)
(46 downTo 12).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
).show()
dialog.addOnSelectItemListener { value ->
Preferences.clockTextSize = value
}.show()
}
binding.actionAltTimezoneClock.setOnClickListener {

View File

@ -102,7 +102,15 @@ class GesturesFragment : Fragment() {
it == IntentHelper.DO_NOTHING_OPTION -> getString(R.string.gestures_do_nothing)
it == IntentHelper.REFRESH_WIDGET_OPTION -> getString(R.string.gestures_refresh_widget)
it != IntentHelper.DEFAULT_OPTION -> it
else -> getString(R.string.default_calendar_app)
else -> {
if (IntentHelper.getCalendarIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_calendar_app
)
} else {
getString(R.string.gestures_do_nothing)
}
}
}
}
}
@ -119,7 +127,15 @@ class GesturesFragment : Fragment() {
it == IntentHelper.DO_NOTHING_OPTION -> getString(R.string.gestures_do_nothing)
it == IntentHelper.REFRESH_WIDGET_OPTION -> getString(R.string.gestures_refresh_widget)
it != IntentHelper.DEFAULT_OPTION -> it
else -> getString(R.string.default_clock_app)
else -> {
if (IntentHelper.getClockIntent(requireContext()).isDefaultSet(requireContext())) {
getString(
R.string.default_clock_app
)
} else {
getString(R.string.gestures_do_nothing)
}
}
}
}
}
@ -144,12 +160,6 @@ class GesturesFragment : Fragment() {
binding.showMultipleEventsToggle.setOnCheckedChangeListener { _, isChecked ->
Preferences.showNextEvent = isChecked
if (!isChecked) {
com.tommasoberlose.anotherwidget.db.EventRepository(requireContext()).run {
resetNextEventData()
close()
}
}
}
binding.actionOpenEventDetails.setOnClickListener {
@ -163,12 +173,9 @@ class GesturesFragment : Fragment() {
}
binding.actionCalendarApp.setOnClickListener {
startActivityForResult(
Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
startActivityForResult(Intent(requireContext(), ChooseApplicationActivity::class.java).apply {
putExtra(Constants.RESULT_APP_PACKAGE, Preferences.calendarAppPackage)
},
RequestCode.CALENDAR_APP_REQUEST_CODE.code
)
}, RequestCode.CALENDAR_APP_REQUEST_CODE.code)
}
binding.actionClockApp.setOnClickListener {

View File

@ -8,20 +8,26 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.animation.LayoutAnimationController
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
import com.google.android.material.card.MaterialCardView
import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
@ -35,6 +41,8 @@ import com.tommasoberlose.anotherwidget.helpers.AlarmHelper
import com.tommasoberlose.anotherwidget.helpers.GlanceProviderHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.models.GlanceProvider
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.viewmodels.MainViewModel
import com.tommasoberlose.anotherwidget.utils.*
@ -230,6 +238,40 @@ class GlanceTabFragment : Fragment() {
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.customNotes != ""
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
val account: GoogleSignInAccount? = GoogleSignIn.getLastSignedInAccount(
context
)
if (GoogleSignIn.hasPermissions(
account,
FITNESS_OPTIONS
) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || requireActivity().checkGrantedPermission(
Manifest.permission.ACTIVITY_RECOGNITION
))
) {
injector.text(
R.id.label,
if (Preferences.showDailySteps) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = Preferences.showDailySteps
} else if (Preferences.showDailySteps) {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.visibility(R.id.error_icon, View.VISIBLE)
injector.visibility(R.id.info_icon, View.GONE)
injector.text(R.id.label, getString(R.string.settings_not_visible))
isVisible = false
} else {
ActivityDetectionReceiver.unregisterFence(requireContext())
injector.text(R.id.label, getString(R.string.settings_not_visible))
injector.visibility(R.id.error_icon, View.GONE)
injector.visibility(R.id.info_icon, View.VISIBLE)
isVisible = false
}
}
Constants.GlanceProviderId.EVENTS -> {
isVisible =
Preferences.showEventsAsGlanceProvider
@ -251,25 +293,6 @@ class GlanceTabFragment : Fragment() {
if (!(isVisible && hasError)) View.VISIBLE else View.GONE
)
}
Constants.GlanceProviderId.WEATHER -> {
isVisible =
Preferences.showWeatherAsGlanceProvider
val hasError = !Preferences.showWeather || (Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-") || Preferences.weatherProviderLocationError != ""
injector.text(
R.id.label,
if (isVisible && !hasError) getString(R.string.settings_visible) else getString(
R.string.settings_not_visible
)
)
injector.visibility(
R.id.error_icon,
if (isVisible && hasError) View.VISIBLE else View.GONE
)
injector.visibility(
R.id.info_icon,
if (!(isVisible && hasError)) View.VISIBLE else View.GONE
)
}
}
injector.alpha(R.id.title, if (isVisible) 1f else .25f)
@ -449,6 +472,30 @@ class GlanceTabFragment : Fragment() {
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}
}
2 -> {
try {
val account: GoogleSignInAccount? = GoogleSignIn.getSignedInAccountFromIntent(
data
).getResult(ApiException::class.java)
if (!GoogleSignIn.hasPermissions(account, FITNESS_OPTIONS)) {
GoogleSignIn.requestPermissions(
requireActivity(),
1,
account,
FITNESS_OPTIONS
)
} else {
adapter.notifyItemRangeChanged(0, adapter.data.size)
}
} catch (e: ApiException) {
e.printStackTrace()
Preferences.showDailySteps = false
}
if (dialog != null) {
dialog?.show()
}

View File

@ -86,28 +86,6 @@ class LayoutFragment : Fragment() {
viewModel: MainViewModel
) {
viewModel.widgetMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.widgetMarginLabel.text = when (it) {
Constants.Dimension.NONE.rawValue -> getString(R.string.settings_widget_dim_none)
Constants.Dimension.SMALL.rawValue -> getString(R.string.settings_widget_dim_small)
Constants.Dimension.LARGE.rawValue -> getString(R.string.settings_widget_dim_large)
else -> getString(R.string.settings_widget_dim_medium)
}
}
}
viewModel.widgetPadding.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.widgetPaddingLabel.text = when (it) {
Constants.Dimension.NONE.rawValue -> getString(R.string.settings_widget_dim_none)
Constants.Dimension.SMALL.rawValue -> getString(R.string.settings_widget_dim_small)
Constants.Dimension.LARGE.rawValue -> getString(R.string.settings_widget_dim_large)
else -> getString(R.string.settings_widget_dim_medium)
}
}
}
viewModel.secondRowTopMargin.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.secondRowTopMarginLabel.text = when (it) {
@ -123,14 +101,12 @@ class LayoutFragment : Fragment() {
maintainScrollPosition {
binding.widgetAlignIcon.setImageDrawable(when (it) {
Constants.WidgetAlign.LEFT.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_left_24)
Constants.WidgetAlign.RIGHT.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_right_24)
Constants.WidgetAlign.CENTER.rawValue -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_center_24)
else -> ContextCompat.getDrawable(requireContext(), R.drawable.round_align_horizontal_center_24)
})
binding.widgetAlignLabel.text = when (it) {
Constants.WidgetAlign.LEFT.rawValue -> getString(R.string.settings_widget_align_left_subtitle)
Constants.WidgetAlign.RIGHT.rawValue -> getString(R.string.settings_widget_align_right_subtitle)
Constants.WidgetAlign.CENTER.rawValue -> getString(R.string.settings_widget_align_center_subtitle)
else -> getString(R.string.settings_widget_align_center_subtitle)
}
@ -169,58 +145,6 @@ class LayoutFragment : Fragment() {
private fun setupListener() {
binding.actionWidgetMargin.setOnClickListener {
BottomSheetMenu<Float>(
requireContext(),
header = getString(R.string.settings_widget_margin_title)
).setSelectedValue(Preferences.widgetMargin)
.addItem(
getString(R.string.settings_widget_dim_none),
Constants.Dimension.NONE.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_small),
Constants.Dimension.SMALL.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_medium),
Constants.Dimension.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_large),
Constants.Dimension.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetMargin = value
}.show()
}
binding.actionWidgetPadding.setOnClickListener {
BottomSheetMenu<Float>(
requireContext(),
header = getString(R.string.settings_widget_padding_title)
).setSelectedValue(Preferences.widgetPadding)
.addItem(
getString(R.string.settings_widget_dim_none),
Constants.Dimension.NONE.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_small),
Constants.Dimension.SMALL.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_medium),
Constants.Dimension.MEDIUM.rawValue
)
.addItem(
getString(R.string.settings_widget_dim_large),
Constants.Dimension.LARGE.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetPadding = value
}.show()
}
binding.actionSecondRowTopMarginSize.setOnClickListener {
BottomSheetMenu<Int>(
requireContext(),
@ -286,10 +210,6 @@ class LayoutFragment : Fragment() {
getString(R.string.settings_widget_align_left_subtitle),
Constants.WidgetAlign.LEFT.rawValue
)
.addItem(
getString(R.string.settings_widget_align_right_subtitle),
Constants.WidgetAlign.RIGHT.rawValue
)
.addOnSelectItemListener { value ->
Preferences.widgetAlign = value
}.show()

View File

@ -23,8 +23,6 @@ import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentPreferencesBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
@ -89,7 +87,6 @@ class PreferencesFragment : Fragment() {
CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
UpdatesReceiver.removeUpdates(requireContext())
}
}
}
@ -136,9 +133,7 @@ class PreferencesFragment : Fragment() {
binding.showWeatherSwitch.setOnCheckedChangeListener { _, enabled: Boolean ->
Preferences.showWeather = enabled
if (enabled) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
WeatherHelper.updateWeather(requireContext())
WeatherReceiver.setUpdates(requireContext())
} else {
WeatherReceiver.removeUpdates(requireContext())
}
@ -166,7 +161,6 @@ class PreferencesFragment : Fragment() {
.withPermissions(
Manifest.permission.READ_CALENDAR
).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
val granted = report.areAllPermissionsGranted()
@ -174,33 +168,12 @@ class PreferencesFragment : Fragment() {
if (granted) {
CalendarHelper.updateEventList(requireContext())
}
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_calendar),
getString(R.string.description_permission_calendar)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
android.content.Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
}
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()

View File

@ -15,7 +15,6 @@ import com.google.android.material.transition.MaterialSharedAxis
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.BottomSheetColorPicker
import com.tommasoberlose.anotherwidget.components.BottomSheetMenu
import com.tommasoberlose.anotherwidget.components.BottomSheetPicker
import com.tommasoberlose.anotherwidget.databinding.FragmentTabTypographyBinding
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
@ -168,27 +167,25 @@ class TypographyFragment : Fragment() {
private fun setupListener() {
binding.actionMainTextSize.setOnClickListener {
BottomSheetPicker(
requireContext(),
items = (40 downTo 10).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.textMainSize },
header = getString(R.string.title_main_text_size),
onItemSelected = {value ->
if (value != null) Preferences.textMainSize = value
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_main_text_size)).setSelectedValue(
Preferences.textMainSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
).show()
dialog.addOnSelectItemListener { value ->
Preferences.textMainSize = value
}.show()
}
binding.actionSecondTextSize.setOnClickListener {
BottomSheetPicker(
requireContext(),
items = (40 downTo 10).map { BottomSheetPicker.MenuItem("${it}sp", it.toFloat()) },
getSelected = { Preferences.textSecondSize },
header = getString(R.string.title_second_text_size),
onItemSelected = {value ->
if (value != null) Preferences.textSecondSize = value
val dialog = BottomSheetMenu<Float>(requireContext(), header = getString(R.string.title_second_text_size)).setSelectedValue(
Preferences.textSecondSize)
(40 downTo 10).filter { it % 2 == 0 }.forEach {
dialog.addItem("${it}sp", it.toFloat())
}
).show()
dialog.addOnSelectItemListener { value ->
Preferences.textSecondSize = value
}.show()
}
binding.actionFontColor.setOnClickListener {
@ -212,7 +209,7 @@ class TypographyFragment : Fragment() {
} else {
Preferences.textGlobalAlpha = alpha.toHexValue()
}
},
}
).show()
}
@ -239,7 +236,7 @@ class TypographyFragment : Fragment() {
} else {
Preferences.textSecondaryAlpha = alpha.toHexValue()
}
},
}
).show()
}
@ -276,7 +273,7 @@ class TypographyFragment : Fragment() {
Intent(requireContext(), CustomFontActivity::class.java),
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code
)
} else if (value != Preferences.customFont) {
} else if (value != Constants.CUSTOM_FONT_DOWNLOADED) {
Preferences.bulk {
customFont = value
customFontFile = ""
@ -317,17 +314,6 @@ class TypographyFragment : Fragment() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == android.app.Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CUSTOM_FONT_CHOOSER_REQUEST_CODE.code -> {
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(requireContext())
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()

View File

@ -91,17 +91,16 @@ class WeatherFragment : Fragment() {
viewModel.weatherProvider.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherProvider.text = WeatherHelper.getProviderName(requireContext(), Constants.WeatherProvider.fromInt(it)!!)
checkWeatherProviderConfig()
}
}
viewModel.weatherProviderError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
checkLocationPermission()
}
viewModel.weatherProviderLocationError.observe(viewLifecycleOwner) {
checkWeatherProviderConfig()
checkLocationPermission()
}
viewModel.customLocationAdd.observe(viewLifecycleOwner) {
@ -109,7 +108,6 @@ class WeatherFragment : Fragment() {
binding.labelCustomLocation.text =
if (it == "") getString(R.string.custom_location_gps) else it
}
checkWeatherProviderConfig()
checkLocationPermission()
}
@ -118,40 +116,43 @@ class WeatherFragment : Fragment() {
binding.tempUnit.text =
if (it == "F") getString(R.string.fahrenheit) else getString(R.string.celsius)
}
checkLocationPermission()
}
viewModel.weatherRefreshPeriod.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherRefreshPeriod.text = getString(SettingsStringHelper.getRefreshPeriodString(it))
}
checkLocationPermission()
}
viewModel.weatherIconPack.observe(viewLifecycleOwner) {
maintainScrollPosition {
binding.labelWeatherIconPack.text = getString(R.string.settings_weather_icon_pack_default).format((it + 1))
}
checkLocationPermission()
}
}
private fun checkLocationPermission() {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
binding.locationPermissionAlert.isVisible = false
} else if (Preferences.customLocationAdd == "") {
WeatherReceiver.setUpdates(requireContext())
} else if (Preferences.showWeather && Preferences.customLocationAdd == "") {
binding.locationPermissionAlert.isVisible = true
binding.locationPermissionAlert.setOnClickListener {
requirePermission()
}
binding.weatherProviderLocationError.isVisible = false
} else {
binding.locationPermissionAlert.isVisible = false
}
}
private fun checkWeatherProviderConfig() {
binding.weatherProviderError.isVisible = Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.isVisible = Preferences.showWeather && Preferences.weatherProviderError != "" && Preferences.weatherProviderError != "-"
binding.weatherProviderError.text = Preferences.weatherProviderError
binding.weatherProviderLocationError.isVisible = Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.isVisible = Preferences.showWeather && Preferences.weatherProviderLocationError != ""
binding.weatherProviderLocationError.text = Preferences.weatherProviderLocationError
}
@ -176,9 +177,11 @@ class WeatherFragment : Fragment() {
.addItem(getString(R.string.celsius), "C")
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
Preferences.weatherTempUnit = value
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
Preferences.weatherTempUnit = value
}.show()
}
@ -190,10 +193,7 @@ class WeatherFragment : Fragment() {
}
dialog
.addOnSelectItemListener { value ->
if (value != Preferences.weatherRefreshPeriod) {
Preferences.weatherRefreshPeriod = value
WeatherReceiver.setUpdates(requireContext())
}
}.show()
}
@ -206,10 +206,12 @@ class WeatherFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
WeatherHelper.updateWeather(requireContext())
WeatherReceiver.setUpdates(requireContext())
checkLocationPermission()
}
RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
checkLocationPermission()
}
//RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
//}
}
}
super.onActivityResult(requestCode, resultCode, data)
@ -218,35 +220,12 @@ class WeatherFragment : Fragment() {
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
Manifest.permission.ACCESS_FINE_LOCATION
).withListener(object: MultiplePermissionsListener {
private var shouldShowRationale = false
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let {
if (report.grantedPermissionResponses.isNotEmpty()) {
if (report.areAllPermissionsGranted()){
checkLocationPermission()
WeatherHelper.updateWeather(requireContext())
}
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
MaterialBottomSheetDialog(
requireContext(),
getString(R.string.title_permission_location),
getString(R.string.description_permission_location)
).setNegativeButton(getString(R.string.action_ignore))
.setPositiveButton(getString(R.string.action_grant_permission)) {
startActivity(
Intent(
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = android.net.Uri.fromParts(
"package",
requireContext().packageName,
null
)
}
)
}.show()
}
}
}
@ -254,7 +233,6 @@ class WeatherFragment : Fragment() {
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) {
shouldShowRationale = true
// Remember to invoke this method when the custom rationale is closed
// or just by default if you don't want to use any custom rationale.
token?.continuePermissionRequest()
@ -263,12 +241,6 @@ class WeatherFragment : Fragment() {
.check()
}
override fun onResume() {
super.onResume()
checkWeatherProviderConfig()
checkLocationPermission()
}
private fun maintainScrollPosition(callback: () -> Unit) {
binding.scrollView.isScrollable = false
callback.invoke()

View File

@ -50,8 +50,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
val showDividers = Preferences.asLiveData(Preferences::showDividers)
val secondRowTopMargin = Preferences.asLiveData(Preferences::secondRowTopMargin)
val widgetAlign = Preferences.asLiveData(Preferences::widgetAlign)
val widgetMargin = Preferences.asLiveData(Preferences::widgetMargin)
val widgetPadding = Preferences.asLiveData(Preferences::widgetPadding)
// Calendar Settings
val showEvents = Preferences.asLiveData(Preferences::showEvents)
@ -59,7 +57,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
val showUntil = Preferences.asLiveData(Preferences::showUntil)
val showDiffTime = Preferences.asLiveData(Preferences::showDiffTime)
val showNextEvent = Preferences.asLiveData(Preferences::showNextEvent)
val showNextEventOnMultipleLines = Preferences.asLiveData(Preferences::showNextEventOnMultipleLines)
val openEventDetails = Preferences.asLiveData(Preferences::openEventDetails)
val calendarAppName = Preferences.asLiveData(Preferences::calendarAppName)
val widgetUpdateFrequency = Preferences.asLiveData(Preferences::widgetUpdateFrequency)
@ -104,8 +101,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
// UI
val fragmentScrollY = MutableLiveData<Int>()
val widgetPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::showClock)) { value = true }
val clockPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::clockTextSize)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockTextAlpha)) { value = true }
@ -114,6 +110,8 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::showAMPMIndicator)) { value = true }
addSource(Preferences.asLiveData(Preferences::clockBottomMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::altTimezoneLabel)) { value = true }
}
val widgetPreferencesUpdate = MediatorLiveData<Boolean>().apply {
addSource(Preferences.asLiveData(Preferences::textGlobalColor)) { value = true }
addSource(Preferences.asLiveData(Preferences::textGlobalAlpha)) { value = true }
addSource(Preferences.asLiveData(Preferences::textSecondaryColor)) { value = true }
@ -135,9 +133,8 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetAlign)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetPadding)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDividers)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowTopMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::isDateCapitalize)) { value = true }
@ -147,7 +144,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::calendarAllDay)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDiffTime)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNextEvent)) { value = true }
addSource(Preferences.asLiveData(Preferences::showNextEventOnMultipleLines)) { value = true }
addSource(Preferences.asLiveData(Preferences::showDeclinedEvents)) { value = true }
addSource(Preferences.asLiveData(Preferences::showInvitedEvents)) { value = true }
addSource(Preferences.asLiveData(Preferences::showAcceptedEvents)) { value = true }
@ -155,7 +151,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherTempUnit)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLon)) { value = true }
@ -173,7 +169,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::musicPlayersFilter)) { value = true }
addSource(Preferences.asLiveData(Preferences::appNotificationsFilter)) { value = true }
addSource(Preferences.asLiveData(Preferences::showEventsAsGlanceProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::showWeatherAsGlanceProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::installedIntegrations)) { value = true }
}

View File

@ -0,0 +1,69 @@
package com.tommasoberlose.anotherwidget.ui.viewmodels.settings
import android.app.Activity
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.billingclient.api.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SupportDevViewModel : ViewModel() {
lateinit var billingClient: BillingClient
val products: MutableLiveData<List<SkuDetails>> = MutableLiveData(emptyList())
fun openConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val params = SkuDetailsParams.newBuilder()
params.setSkusList(listOf("donation_coffee", "donation_donuts", "donation_breakfast", "donation_lunch", "donation_dinner")).setType(BillingClient.SkuType.INAPP)
viewModelScope.launch(Dispatchers.IO) {
val skuDetailsList = billingClient.querySkuDetails(params.build()).skuDetailsList
withContext(Dispatchers.Main) {
products.value = skuDetailsList
}
}
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
fun purchase(activity: Activity, product: SkuDetails) {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(product)
.build()
billingClient.launchBillingFlow(activity, flowParams)
}
fun handlePurchase(purchase: Purchase) {
if (!purchase.isAcknowledged) {
viewModelScope.launch(Dispatchers.IO) {
val token = purchase.purchaseToken
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(token)
billingClient.acknowledgePurchase(acknowledgePurchaseParams.build())
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(token)
.build()
billingClient.consumePurchase(consumeParams)
}
}
}
fun closeConnection() {
billingClient.endConnection()
}
}

View File

@ -9,9 +9,10 @@ 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 com.tommasoberlose.anotherwidget.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CustomFontViewModel(application: Application) : AndroidViewModel(application) {
@ -33,7 +34,7 @@ class CustomFontViewModel(application: Application) : AndroidViewModel(applicati
}
}
// DownloadableFontList.requestDownloadableFontList(fontListCallback, BuildConfig.GOOGLE_API_KEY)
DownloadableFontList.requestDownloadableFontList(fontListCallback, BuildConfig.GOOGLE_API_KEY)
}
}
}

View File

@ -1,999 +0,0 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews? {
var views = RemoteViews(context.packageName, if (!rightAligned) R.layout.left_aligned_widget_sans else R.layout.right_aligned_widget_sans)
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val margin = Preferences.widgetMargin.convertDpToPixel(context).toInt()
views.setViewPadding(R.id.widget_shape_background, margin, margin, margin, margin)
val refreshIntent = IntentHelper.getPendingIntent(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
// Padding
val padding = (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt()
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) {
ex.printStackTrace()
}
// Clock
views = ClockWidget(context).updateClockView(views, appWidgetId)
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface) ?: return null
val width = w - (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt() * 2
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
}
return views
}
private fun updateGridView(bindingView: LeftAlignedWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false, width = bindingView.weatherDateLine.width, height = bindingView.weatherDateLine.height)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false, width = bindingView.weatherSubLine.width, height = bindingView.weatherSubLine.height)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false, width = bindingView.date.width, height = bindingView.date.height)
)
val calPIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventsCount > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false, width = bindingView.actionNext.width, height = bindingView.actionNext.height)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
}
// Event intent
val eventIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false, width = bindingView.nextEvent.width, height = bindingView.nextEvent.height)
)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
// Event time difference
if (Preferences.showDiffTime && Calendar.getInstance().timeInMillis < nextEvent.startDate) {
views.setImageViewBitmap(
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
bindingView.nextEventDifferenceTime,
draw = false,
width = bindingView.nextEventDifferenceTime.width,
height = bindingView.nextEventDifferenceTime.height
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
if (!Preferences.showNextEventOnMultipleLines) {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, if (Preferences.showWeather && Preferences.weatherIcon != "") View.VISIBLE else View.GONE)
views.setViewVisibility(R.id.first_line_rect, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false
var isWeatherShown = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
val alarmIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
ContextCompat.getDrawable(
remotePackageContext,
Preferences.lastNotificationIcon)
}
val notificationIntent = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
notificationIntent
)
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && GreetingsHelper.getRandomString(context).isNotBlank()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
val pIntentDetail = IntentHelper.getPendingIntent(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
pIntentDetail
)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
weatherPIntent
)
showSomething = true
isWeatherShown = true
break@loop
}
}
}
}
if (showSomething) {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, if (isWeatherShown) View.GONE else View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
} else {
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
} else {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false, width = bindingView.subLine.width, height = bindingView.subLine.height)
)
} catch (ex: Exception) {
ex.printStackTrace()
}
return views
}
// Generates the widget bitmap from the view
private fun generateWidgetView(typeface: Typeface? = null): LeftAlignedWidgetBinding? {
try {
var isWeatherShownAsGlanceProvider = false
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
val bindingView = LeftAlignedWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.weatherDateLine.isVisible = true
val currentTemp = String.format(
Locale.getDefault(),
"%d°%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.weatherSubLineWeatherIcon.isVisible = false
bindingView.weatherDateLineWeatherIcon.isVisible = false
} else {
bindingView.weatherSubLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherDateLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherSubLineWeatherIcon.isVisible = true
bindingView.weatherDateLineWeatherIcon.isVisible = true
}
bindingView.weatherDateLineTemperature.text = currentTemp
bindingView.weatherSubLineTemperature.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.weatherSubLine.isVisible = false
}
} else {
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
}
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.nextEventDifferenceTime.isVisible = false
bindingView.actionNext.isVisible = false
bindingView.date.text = DateHelper.getDateText(context, now)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventsCount > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showNextEventOnMultipleLines) {
bindingView.nextEvent.apply {
isSingleLine = false
maxLines = 3
gravity = if (rightAligned) Gravity.END else Gravity.START
}
}
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
val diffTime = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.text = diffTime
if (!Preferences.showNextEventOnMultipleLines) {
bindingView.nextEventDifferenceTime.isVisible = true
} else {
val text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime)
if (text.endsWith(diffTime)) {
bindingView.nextEvent.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
(v as TextView).layout?.run {
val diff = diffTime.trimStart();
val diffStart = text.length - diff.length
if (getLineStart(lineCount - 1) > diffStart)
v.text = (text.substring(0, diffStart).trimEnd() + '\n' + diff)
}
}
}
bindingView.nextEvent.text = text
bindingView.nextEventDifferenceTime.isVisible = false
}
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place_24
)
)
bindingView.subLineText.text = nextEvent.address
} else {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
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)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
val endCal = Calendar.getInstance()
endCal.timeInMillis = nextEvent.endDate
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)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
if (nextEvent.startDate != nextEvent.endDate) {
bindingView.subLineText.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
bindingView.subLineText.text =
String.format("%s", startHour)
}
} else {
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.after(start)) {
DateHelper.getDateText(context, now)
} else {
DateHelper.getDateText(context, start)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = Preferences.showWeather && Preferences.weatherIcon != ""
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.subLineIcon.isVisible = true
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note_24
)
)
bindingView.subLineText.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = nextAlarm
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
bindingView.subLineIcon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel != 100) {
bindingView.subLineText.text = context.getString(R.string.charging)
} else {
bindingView.subLineText.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text = Preferences.customNotes
bindingView.subLineText.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext,
Preferences.lastNotificationIcon)
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(icon)
} else {
bindingView.subLineIcon.isVisible = false
}
bindingView.subLineText.text = Preferences.lastNotificationTitle
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
val greetingsText = GreetingsHelper.getRandomString(context)
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && greetingsText.isNotBlank()) {
bindingView.subLineText.text = greetingsText
bindingView.subLineText.maxLines = 2
bindingView.subLineIcon.isVisible = false
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
bindingView.subLineText.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
} else "").trimEnd()
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.subLineText.text = String.format(
Locale.getDefault(),
"%d°%s %s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit,
WeatherHelper.getWeatherLabel(context, Preferences.weatherIcon)
)
bindingView.subLineIcon.isVisible = true
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.subLineIcon.isVisible = false
} else {
bindingView.subLineIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.subLineIcon.isVisible = true
}
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
showSomething = true
isWeatherShownAsGlanceProvider = true
break@loop
}
}
}
}
if (showSomething) {
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = false
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else {
bindingView.subLineIcon.isVisible = false
}
}
// Color
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.actionNext)
} else {
listOf<ImageView>(
bindingView.actionNext,
bindingView.weatherDateLineWeatherIcon,
bindingView.weatherSubLineWeatherIcon
)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue()
.toFloat() else Preferences.textGlobalAlpha.toIntValue()
.toFloat()) / 100
}
listOf<TextView>(bindingView.subLineText, bindingView.weatherSubLineDivider, bindingView.weatherSubLineTemperature).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (!isWeatherShownAsGlanceProvider) {
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.subLineIcon, bindingView.subLineIconShadow)
} else {
listOf<ImageView>(
bindingView.subLineIcon,
bindingView.weatherSubLineWeatherIcon,
bindingView.subLineIconShadow
)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue()
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
}
// Text Size
listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineTemperature to ((Preferences.textMainSize + Preferences.textSecondSize) / 2),
bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize * 0.9f),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
if (!it.first.includeFontPadding && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P)
it.first.isFallbackLineSpacing = false
}
// Icons scale
listOf(
bindingView.subLineIcon to Preferences.textSecondSize / 16f,
bindingView.subLineIconShadow to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon to Preferences.textSecondSize / 16f,
bindingView.weatherDateLineWeatherIcon to ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 24f,
bindingView.actionNext to Preferences.textMainSize / 24f,
bindingView.actionNextShadow to Preferences.textMainSize / 24f
).forEach {
if (it.first.tag == null)
it.first.tag = it.first.layoutParams.height
it.first.layoutParams = it.first.layoutParams.apply {
height = ((it.first.tag as Int) * it.second).roundToInt()
width = height
}
}
// Shadows
val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 2f
2 -> 3f
else -> 2f
}.toPixel(context)
val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> Color.DKGRAY
2 -> Color.BLACK
else -> Color.DKGRAY
}
val shadowOffset =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 0.5f
else -> 0f
}.toPixel(context)
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor)
}
// Icons shadow
listOf(
Pair(bindingView.subLineIcon, bindingView.subLineIconShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.8f)
}
}
listOf(
Pair(bindingView.actionNext, bindingView.actionNextShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.6f)
}
}
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
val googleSans: Typeface? = androidx.core.content.res.ResourcesCompat.getFont(
context,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
}
)
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = typeface
}
}
// Dividers
arrayOf(bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.GONE
}
// Right Aligned
if (rightAligned) {
bindingView.mainContent.layoutParams = (bindingView.mainContent.layoutParams as RelativeLayout.LayoutParams).apply {
addRule(RelativeLayout.ALIGN_PARENT_END)
}
bindingView.mainContent.gravity = Gravity.END
bindingView.dateLayout.gravity = Gravity.END
bindingView.date.gravity = Gravity.END
bindingView.calendarLayout.gravity = Gravity.END or Gravity.CENTER_VERTICAL
bindingView.nextEvent.gravity = Gravity.END
bindingView.subLineContainer.gravity = Gravity.END or Gravity.CENTER_VERTICAL
bindingView.subLineText.gravity = Gravity.END
}
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
return null
}
}
}

View File

@ -10,7 +10,9 @@ import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
class ClockWidget(val context: Context) {
fun updateClockView(views: RemoteViews, widgetID: Int): RemoteViews {
@ -22,25 +24,24 @@ class ClockWidget(val context: Context) {
views.setViewVisibility(R.id.clock_bottom_margin_small, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_medium, View.GONE)
views.setViewVisibility(R.id.clock_bottom_margin_large, View.GONE)
views.setViewVisibility(R.id.timezones_container, View.GONE)
} else {
views.setTextColor(R.id.time, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextColor(R.id.time_am_pm, ColorHelper.getClockFontColor(context.isDarkTheme()))
views.setTextViewTextSize(
R.id.time,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize
Preferences.clockTextSize.toPixel(context)
)
views.setTextViewTextSize(
R.id.time_am_pm,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize / 5 * 2
Preferences.clockTextSize.toPixel(context) / 5 * 2
)
val clockPIntent = IntentHelper.getPendingIntent(
val clockPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_IMMUTABLE
0
)
views.setOnClickPendingIntent(R.id.time, clockPIntent)
views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent)
@ -77,29 +78,19 @@ class ClockWidget(val context: Context) {
views.setTextViewTextSize(
R.id.alt_timezone_time,
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize / 3
Preferences.clockTextSize.toPixel(context) / 3
)
views.setTextViewTextSize(
R.id.alt_timezone_time_am_pm,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize / 3) / 5 * 2
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
views.setTextViewTextSize(
R.id.alt_timezone_label,
TypedValue.COMPLEX_UNIT_SP,
(Preferences.clockTextSize / 3) / 5 * 2
(Preferences.clockTextSize.toPixel(context) / 3) / 5 * 2
)
val padding = (TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
Preferences.clockTextSize,
context.resources.displayMetrics
) * 0.2).toInt()
if (Preferences.widgetAlign == Constants.WidgetAlign.RIGHT.rawValue)
views.setViewPadding(R.id.timezones_container, 0, padding, padding, 0)
else
views.setViewPadding(R.id.timezones_container, padding, padding, 0,0)
views.setOnClickPendingIntent(R.id.timezones_container, clockPIntent)
views.setViewVisibility(R.id.timezones_container, View.VISIBLE)
} else {
@ -108,6 +99,7 @@ class ClockWidget(val context: Context) {
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views

View File

@ -0,0 +1,890 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateMargins
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class LeftAlignedWidget(val context: Context) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews {
var views = RemoteViews(context.packageName, R.layout.left_aligned_widget_sans)
try {
// Background
views.setInt(
R.id.widget_shape_background,
"setColorFilter",
ColorHelper.getBackgroundColorRgb(context.isDarkTheme())
)
views.setInt(
R.id.widget_shape_background,
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
views = ClockWidget(context).updateClockView(views, appWidgetId)
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateGridView(bindingView: LeftAlignedWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
val eventRepository = EventRepository(context)
try {
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false)
)
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_medium_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
)
views.setViewVisibility(
R.id.sub_line_top_margin_large_sans,
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
R.id.action_next_rect,
PendingIntent.getBroadcast(
context,
widgetID,
Intent(
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.action_next_rect, View.GONE)
}
// Event intent
val eventIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
// Event time difference
if (Preferences.showDiffTime && Calendar.getInstance().timeInMillis < nextEvent.startDate) {
views.setImageViewBitmap(
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
bindingView.nextEventDifferenceTime,
draw = false
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false)
)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.first_line_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
val alarmIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
ContextCompat.getDrawable(
remotePackageContext,
Preferences.lastNotificationIcon)
}
val notificationIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
notificationIntent
)
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && GreetingsHelper.getRandomString(context).isNotBlank()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider&& Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
pIntentDetail
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
} else {
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
}
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
}
// Generates the widget bitmap from the view
fun generateWidgetView(typeface: Typeface? = null): LeftAlignedWidgetBinding {
val eventRepository = EventRepository(context)
val bindingView = LeftAlignedWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.weatherDateLine.isVisible = true
val currentTemp = String.format(
Locale.getDefault(),
"%d°%s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit
)
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.weatherSubLineWeatherIcon.isVisible = false
bindingView.weatherDateLineWeatherIcon.isVisible = false
} else {
bindingView.weatherSubLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherDateLineWeatherIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.weatherSubLineWeatherIcon.isVisible = true
bindingView.weatherDateLineWeatherIcon.isVisible = true
}
bindingView.weatherDateLineTemperature.text = currentTemp
bindingView.weatherSubLineTemperature.text = currentTemp
if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.weatherSubLine.isVisible = false
}
} else {
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
}
val now = Calendar.getInstance().apply {
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.nextEventDifferenceTime.isVisible = false
bindingView.actionNext.isVisible = false
bindingView.date.text = DateHelper.getDateText(context, now)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
bindingView.nextEventDifferenceTime.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.isVisible = true
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_place_24
)
)
bindingView.subLineText.text = nextEvent.address
} else {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
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)
var dayDiff =
TimeUnit.MILLISECONDS.toDays(nextEvent.endDate - nextEvent.startDate)
val startCal = Calendar.getInstance()
startCal.timeInMillis = nextEvent.startDate
val endCal = Calendar.getInstance()
endCal.timeInMillis = nextEvent.endDate
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)
) {
dayDiff++
}
var multipleDay = ""
if (dayDiff > 0) {
multipleDay = String.format(
" (+%s%s)",
dayDiff,
context.getString(R.string.day_char)
)
}
if (nextEvent.startDate != nextEvent.endDate) {
bindingView.subLineText.text =
String.format("%s - %s%s", startHour, endHour, multipleDay)
} else {
bindingView.subLineText.text =
String.format("%s", startHour)
}
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateUtils.formatDateTime(context, now.timeInMillis, flags)
} else {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
}
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = true
bindingView.subLineTopMarginSmall.visibility = View.GONE
bindingView.subLineTopMarginMedium.visibility = View.GONE
bindingView.subLineTopMarginLarge.visibility = View.GONE
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
bindingView.subLineIcon.isVisible = true
var showSomething = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(
context
)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_music_note_24
)
)
bindingView.subLineText.text = MediaPlayerHelper.getMediaInfo()
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm && nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging) {
bindingView.subLineIcon.isVisible = false
val batteryLevel = BatteryHelper.getBatteryLevel(context)
if (batteryLevel != 100) {
bindingView.subLineText.text = context.getString(R.string.charging)
} else {
bindingView.subLineText.text =
context.getString(R.string.charged)
}
showSomething = true
break@loop
} else if (Preferences.isBatteryLevelLow) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.battery_low_warning)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text = Preferences.customNotes
bindingView.subLineText.maxLines = 2
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
bindingView.subLineIcon.isVisible = false
bindingView.subLineText.text =
context.getString(R.string.daily_steps_counter)
.format(Preferences.googleFitSteps)
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.NOTIFICATIONS -> {
if (Preferences.showNotifications && ActiveNotificationsHelper.showLastNotification()) {
try {
if (Preferences.lastNotificationIcon != 0) {
val remotePackageContext = context.createPackageContext(
Preferences.lastNotificationPackage, 0)
val icon = ContextCompat.getDrawable(remotePackageContext,
Preferences.lastNotificationIcon)
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(icon)
} else {
bindingView.subLineIcon.isVisible = false
}
bindingView.subLineText.text = Preferences.lastNotificationTitle
showSomething = true
break@loop
} catch (ex: Exception) {}
}
}
Constants.GlanceProviderId.GREETINGS -> {
val greetingsText = GreetingsHelper.getRandomString(context)
if (Preferences.showGreetings && GreetingsHelper.showGreetings() && greetingsText.isNotBlank()) {
bindingView.subLineText.text = greetingsText
bindingView.subLineText.maxLines = 2
bindingView.subLineIcon.isVisible = false
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider && Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
bindingView.subLineText.text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
} else "").trimEnd()
bindingView.subLineIcon.isVisible = true
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_today_24
)
)
showSomething = true
break@loop
}
}
}
}
if (showSomething) {
bindingView.dateLayout.isVisible = true
bindingView.calendarLayout.isVisible = false
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = false
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginMedium.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.MEDIUM.rawValue) View.VISIBLE else View.GONE
bindingView.subLineTopMarginLarge.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.LARGE.rawValue) View.VISIBLE else View.GONE
} else {
bindingView.subLineIcon.isVisible = false
}
}
// Color
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
).forEach {
it.setTextColor(ColorHelper.getFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.actionNext)
} else {
listOf<ImageView>(
bindingView.actionNext,
bindingView.weatherDateLineWeatherIcon,
bindingView.weatherSubLineWeatherIcon
)
}.forEach {
it.setColorFilter(ColorHelper.getFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textGlobalAlphaDark.toIntValue()
.toFloat() else Preferences.textGlobalAlpha.toIntValue()
.toFloat()) / 100
}
listOf<TextView>(bindingView.subLineText, bindingView.weatherSubLineDivider, bindingView.weatherSubLineTemperature).forEach {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.subLineIcon, bindingView.subLineIconShadow)
} else {
listOf<ImageView>(bindingView.subLineIcon, bindingView.weatherSubLineWeatherIcon, bindingView.subLineIconShadow)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
(if (context.isDarkTheme()) Preferences.textSecondaryAlphaDark.toIntValue()
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
// Text Size
listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineTemperature to ((Preferences.textMainSize + Preferences.textSecondSize) / 2),
bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
}
// Icons scale
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherDateLineWeatherIcon.scaleX = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.weatherDateLineWeatherIcon.scaleY = ((Preferences.textMainSize + Preferences.textSecondSize) / 2) / 20f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f
// 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
}
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Icons shadow
listOf(
Pair(bindingView.subLineIcon, bindingView.subLineIconShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first)
}
}
listOf(
Pair(bindingView.actionNext, bindingView.actionNextShadow),
).forEach {
if ((if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) == 0) {
it.second.isVisible = false
} else {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.6f)
}
}
// Custom Font
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>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = googleSans
}
} else if (Preferences.customFont == Constants.CUSTOM_FONT_DOWNLOADED && typeface != null) {
listOf<TextView>(
bindingView.date,
bindingView.weatherDateLineTemperature,
bindingView.nextEvent,
bindingView.nextEventDifferenceTime,
bindingView.subLineText,
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.typeface = typeface
}
}
// Dividers
arrayOf(bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
}
eventRepository.close()
return bindingView
}
}

View File

@ -1,20 +1,47 @@
package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.format.DateUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.viewbinding.ViewBinding
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.databinding.TheWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.*
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.convertDpToPixel
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
import java.lang.Exception
import java.text.DateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.math.min
import kotlin.math.roundToInt
class MainWidget : AppWidgetProvider() {
@ -32,28 +59,27 @@ class MainWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) {
CalendarHelper.updateEventList(context)
WeatherHelper.updateWeather(context)
WeatherReceiver.setUpdates(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
if (Preferences.showEvents) {
CalendarHelper.setEventUpdatesAndroidN(context)
} else {
CalendarHelper.removeEventUpdatesAndroidN(context)
}
}
override fun onDisabled(context: Context) {
if (getWidgetCount(context) == 0) {
CalendarHelper.removeEventUpdatesAndroidN(context)
UpdatesReceiver.removeUpdates(context)
WeatherReceiver.removeUpdates(context)
}
}
companion object {
private val handler by lazy { android.os.Handler(android.os.Looper.getMainLooper()) }
fun updateWidget(context: Context) {
handler.run {
removeCallbacksAndMessages(null)
postDelayed ({
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
}, 100)
}
}
fun getWidgetCount(context: Context): Int {
@ -64,34 +90,25 @@ class MainWidget : AppWidgetProvider() {
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager,
appWidgetId: Int) {
val displayMetrics = Resources.getSystem().displayMetrics
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val dimensions = WidgetHelper.WidgetSizeProvider(context, appWidgetManager).getWidgetsSize(appWidgetId)
WidgetHelper.runWithCustomTypeface(context) {
val views = when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(appWidgetId, dimensions.first, it)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(context, rightAligned = true).generateWidget(appWidgetId, dimensions.first, it)
else -> StandardWidget(context).generateWidget(appWidgetId, dimensions.first, it)
}
try {
if (views != null) appWidgetManager.updateAppWidget(appWidgetId, views)
} catch (ex: Exception) {
ex.printStackTrace()
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
else -> StandardWidget(context).generateWidget(appWidgetId, min(dimensions.first - 8.toPixel(context), min(width, height) - 16.toPixel(context)), it)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
fun getWidgetView(context: Context, width: Int, typeface: Typeface?): RemoteViews? {
fun getWidgetView(context: Context, typeface: Typeface?): ViewBinding {
return when (Preferences.widgetAlign) {
Constants.WidgetAlign.LEFT.rawValue -> AlignedWidget(context).generateWidget(
0,
width,
typeface
)
Constants.WidgetAlign.RIGHT.rawValue -> AlignedWidget(
context,
rightAligned = true
).generateWidget(0, width, typeface)
else -> StandardWidget(context).generateWidget(0, width, typeface)
Constants.WidgetAlign.LEFT.rawValue -> LeftAlignedWidget(context).generateWidgetView(typeface)
else -> StandardWidget(context).generateWidgetView(typeface)
}
}
}

View File

@ -2,14 +2,17 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RemoteViews
import android.widget.TextView
@ -24,6 +27,7 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
@ -36,7 +40,7 @@ import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
class StandardWidget(val context: Context) {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews? {
fun generateWidget(appWidgetId: Int, w: Int, typeface: Typeface? = null): RemoteViews {
var views = RemoteViews(context.packageName, R.layout.the_widget_sans)
@ -52,21 +56,16 @@ class StandardWidget(val context: Context) {
"setImageAlpha",
ColorHelper.getBackgroundAlpha(context.isDarkTheme())
)
val margin = Preferences.widgetMargin.convertDpToPixel(context).toInt()
views.setViewPadding(R.id.widget_shape_background, margin, margin, margin, margin)
val refreshIntent = IntentHelper.getPendingIntent(
val refreshIntent = PendingIntent.getActivity(
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
// Padding
val padding = (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt()
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
@ -74,66 +73,61 @@ class StandardWidget(val context: Context) {
// Setup listener
try {
val generatedBinding = generateWidgetView(typeface) ?: return null
val width = w - (Preferences.widgetPadding.convertDpToPixel(context) + Preferences.widgetMargin.convertDpToPixel(context)).toInt() * 2
val generatedBinding = generateWidgetView(typeface)
views.setImageViewBitmap(
R.id.bitmap_container,
BitmapHelper.getBitmapFromView(generatedBinding.root, width)
BitmapHelper.getBitmapFromView(generatedBinding.root, width = w)
)
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
}
private fun updateGridView(bindingView: TheWidgetBinding, views: RemoteViews, widgetID: Int): RemoteViews {
try {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
try {
// Weather
if (Preferences.showWeather && Preferences.weatherIcon != "") {
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
views.setImageViewBitmap(
R.id.weather_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false, width = bindingView.weatherDateLine.width, height = bindingView.weatherDateLine.height)
BitmapHelper.getBitmapFromView(bindingView.weatherDateLine, draw = false)
)
views.setImageViewBitmap(
R.id.weather_sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false, width = bindingView.weatherSubLine.width, height = bindingView.weatherSubLine.height)
BitmapHelper.getBitmapFromView(bindingView.weatherSubLine, draw = false)
)
} else {
views.setViewVisibility(R.id.weather_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line, View.GONE)
}
// Calendar
views.setImageViewBitmap(
R.id.date_rect,
BitmapHelper.getBitmapFromView(bindingView.date, draw = false, width = bindingView.date.width, height = bindingView.date.height)
BitmapHelper.getBitmapFromView(bindingView.date, draw = false)
)
val calPIntent = IntentHelper.getPendingIntent(
val calPIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
@ -141,9 +135,12 @@ class StandardWidget(val context: Context) {
// Second row
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false, width = bindingView.subLine.width, height = bindingView.subLine.height)
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
// Spacing
views.setViewVisibility(
R.id.sub_line_top_margin_small_sans,
@ -159,12 +156,12 @@ class StandardWidget(val context: Context) {
)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
if (Preferences.showNextEvent && eventsCount > 1) {
if (Preferences.showNextEvent && eventRepository.getEventsCount() > 1) {
// Action next event
views.setImageViewBitmap(
R.id.action_next_rect,
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false, width = bindingView.actionNext.width, height = bindingView.actionNext.height)
BitmapHelper.getBitmapFromView(bindingView.actionNext, draw = false)
)
views.setViewVisibility(R.id.action_next_rect, View.VISIBLE)
views.setOnClickPendingIntent(
@ -176,14 +173,14 @@ class StandardWidget(val context: Context) {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
)
// Action previous event
views.setImageViewBitmap(
R.id.action_previous_rect,
BitmapHelper.getBitmapFromView(bindingView.actionPrevious, draw = false, width = bindingView.actionPrevious.width, height = bindingView.actionPrevious.height)
BitmapHelper.getBitmapFromView(bindingView.actionPrevious, draw = false)
)
views.setViewVisibility(R.id.action_previous_rect, View.VISIBLE)
views.setOnClickPendingIntent(
@ -195,7 +192,7 @@ class StandardWidget(val context: Context) {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
)
@ -207,11 +204,11 @@ class StandardWidget(val context: Context) {
}
// Event intent
val eventIntent = IntentHelper.getPendingIntent(
val eventIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
@ -222,63 +219,59 @@ class StandardWidget(val context: Context) {
R.id.next_event_difference_time_rect,
BitmapHelper.getBitmapFromView(
bindingView.nextEventDifferenceTime,
draw = false,
width = bindingView.nextEventDifferenceTime.width,
height = bindingView.nextEventDifferenceTime.height
draw = false
)
)
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
if (!Preferences.showNextEventOnMultipleLines) {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.VISIBLE)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
views.setOnClickPendingIntent(R.id.next_event_difference_time_rect, eventIntent)
} else {
views.setViewVisibility(R.id.next_event_difference_time_rect, View.GONE)
}
// Event information
if (nextEvent.address != "" && Preferences.secondRowInformation == 1) {
val mapIntent = IntentHelper.getPendingIntent(
val mapIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
val pIntentDetail = IntentHelper.getPendingIntent(
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
IntentHelper.getEventIntent(
context,
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
views.setImageViewBitmap(
R.id.next_event_rect,
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false, width = bindingView.nextEvent.width, height = bindingView.nextEvent.height)
BitmapHelper.getBitmapFromView(bindingView.nextEvent, draw = false)
)
views.setViewVisibility(R.id.calendar_layout_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_sub_line_rect, if (Preferences.showWeather && Preferences.weatherIcon != "") View.VISIBLE else View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.first_line_rect, View.GONE)
} else if (GlanceProviderHelper.showGlanceProviders(context)) {
var showSomething = false
var isWeatherShown = false
loop@ for (provider: Constants.GlanceProviderId in GlanceProviderHelper.getGlanceProviders(context)) {
when (provider) {
Constants.GlanceProviderId.PLAYING_SONG -> {
if (MediaPlayerHelper.isSomeonePlaying(context)) {
val musicIntent = IntentHelper.getPendingIntent(
val musicIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
@ -286,30 +279,27 @@ class StandardWidget(val context: Context) {
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
val alarmIntent = IntentHelper.getPendingIntent(
if (Preferences.showNextAlarm && nextAlarm != "") {
val alarmIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
if (Preferences.isCharging || Preferences.isBatteryLevelLow) {
val batteryIntent = IntentHelper.getPendingIntent(
val batteryIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
@ -319,17 +309,16 @@ class StandardWidget(val context: Context) {
}
Constants.GlanceProviderId.CUSTOM_INFO -> {
if (Preferences.customNotes.isNotEmpty()) {
showSomething = true
break@loop
}
}
Constants.GlanceProviderId.GOOGLE_FIT_STEPS -> {
if (Preferences.showDailySteps && Preferences.googleFitSteps > 0) {
val fitIntent = IntentHelper.getPendingIntent(
val fitIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
@ -346,11 +335,11 @@ class StandardWidget(val context: Context) {
remotePackageContext,
Preferences.lastNotificationIcon)
}
val notificationIntent = IntentHelper.getPendingIntent(
val notificationIntent = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -370,7 +359,7 @@ class StandardWidget(val context: Context) {
Constants.GlanceProviderId.EVENTS -> {
if (Preferences.showEventsAsGlanceProvider&& Preferences.showEvents && context.checkGrantedPermission(
Manifest.permission.READ_CALENDAR) && nextEvent != null) {
val pIntentDetail = IntentHelper.getPendingIntent(
val pIntentDetail = PendingIntent.getActivity(
context,
widgetID,
IntentHelper.getEventIntent(
@ -378,7 +367,7 @@ class StandardWidget(val context: Context) {
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -388,21 +377,6 @@ class StandardWidget(val context: Context) {
break@loop
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
weatherPIntent
)
showSomething = true
isWeatherShown = true
break@loop
}
}
}
}
@ -410,10 +384,9 @@ class StandardWidget(val context: Context) {
if (showSomething) {
views.setImageViewBitmap(
R.id.sub_line_rect,
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false, width = bindingView.subLine.width, height = bindingView.subLine.height)
BitmapHelper.getBitmapFromView(bindingView.subLine, draw = false)
)
views.setViewVisibility(R.id.weather_rect, if (isWeatherShown) View.GONE else View.VISIBLE)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.sub_line_rect, View.VISIBLE)
@ -425,20 +398,12 @@ class StandardWidget(val context: Context) {
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
} else {
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
views.setViewVisibility(R.id.weather_rect, View.VISIBLE)
views.setViewVisibility(R.id.calendar_layout_rect, View.GONE)
views.setViewVisibility(R.id.sub_line_rect, View.GONE)
views.setViewVisibility(R.id.weather_sub_line_rect, View.GONE)
// Spacing
views.setViewVisibility(R.id.sub_line_top_margin_small_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_medium_sans, View.GONE)
views.setViewVisibility(R.id.sub_line_top_margin_large_sans, View.GONE)
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
} finally {
eventRepository.close()
}
return views
@ -446,14 +411,8 @@ class StandardWidget(val context: Context) {
// Generates the widget bitmap from the view
private fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding? {
try {
var isWeatherShownAsGlanceProvider = false
fun generateWidgetView(typeface: Typeface? = null): TheWidgetBinding {
val eventRepository = EventRepository(context)
val nextEvent = eventRepository.getNextEvent()
val eventsCount = eventRepository.getEventsCount()
eventRepository.close()
val bindingView = TheWidgetBinding.inflate(LayoutInflater.from(context))
bindingView.loader.isVisible = false
@ -503,30 +462,26 @@ class StandardWidget(val context: Context) {
bindingView.date.text = DateHelper.getDateText(context, now)
val nextEvent = eventRepository.getNextEvent()
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (Preferences.showEvents && context.checkGrantedPermission(Manifest.permission.READ_CALENDAR) && nextEvent != null && !Preferences.showEventsAsGlanceProvider) {
// Multiple counter
bindingView.actionNext.isVisible =
Preferences.showNextEvent && eventsCount > 1
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.actionPrevious.isVisible =
Preferences.showNextEvent && eventsCount > 1
Preferences.showNextEvent && eventRepository.getEventsCount() > 1
bindingView.nextEvent.text = nextEvent.title
if (Preferences.showNextEventOnMultipleLines) {
bindingView.nextEvent.apply {
isSingleLine = false
maxLines = 3
gravity = Gravity.CENTER
}
}
if (Preferences.showDiffTime && now.timeInMillis < nextEvent.startDate) {
val diffTime = if (!nextEvent.allDay) {
bindingView.nextEventDifferenceTime.text = if (!nextEvent.allDay) {
SettingsStringHelper.getDifferenceText(
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
@ -534,25 +489,7 @@ class StandardWidget(val context: Context) {
nextEvent.startDate
).toLowerCase(Locale.getDefault())
}
bindingView.nextEventDifferenceTime.text = diffTime
if (!Preferences.showNextEventOnMultipleLines) {
bindingView.nextEventDifferenceTime.isVisible = true
} else {
val text = context.getString(R.string.events_glance_provider_format).format(nextEvent.title, diffTime)
if (text.endsWith(diffTime)) {
bindingView.nextEvent.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
(v as TextView).layout?.run {
val diff = diffTime.trimStart();
val diffStart = text.length - diff.length
if (getLineStart(lineCount - 1) > diffStart)
v.text = (text.substring(0, diffStart).trimEnd() + '\n' + diff)
}
}
}
bindingView.nextEvent.text = text
bindingView.nextEventDifferenceTime.isVisible = false
}
} else {
bindingView.nextEventDifferenceTime.isVisible = false
}
@ -615,12 +552,18 @@ class StandardWidget(val context: Context) {
}
} else {
val flags: Int =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or DateUtils.FORMAT_ABBREV_MONTH
val start = Calendar.getInstance().apply { timeInMillis = nextEvent.startDate }
bindingView.subLineText.text = if (now.after(start)) {
DateHelper.getDateText(context, now)
bindingView.subLineText.text = if (now.get(Calendar.DAY_OF_YEAR) == start.get(
Calendar.DAY_OF_YEAR)) {
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
} else if (now.get(Calendar.DAY_OF_YEAR) > start.get(Calendar.DAY_OF_YEAR) || now.get(
Calendar.YEAR) > start.get(Calendar.YEAR)) {
DateUtils.formatDateTime(context, now.timeInMillis, flags)
} else {
DateHelper.getDateText(context, start)
DateUtils.formatDateTime(context, nextEvent.startDate, flags)
}
}
}
@ -628,7 +571,7 @@ class StandardWidget(val context: Context) {
bindingView.dateLayout.isVisible = false
bindingView.calendarLayout.isVisible = true
bindingView.subLine.isVisible = true
bindingView.weatherSubLine.isVisible = Preferences.showWeather && Preferences.weatherIcon != ""
bindingView.weatherSubLine.isVisible = true
bindingView.subLineTopMarginSmall.visibility =
if (Preferences.secondRowTopMargin == Constants.SecondRowTopMargin.SMALL.rawValue) View.VISIBLE else View.GONE
@ -657,21 +600,18 @@ class StandardWidget(val context: Context) {
}
}
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
if (Preferences.showNextAlarm) {
val nextAlarm = AlarmHelper.getNextAlarm(context)
if (nextAlarm != "") {
if (Preferences.showNextAlarm && nextAlarm != "") {
bindingView.subLineIcon.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.round_alarm_24
)
)
bindingView.subLineText.text = nextAlarm
bindingView.subLineText.text = AlarmHelper.getNextAlarm(context)
showSomething = true
break@loop
}
}
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
if (Preferences.showBatteryCharging) {
BatteryHelper.updateBatteryInfo(context)
@ -752,7 +692,8 @@ class StandardWidget(val context: Context) {
context,
now.timeInMillis,
nextEvent.startDate
).toLowerCase(Locale.getDefault())
)
.toLowerCase(Locale.getDefault())
} else {
SettingsStringHelper.getAllDayEventDifferenceText(
context,
@ -772,33 +713,6 @@ class StandardWidget(val context: Context) {
break@loop
}
}
Constants.GlanceProviderId.WEATHER -> {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
bindingView.subLineText.text = String.format(
Locale.getDefault(),
"%d°%s %s",
Preferences.weatherTemp.roundToInt(),
Preferences.weatherRealTempUnit,
WeatherHelper.getWeatherLabel(context, Preferences.weatherIcon)
)
bindingView.subLineIcon.isVisible = true
val icon: String = Preferences.weatherIcon
if (icon == "") {
bindingView.subLineIcon.isVisible = false
} else {
bindingView.subLineIcon.setImageResource(WeatherHelper.getWeatherIconResource(context, icon))
bindingView.subLineIcon.isVisible = true
}
bindingView.weatherDateLine.isVisible = false
bindingView.weatherSubLine.isVisible = false
isWeatherShownAsGlanceProvider = true
showSomething = true
break@loop
}
}
}
}
@ -852,15 +766,10 @@ class StandardWidget(val context: Context) {
it.setTextColor(ColorHelper.getSecondaryFontColor(context.applicationContext.isDarkTheme()))
}
if (!isWeatherShownAsGlanceProvider) {
if (Preferences.weatherIconPack != Constants.WeatherIconPack.MINIMAL.rawValue) {
listOf<ImageView>(bindingView.subLineIcon, bindingView.subLineIconShadow)
} else {
listOf<ImageView>(
bindingView.subLineIcon,
bindingView.weatherSubLineWeatherIcon,
bindingView.subLineIconShadow
)
listOf<ImageView>(bindingView.subLineIcon, bindingView.weatherSubLineWeatherIcon, bindingView.subLineIconShadow)
}.forEach {
it.setColorFilter(ColorHelper.getSecondaryFontColorRgb(context.applicationContext.isDarkTheme()))
it.alpha =
@ -868,65 +777,60 @@ class StandardWidget(val context: Context) {
.toFloat() else Preferences.textSecondaryAlpha.toIntValue()
.toFloat()) / 100
}
}
// Text Size
listOf<Pair<TextView, Float>>(
bindingView.date to Preferences.textMainSize,
bindingView.weatherDateLineDivider to (Preferences.textMainSize * 0.9f),
bindingView.weatherDateLineDivider to (Preferences.textMainSize - 2),
bindingView.weatherDateLineTemperature to Preferences.textMainSize,
bindingView.nextEvent to Preferences.textMainSize,
bindingView.nextEventDifferenceTime to Preferences.textMainSize,
bindingView.subLineText to Preferences.textSecondSize,
bindingView.weatherSubLineDivider to (Preferences.textSecondSize * 0.9f),
bindingView.weatherSubLineDivider to (Preferences.textSecondSize - 2),
bindingView.weatherSubLineTemperature to Preferences.textSecondSize,
).forEach {
it.first.setTextSize(TypedValue.COMPLEX_UNIT_SP, it.second)
if (!it.first.includeFontPadding && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P)
it.first.isFallbackLineSpacing = false
}
// Icons scale
listOf(
bindingView.subLineIcon to Preferences.textSecondSize / 16f,
bindingView.subLineIconShadow to Preferences.textSecondSize / 16f,
bindingView.weatherSubLineWeatherIcon to Preferences.textSecondSize / 16f,
bindingView.weatherDateLineWeatherIcon to Preferences.textMainSize / 24f,
bindingView.actionNext to Preferences.textMainSize / 24f,
bindingView.actionNextShadow to Preferences.textMainSize / 24f,
bindingView.actionPrevious to Preferences.textMainSize / 24f,
bindingView.actionPreviousShadow to Preferences.textMainSize / 24f
).forEach {
if (it.first.tag == null)
it.first.tag = it.first.layoutParams.height
it.first.layoutParams = it.first.layoutParams.apply {
height = ((it.first.tag as Int) * it.second).roundToInt()
width = height
}
}
bindingView.subLineIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.subLineIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleX = Preferences.textSecondSize / 18f
bindingView.weatherSubLineWeatherIcon.scaleY = Preferences.textSecondSize / 18f
bindingView.weatherDateLineWeatherIcon.scaleX = Preferences.textMainSize / 18f
bindingView.weatherDateLineWeatherIcon.scaleY = Preferences.textMainSize / 18f
bindingView.actionNext.scaleX = Preferences.textMainSize / 28f
bindingView.actionNext.scaleY = Preferences.textMainSize / 28f
bindingView.actionPrevious.scaleX = Preferences.textMainSize / 28f
bindingView.actionPrevious.scaleY = Preferences.textMainSize / 28f
// Shadows
val shadowRadius =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 2f
2 -> 3f
else -> 2f
}.toPixel(context)
1 -> 5f
2 -> 5f
else -> 5f
}
val shadowColor =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> Color.TRANSPARENT
1 -> Color.DKGRAY
1 -> R.color.black_50
2 -> Color.BLACK
else -> Color.DKGRAY
else -> R.color.black_50
}
val shadowOffset =
val shadowDy =
when (if (context.isDarkTheme()) Preferences.textShadowDark else Preferences.textShadow) {
0 -> 0f
1 -> 0f
2 -> 0.5f
2 -> 1f
else -> 0f
}.toPixel(context)
}
listOf<TextView>(
bindingView.date,
@ -938,7 +842,7 @@ class StandardWidget(val context: Context) {
bindingView.weatherSubLineDivider,
bindingView.weatherSubLineTemperature,
).forEach {
it.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor)
it.setShadowLayer(shadowRadius, 0f, shadowDy, shadowColor)
}
// Icons shadow
@ -952,7 +856,7 @@ class StandardWidget(val context: Context) {
it.second.isVisible = it.first.isVisible
it.second.scaleX = it.first.scaleX
it.second.scaleY = it.first.scaleY
it.second.applyShadow(it.first, 0.8f)
it.second.applyShadow(it.first)
}
}
@ -975,17 +879,14 @@ class StandardWidget(val context: Context) {
// Custom Font
if (Preferences.customFont == Constants.CUSTOM_FONT_GOOGLE_SANS) {
val googleSans: Typeface? = androidx.core.content.res.ResourcesCompat.getFont(
context,
when (Preferences.customFontVariant) {
"100" -> R.font.google_sans_thin
"200" -> R.font.google_sans_light
"500" -> R.font.google_sans_medium
"700" -> R.font.google_sans_bold
"800" -> R.font.google_sans_black
else -> R.font.google_sans_regular
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>(
bindingView.date,
@ -1016,15 +917,14 @@ class StandardWidget(val context: Context) {
// Dividers
arrayOf(bindingView.weatherDateLineDivider, bindingView.weatherSubLineDivider).forEach {
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.GONE
it.visibility = if (Preferences.showDividers) View.VISIBLE else View.INVISIBLE
it.layoutParams = (it.layoutParams as ViewGroup.MarginLayoutParams).apply {
this.marginEnd = if (Preferences.showDividers) 8f.convertDpToPixel(context).toInt() else 0
}
}
eventRepository.close()
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
return null
}
}
}

View File

@ -24,20 +24,18 @@ import android.util.TypedValue
import android.view.animation.AlphaAnimation
import android.widget.RelativeLayout
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatDelegate
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.core.animation.addListener
import androidx.core.view.isVisible
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.components.OnSingleClickListener
import com.tommasoberlose.anotherwidget.global.Preferences
import java.util.*
fun PackageManager.missingSystemFeature(name: String): Boolean = !hasSystemFeature(name)
fun Context.toast(message: String, long: Boolean = false) {
val toast = Toast.makeText(applicationContext, message, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)
val toast = Toast.makeText(this, message, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)
// toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
@ -194,7 +192,7 @@ fun String.isValidEmail(): Boolean
Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun Context.isDarkTheme(): Boolean {
return Preferences.darkThemePreference == AppCompatDelegate.MODE_NIGHT_YES || Preferences.darkThemePreference == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
}
fun Activity.isNotificationAccessGranted(): Boolean = Settings.Secure.getString(this.contentResolver,"enabled_notification_listeners").contains(this.packageName)
@ -214,14 +212,6 @@ fun Context.checkGrantedPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
fun android.app.AlarmManager.setExactIfCanSchedule(type: Int, triggerAtMillis: Long, operation: android.app.PendingIntent) {
// uncomment the following check after bumping compileSdkVersion/targetSdkVersion to 31
//if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S || canScheduleExactAlarms())
setExact(type, triggerAtMillis, operation)
//else
// set(type, triggerAtMillis, operation)
}
fun Context.getCurrentWallpaper(): Drawable? = try {
WallpaperManager.getInstance(this).drawable
} catch (e: Exception) {
@ -275,11 +265,3 @@ fun View.setOnSingleClickListener(l: View.OnClickListener) {
fun View.setOnSingleClickListener(l: (View) -> Unit) {
setOnClickListener(OnSingleClickListener(l))
}
fun ignoreExceptions(function: () -> Unit) = run {
try {
function.invoke()
} catch (ex: Exception) {
ex.printStackTrace()
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

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