@@ -4,21 +4,18 @@ import android.Manifest
44import android.annotation.SuppressLint
55import android.content.*
66import android.content.pm.PackageManager
7- import android.location.Geocoder
8- import android.location.Location
97import android.os.*
108import android.util.AttributeSet
119import androidx.annotation.StringRes
1210import androidx.appcompat.widget.AppCompatTextView
1311import androidx.core.content.ContextCompat
14- import androidx.core.os.ConfigurationCompat
1512import com.google.android.gms.location.*
16- import com.google.android.gms.tasks.CancellationTokenSource
1713import io.nekohasekai.sagernet.R
1814import io.nekohasekai.sagernet.database.DataStore
1915import kotlinx.coroutines.*
2016import org.json.JSONObject
2117import java.net.URL
18+ import java.net.URLEncoder
2219import java.util.*
2320import kotlin.math.roundToInt
2421
@@ -31,116 +28,66 @@ class Greetings @JvmOverloads constructor(
3128 private val fusedClient by lazy { LocationServices .getFusedLocationProviderClient(context) }
3229 private val handler = Handler (Looper .getMainLooper())
3330 private val coroutineScope = CoroutineScope (SupervisorJob () + Dispatchers .Main )
34-
35- private val prefs by lazy { context.getSharedPreferences(" neko_weather_cache" , Context .MODE_PRIVATE ) }
3631
3732 private var showWeather = false
38- private var useManualCity = false
39-
4033 private var lastWeatherTime = 0L
41- private var lastLat = 0.0
42- private var lastLon = 0.0
43- private var cachedTemp = 0
44- private var cachedCode = - 1
45- private var cachedLocName = " "
34+ private var lastWeatherText: String? = null
35+ private var useManualCity = false
4636
4737 private val weatherInterval = 30 * 60 * 1000L
48- private val minDistanceChange = 2000f
38+ private val locationRequest = LocationRequest .Builder (
39+ Priority .PRIORITY_BALANCED_POWER_ACCURACY , 10_000L
40+ ).setMinUpdateIntervalMillis(60_000L ).build()
4941
5042 private val timeReceiver = object : BroadcastReceiver () {
5143 override fun onReceive (context : Context ? , intent : Intent ? ) {
5244 if (intent?.action in listOf (
53- Intent .ACTION_TIME_TICK ,
54- Intent .ACTION_TIME_CHANGED ,
55- Intent .ACTION_TIMEZONE_CHANGED
45+ Intent .ACTION_TIME_TICK , Intent .ACTION_TIME_CHANGED , Intent .ACTION_TIMEZONE_CHANGED
5646 )
57- ) {
58- refreshGreetingText()
59- }
47+ ) updateGreeting(lastWeatherText ? : " " )
6048 }
6149 }
6250
6351 init {
6452 showWeather = DataStore .showWeatherInfo
6553 useManualCity = DataStore .manualWeatherEnabled
66-
67- loadCache()
68- refreshGreetingText()
54+ updateGreeting()
6955 }
7056
7157 override fun onAttachedToWindow () {
7258 super .onAttachedToWindow()
73- val filter = IntentFilter ().apply {
59+ context.registerReceiver(timeReceiver, IntentFilter ().apply {
7460 addAction(Intent .ACTION_TIME_TICK )
7561 addAction(Intent .ACTION_TIME_CHANGED )
7662 addAction(Intent .ACTION_TIMEZONE_CHANGED )
77- }
78- context.registerReceiver(timeReceiver, filter)
63+ })
7964
80- checkSettingsAndFetch()
65+ showWeather = DataStore .showWeatherInfo
66+ useManualCity = DataStore .manualWeatherEnabled
67+
68+ if (showWeather) fetchWeather()
69+ scheduleWeatherRefresh()
8170 }
8271
8372 override fun onDetachedFromWindow () {
8473 super .onDetachedFromWindow()
85- try {
86- context.unregisterReceiver(timeReceiver)
87- } catch (e: Exception ) {
88- }
74+ context.unregisterReceiver(timeReceiver)
8975 handler.removeCallbacksAndMessages(null )
9076 coroutineScope.cancel()
9177 }
9278
9379 override fun isFocused (): Boolean = true
9480
95- private fun getAppLocale (): Locale {
96- return ConfigurationCompat .getLocales(context.resources.configuration)[0 ]
97- ? : Locale .getDefault()
98- }
99-
100- private fun loadCache () {
101- lastWeatherTime = prefs.getLong(" cached_time" , 0L )
102- lastLat = prefs.getFloat(" cached_lat" , 0f ).toDouble()
103- lastLon = prefs.getFloat(" cached_lon" , 0f ).toDouble()
104- cachedTemp = prefs.getInt(" cached_temp" , 0 )
105- cachedCode = prefs.getInt(" cached_code" , - 1 )
106- cachedLocName = prefs.getString(" cached_loc_name" , " " ) ? : " "
107- }
108-
109- private fun saveCache (time : Long , lat : Double , lon : Double , temp : Int , code : Int , locName : String ) {
110- lastWeatherTime = time
111- lastLat = lat
112- lastLon = lon
113- cachedTemp = temp
114- cachedCode = code
115- cachedLocName = locName
116-
117- prefs.edit().apply {
118- putLong(" cached_time" , time)
119- putFloat(" cached_lat" , lat.toFloat())
120- putFloat(" cached_lon" , lon.toFloat())
121- putInt(" cached_temp" , temp)
122- putInt(" cached_code" , code)
123- putString(" cached_loc_name" , locName)
124- apply ()
125- }
126- }
127-
128- private fun refreshGreetingText () {
129- val weatherString = if (cachedCode != - 1 && showWeather) {
130- val condition = getLocalizedCondition(cachedCode)
131- val emoji = getWeatherEmoji(cachedCode)
132- val prefix = context.getString(R .string.weather_today)
133- val locSuffix = if (cachedLocName.isNotEmpty()) " ($cachedLocName )" else " "
134-
135- " $prefix $condition $emoji , $cachedTemp °C$locSuffix "
136- } else {
137- " "
138- }
139-
140- updateTextView(weatherString)
81+ private fun scheduleWeatherRefresh () {
82+ handler.postDelayed(object : Runnable {
83+ override fun run () {
84+ if (showWeather) fetchWeather()
85+ handler.postDelayed(this , weatherInterval)
86+ }
87+ }, weatherInterval)
14188 }
14289
143- private fun updateTextView (weatherText : String ) {
90+ private fun updateGreeting (weatherText : String = "" ) {
14491 val hour = Calendar .getInstance().get(Calendar .HOUR_OF_DAY )
14592 @StringRes val greetRes = when (hour) {
14693 in 5 .. 10 -> R .string.uwu_greeting_morning
@@ -154,16 +101,6 @@ class Greetings @JvmOverloads constructor(
154101 text = if (weatherText.isNotEmpty() && showWeather) " $greeting $weatherText " else greeting
155102 }
156103
157- private fun checkSettingsAndFetch () {
158- showWeather = DataStore .showWeatherInfo
159- useManualCity = DataStore .manualWeatherEnabled
160-
161- if (showWeather) fetchWeather()
162-
163- handler.removeCallbacksAndMessages(null )
164- handler.postDelayed({ checkSettingsAndFetch() }, weatherInterval)
165- }
166-
167104 private fun fetchWeather () {
168105 if (useManualCity) {
169106 val city = DataStore .manualWeatherCity.ifEmpty { " Tokyo" }
@@ -178,98 +115,85 @@ class Greetings @JvmOverloads constructor(
178115 if (ContextCompat .checkSelfPermission(context, Manifest .permission.ACCESS_FINE_LOCATION )
179116 != PackageManager .PERMISSION_GRANTED
180117 ) {
181- fetchWeatherByCity(DataStore .manualWeatherCity.ifEmpty { " Tokyo" } )
118+ fetchWeatherByCity(" Tokyo" )
182119 return
183120 }
184121
185- val cancellationTokenSource = CancellationTokenSource ()
186- fusedClient.getCurrentLocation(Priority .PRIORITY_HIGH_ACCURACY , cancellationTokenSource.token)
187- .addOnSuccessListener { location: Location ? ->
188- if (location != null ) {
189- fetchWeatherByCoords(location.latitude, location.longitude)
190- } else {
191- fusedClient.lastLocation.addOnSuccessListener { lastLoc ->
192- if (lastLoc != null ) {
193- fetchWeatherByCoords(lastLoc.latitude, lastLoc.longitude)
194- } else {
195- fetchWeatherByCity(" Tokyo" )
196- }
197- }
198- }
199- }
200- .addOnFailureListener {
201- fetchWeatherByCity(" Tokyo" )
122+ fusedClient.requestLocationUpdates(locationRequest, object : LocationCallback () {
123+ override fun onLocationResult (result : LocationResult ) {
124+ fusedClient.removeLocationUpdates(this )
125+ val loc = result.lastLocation
126+ if (loc != null ) fetchWeatherByCoords(loc.latitude, loc.longitude)
127+ else fetchWeatherByCity(" Tokyo" )
202128 }
129+ }, Looper .getMainLooper())
203130 }
204131
205132 private fun fetchWeatherByCoords (lat : Double , lon : Double ) {
206133 coroutineScope.launch(Dispatchers .IO ) {
207134 try {
208135 val now = System .currentTimeMillis()
209- val dist = FloatArray (1 )
210- Location .distanceBetween(lastLat, lastLon, lat, lon, dist)
211- val distanceChanged = dist[0 ] > minDistanceChange
212-
213- if ((now - lastWeatherTime) < weatherInterval && ! distanceChanged && cachedCode != - 1 ) {
214- withContext(Dispatchers .Main ) { refreshGreetingText() }
136+ if ((now - lastWeatherTime) < weatherInterval && lastWeatherText != null ) {
137+ withContext(Dispatchers .Main ) { updateGreeting(lastWeatherText!! ) }
215138 return @launch
216139 }
217140
218- var locationName = " "
219- try {
220- val geocoder = Geocoder (context, getAppLocale())
221- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
222- geocoder.getFromLocation(lat, lon, 1 ) { addresses ->
223- if (addresses.isNotEmpty()) {
224- locationName = addresses[0 ].subLocality ? : addresses[0 ].locality ? : " "
225- }
226- }
227- } else {
228- @Suppress(" DEPRECATION" )
229- val addresses = geocoder.getFromLocation(lat, lon, 1 )
230- if (! addresses.isNullOrEmpty()) {
231- locationName = addresses[0 ].subLocality ? : addresses[0 ].locality ? : " "
232- }
233- }
234- } catch (e: Exception ) {
235- e.printStackTrace()
236- }
141+ val geoUrl = " https://geocoding-api.open-meteo.com/v1/reverse?latitude=$lat &longitude=$lon "
142+ val geoResponse = URL (geoUrl).readText()
143+ val geoJson = JSONObject (geoResponse)
144+ val cityName = geoJson.optJSONArray(" results" )
145+ ?.optJSONObject(0 )?.optString(" name" ) ? : " Your location"
237146
238147 val response = URL (
239- " https://api.open-meteo.com/v1/forecast?latitude=$lat &longitude=$lon ¤t_weather=true&timezone=auto "
148+ " https://api.open-meteo.com/v1/forecast?latitude=$lat &longitude=$lon ¤t_weather=true"
240149 ).readText()
241-
242150 val current = JSONObject (response).getJSONObject(" current_weather" )
243151 val temp = current.getDouble(" temperature" ).roundToInt()
244152 val code = current.getInt(" weathercode" )
245153
246- withContext( Dispatchers . Main ) {
247- saveCache(now, lat, lon, temp, code, locationName )
248- refreshGreetingText()
249- }
154+ val condition = getLocalizedCondition(code)
155+ val emoji = getWeatherEmoji( code)
156+ val prefix = context.getString( R .string.weather_today)
157+ val weatherText = " $prefix $condition $emoji , $temp °C ( $cityName ) "
250158
159+ lastWeatherText = weatherText
160+ lastWeatherTime = now
161+ withContext(Dispatchers .Main ) { updateGreeting(weatherText) }
251162 } catch (e: Exception ) {
252163 e.printStackTrace()
253- if (cachedCode != - 1 ) {
254- withContext(Dispatchers .Main ) { refreshGreetingText() }
255- }
256164 }
257165 }
258166 }
259167
260168 private fun fetchWeatherByCity (city : String ) {
261169 coroutineScope.launch(Dispatchers .IO ) {
262170 try {
263- val geoResponse = URL (" https://geocoding-api.open-meteo.com/v1/search?name=$city &count=1" ).readText()
171+ val encoded = URLEncoder .encode(city, " UTF-8" )
172+ val geoResponse = URL (" https://geocoding-api.open-meteo.com/v1/search?name=$encoded " ).readText()
264173 val geoJson = JSONObject (geoResponse)
265174 val results = geoJson.optJSONArray(" results" ) ? : return @launch
266-
267- if (results.length() > 0 ) {
268- val first = results.getJSONObject(0 )
269- val lat = first.getDouble(" latitude" )
270- val lon = first.getDouble(" longitude" )
271- fetchWeatherByCoords(lat, lon)
272- }
175+ if (results.length() == 0 ) return @launch
176+
177+ val first = results.getJSONObject(0 )
178+ val lat = first.getDouble(" latitude" )
179+ val lon = first.getDouble(" longitude" )
180+ val cityName = first.optString(" name" , city)
181+
182+ val response = URL (
183+ " https://api.open-meteo.com/v1/forecast?latitude=$lat &longitude=$lon ¤t_weather=true"
184+ ).readText()
185+ val current = JSONObject (response).getJSONObject(" current_weather" )
186+ val temp = current.getDouble(" temperature" ).roundToInt()
187+ val code = current.getInt(" weathercode" )
188+
189+ val condition = getLocalizedCondition(code)
190+ val emoji = getWeatherEmoji(code)
191+ val prefix = context.getString(R .string.weather_today)
192+ val weatherText = " $prefix $condition $emoji , $temp °C — $cityName "
193+
194+ lastWeatherText = weatherText
195+ lastWeatherTime = System .currentTimeMillis()
196+ withContext(Dispatchers .Main ) { updateGreeting(weatherText) }
273197 } catch (e: Exception ) {
274198 e.printStackTrace()
275199 }
@@ -314,4 +238,4 @@ class Greetings @JvmOverloads constructor(
314238 }
315239 return context.getString(resId)
316240 }
317- }
241+ }
0 commit comments