This commit is contained in:
Andrey Kassaev 2024-04-01 01:14:09 +04:00
commit 4511338576
80 changed files with 1877 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\79967\.android\avd\Pixel_8_Pro_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-03-31T20:42:32.914574800Z" />
</State>
</entry>
</value>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,41 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/other.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ScreenshotViewer">
<option name="frameScreenshot" value="true" />
</component>
</project>

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# Weather Widget
Android weather forecast application with widget. Tracks user location and update weather every 5 min.
Currently in russian. [Click here to get apk][1]
Built with:
Jetpack Compose
Jetpack Glance
Foreground Service
Retrofit
Hilt
https://www.weatherapi.com/ as Weather Forecast Provider
On initial start GPS and Internet must be ON and permission granted.
<img src="./assets/main_screen.png" width="200">
<img src="./assets/foreground_service.png" width="200">
<img src="./assets/activity.png" width="200">
[1]: <https://kassaev.com/media/weatherWidget.apk>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

94
app/build.gradle.kts Normal file
View File

@ -0,0 +1,94 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
kotlin("kapt")
id("com.google.dagger.hilt.android")
}
android {
namespace = "com.kassaev.weatherwidget"
compileSdk = 34
defaultConfig {
applicationId = "com.kassaev.weatherwidget"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
// For AppWidgets support
implementation("androidx.glance:glance-appwidget:1.0.0")
// For interop APIs with Material 3
implementation("androidx.glance:glance-material3:1.0.0")
//hilt
val hilt_version = "2.51"
implementation("com.google.dagger:hilt-android:$hilt_version")
kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
//retrofit
val retrofit_version = "2.10.0"
implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
implementation("com.squareup.retrofit2:converter-gson:2.10.0")
//location tracking
implementation("com.google.android.gms:play-services-location:21.2.0")
}
kapt {
correctErrorTypes = true
}
}
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.kassaev.weatherwidget",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 26
}

Binary file not shown.

View File

@ -0,0 +1,27 @@
package com.kassaev.weatherwidget
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals(
"com.kassaev.weatherwidget",
appContext.packageName
)
}
}

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<application
android:name=".di.App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.WeatherWidget"
tools:targetApi="31">
<service android:name=".WeatherForegroundService" android:foregroundServiceType="location"
tools:ignore="ForegroundServicePermission" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.WeatherWidget"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".WeatherWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/weather_widget"/>
</receiver>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,17 @@
package com.kassaev.weatherwidget
import android.content.Context
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.pm.PackageManager
fun Context.hasLocationPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}

View File

@ -0,0 +1,74 @@
package com.kassaev.weatherwidget
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.location.LocationManager
import android.os.Looper
import androidx.core.app.PendingIntentCompat.send
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
class DefaultLocationClient(
private val context: Context,
private val client: FusedLocationProviderClient
): LocationClient {
private val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
fun isGpsEnabled(): Boolean =
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
@SuppressLint("MissingPermission")
override fun getLocationUpdates(interval: Long): Flow<Location> {
return callbackFlow {
if(!context.hasLocationPermission()){
throw LocationClient.LocationException("Permission not granted")
}
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
if(!isGpsEnabled && !isNetworkEnabled){
throw LocationClient.LocationException("GPS is disabled")
}
val request = LocationRequest.create()
.setInterval(interval)
// .setFastestInterval(interval)
val locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
super.onLocationResult(result)
println("CALLBACK CALLED")
result.locations.lastOrNull()?.let { location ->
launch {
println("SEND ${location.latitude}, ${location.longitude}")
send(location)
}
}
}
}
client.requestLocationUpdates(
request,
locationCallback,
Looper.getMainLooper()
)
awaitClose {
client.removeLocationUpdates(locationCallback)
}
}
}
}

View File

@ -0,0 +1,10 @@
package com.kassaev.weatherwidget
import android.location.Location
import kotlinx.coroutines.flow.Flow
interface LocationClient {
fun getLocationUpdates(interval: Long): Flow<Location>
class LocationException(message: String): Exception()
}

View File

@ -0,0 +1,24 @@
package com.kassaev.weatherwidget
import android.content.Context
import android.location.LocationManager
import android.location.LocationProvider
import android.os.Build
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class LocationObserver(context: Context) {
private val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
fun observe(): Flow<Boolean> =
flow {
while (true){
emit(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
delay(1000 * 5)
}
}
}

View File

@ -0,0 +1,82 @@
package com.kassaev.weatherwidget
import android.Manifest
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import com.kassaev.weatherwidget.ui.theme.WeatherWidgetTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject lateinit var networkObserver: NetworkObserver
@Inject lateinit var locationObserver: LocationObserver
@Inject lateinit var viewModel: WeatherViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
),
0
)
Intent(
applicationContext,
WeatherForegroundService::class.java
).apply {
action = Actions.START.toString()
setPackage(packageName)
startService(this)
}
setContent {
WeatherWidgetTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val networkStatus by networkObserver.observe().collectAsState(initial = false)
val locationStatus by locationObserver.observe().collectAsState(initial = false)
Column(
modifier = Modifier
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Network is " + if(networkStatus)"ON" else "OFF"
)
Text(
text = "GPS is " + if(locationStatus)"ON" else "OFF"
)
Text(
text = "${viewModel.currentWeather?.city}"
)
Text(
text = "${viewModel.currentWeather?.temp}"
)
Text(
text = "${viewModel.currentWeather?.desc}"
)
}
}
}
}
}
}

View File

@ -0,0 +1,47 @@
package com.kassaev.weatherwidget
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
class NetworkObserver(context: Context) {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun observe(): Flow<Boolean> =
callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
launch {
send(true)
}
}
override fun onLost(network: Network) {
super.onLost(network)
launch {
send(false)
}
}
override fun onUnavailable() {
super.onUnavailable()
launch {
send(false)
}
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
awaitClose{
connectivityManager.unregisterNetworkCallback(callback)
}
}.distinctUntilChanged()
}

View File

@ -0,0 +1,130 @@
package com.kassaev.weatherwidget
import android.app.Notification
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.state.updateAppWidgetState
import androidx.glance.appwidget.updateAll
import com.google.android.gms.location.LocationServices
import com.kassaev.weatherwidget.data.WeatherApiService
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class WeatherForegroundService: Service() {
@Inject lateinit var viewModel: WeatherViewModel
@Inject lateinit var networkObserver: NetworkObserver
var isNetworkEnabled: Boolean = false
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private lateinit var locationClient: LocationClient
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
serviceScope.launch {
networkObserver.observe().collect{
isNetworkEnabled = it
}
}
locationClient = DefaultLocationClient(
applicationContext,
LocationServices.getFusedLocationProviderClient(applicationContext)
)
start()
}
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int
): Int {
when(intent?.action){
Actions.START.toString() -> start()
Actions.STOP.toString() -> stop()
}
return super.onStartCommand(
intent,
flags,
startId
)
}
override fun onDestroy() {
super.onDestroy()
serviceScope.cancel()
}
private fun stop(){
stopForeground(STOP_FOREGROUND_DETACH)
stopSelf()
}
private fun start() {
val interval: Long = 1000 * 60 * 5
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = Notification.Builder(this, "weather_channel")
.setSmallIcon(R.drawable.logo_svg)
.setContentTitle("Weather")
.setOngoing(true)
locationClient
.getLocationUpdates(interval)
.catch { e -> e.printStackTrace() }
.onEach { location ->
val formattedLocationString = "${location.latitude},${location.longitude}"
if (isNetworkEnabled){
Locale.getDefault().language
val currentWeather = serviceScope.async {
WeatherApiService.weatherApi.getWeatherForecast(formattedLocationString)
}.await().toModel()
GlanceAppWidgetManager(applicationContext).getGlanceIds(WeatherWidget::class.java).forEach { glanceId ->
updateAppWidgetState(applicationContext, glanceId){ prefs ->
prefs[stringPreferencesKey("city")] = currentWeather.city
prefs[stringPreferencesKey("temp")] = currentWeather.temp
prefs[stringPreferencesKey("desc")] = currentWeather.desc
prefs[stringPreferencesKey("iconUri")] = currentWeather.iconUri
}
}
val updatedNotification = notification.setContentText(
"${currentWeather.city}, ${currentWeather.temp}℃, ${currentWeather.desc}"
)
notificationManager.notify(
1,
updatedNotification.build()
)
WeatherWidget().updateAll(applicationContext)
viewModel.currentWeather = currentWeather
}
}.launchIn(serviceScope)
startForeground(1, notification.build())
}
}
enum class Actions{
START, STOP
}

View File

@ -0,0 +1,19 @@
package com.kassaev.weatherwidget
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.kassaev.weatherwidget.data.WeatherRepository
import com.kassaev.weatherwidget.domain.WeatherModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class WeatherViewModel @Inject constructor(
private val repository: WeatherRepository
): ViewModel() {
var currentWeather by mutableStateOf<WeatherModel?>(WeatherModel())
}

View File

@ -0,0 +1,106 @@
package com.kassaev.weatherwidget
import android.content.Context
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import androidx.glance.appwidget.action.actionStartService
import androidx.glance.appwidget.appWidgetBackground
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.currentState
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxSize
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import java.io.File
class WeatherWidget: GlanceAppWidget() {
override var stateDefinition = CustomGlanceStateDefinition
override suspend fun provideGlance(
context: Context,
id: GlanceId
) {
provideContent {
val prefs = currentState<Preferences>()
Box(
modifier = GlanceModifier
.appWidgetBackground()
.fillMaxSize()
.background(Color(0xBEFFFFFF))
.clickable(
actionStartService<WeatherForegroundService>(
isForegroundService = true
)
),
contentAlignment = Alignment.Center
) {
if (prefs[stringPreferencesKey("city")] != null){
Column(
verticalAlignment = Alignment.Vertical.CenterVertically,
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
) {
Text(
text = "${prefs[stringPreferencesKey("city")]}"
)
Text(
text = "${prefs[stringPreferencesKey("temp")]}" + " \u2103",
style = TextStyle(
fontSize = 32.sp
)
)
Row {
Text(
text = "${prefs[stringPreferencesKey("desc")]}"
)
}
}
} else {
Text(text = "Tap me!")
}
}
}
}
companion object {
object CustomGlanceStateDefinition : GlanceStateDefinition<Preferences> {
override suspend fun getDataStore(context: Context, fileKey: String): DataStore<Preferences> {
return context.dataStore
}
override fun getLocation(context: Context, fileKey: String): File {
// Note: The Datastore Preference file resides is in the context.applicationContext.filesDir + "datastore/"
return File(context.applicationContext.filesDir, "datastore/$fileName")
}
private const val fileName = "widget_store"
private val Context.dataStore: DataStore<Preferences>
by preferencesDataStore(name = fileName)
}
}
}
class WeatherWidgetReceiver : GlanceAppWidgetReceiver(){
override val glanceAppWidget: GlanceAppWidget
get() = WeatherWidget()
}

View File

@ -0,0 +1,9 @@
package com.kassaev.weatherwidget.data
import retrofit2.http.GET
import retrofit2.http.Query
interface IWeatherApi {
@GET("current.json?key=5a5667212db44b7c8f195312242603&aqi=no&lang=ru")
suspend fun getWeatherForecast(@Query("q") location: String): WeatherEntity
}

View File

@ -0,0 +1,13 @@
package com.kassaev.weatherwidget.data
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object WeatherApiService {
val baseUri = "https://api.weatherapi.com/v1/"
val retrofit = Retrofit.Builder()
.baseUrl(baseUri)
.addConverterFactory(GsonConverterFactory.create())
.build()
val weatherApi = retrofit.create(IWeatherApi::class.java)
}

View File

@ -0,0 +1,60 @@
package com.kassaev.weatherwidget.data
import com.kassaev.weatherwidget.domain.WeatherModel
data class WeatherEntity(
val location: Location,
val current: Current,
) {
fun toModel(): WeatherModel =
WeatherModel(
city = this.location.name,
temp = this.current.temp_c.toInt().toString(),
desc = this.current.condition.text,
iconUri = this.current.condition.icon
)
}
data class Location(
val name: String,
val region: String,
val country: String,
val lat: Double,
val lon: Double,
val tz_id: String,
val localtime_epoch: Long,
val localtime: String
)
data class Current(
val last_updated_epoch: Long,
val last_updated: String,
val temp_c: Double,
val temp_f: Double,
val is_day: Int,
val condition: Condition,
val wind_mph: Double,
val wind_kph: Double,
val wind_degree: Int,
val wind_dir: String,
val pressure_mb: Double,
val pressure_in: Double,
val precip_mm: Double,
val precip_in: Double,
val humidity: Int,
val cloud: Double,
val feelslike_c: Double,
val feelslike_f: Double,
val vis_km: Double,
val vis_miles: Double,
val uv: Double,
val gust_mph: Double,
val gust_kph: Double
)
data class Condition(
val text: String,
val icon: String,
val code: Int
)

View File

@ -0,0 +1,4 @@
package com.kassaev.weatherwidget.data
class WeatherRepository {
}

View File

@ -0,0 +1,28 @@
package com.kassaev.weatherwidget.di
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App: Application() {
override fun onCreate() {
super.onCreate()
context = applicationContext
val channel = NotificationChannel(
"weather_channel",
"Weather",
NotificationManager.IMPORTANCE_LOW
)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
companion object {
lateinit var context: Context
}
}

View File

@ -0,0 +1,34 @@
package com.kassaev.weatherwidget.di
import android.content.Context
import com.kassaev.weatherwidget.LocationObserver
import com.kassaev.weatherwidget.NetworkObserver
import com.kassaev.weatherwidget.WeatherViewModel
import com.kassaev.weatherwidget.data.WeatherRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object Module {
@Provides
@Singleton
fun provideViewModel(repository: WeatherRepository): WeatherViewModel = WeatherViewModel(repository)
@Provides
@Singleton
fun provideRepository(): WeatherRepository = WeatherRepository()
@Provides
@Singleton
fun provideNetworkObserver(@ApplicationContext context: Context): NetworkObserver = NetworkObserver(context)
@Provides
@Singleton
fun provideLocationObserver(@ApplicationContext context: Context): LocationObserver = LocationObserver(context)
}

View File

@ -0,0 +1,15 @@
package com.kassaev.weatherwidget.domain
data class WeatherModel(
val city: String,
val temp: String,
val desc: String,
val iconUri: String
) {
constructor(): this(
city = "",
temp = "",
desc = "",
iconUri = ""
)
}

View File

@ -0,0 +1,11 @@
package com.kassaev.weatherwidget.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,73 @@
package com.kassaev.weatherwidget.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun WeatherWidgetTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(
window,
view
).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.kassaev.weatherwidget.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,130 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M256,256m-256,0a256,256 0,1 1,512 0a256,256 0,1 1,-512 0"
android:fillColor="#B5AADD"
android:fillAlpha="0.1"/>
<group>
<clip-path
android:pathData="M69,83h427v347h-427z"/>
<group>
<clip-path
android:pathData="M393.26,108.7H69V430H393.26V108.7Z"/>
<path
android:strokeWidth="1"
android:pathData="M385.27,110.31H70.61V428.39H385.27V110.31Z"
android:strokeAlpha="0.0229128"
android:fillColor="#00000000"
android:strokeColor="#2B2B2B"
android:fillAlpha="0.0229128"/>
<group>
<clip-path
android:pathData="M393.26,160.11H69V375.38H393.26V160.11Z"/>
<path
android:pathData="M176.53,220.51C202.56,220.53 223.62,241.52 223.6,267.37C223.58,293.23 202.49,314.19 176.46,314.17C150.43,314.15 129.37,293.16 129.39,267.3C129.41,241.45 150.51,220.49 176.53,220.51Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M148.68,366.73L148.68,366.73C145.84,365.97 144.17,363.07 144.93,360.27L143.6,359.91L144.93,360.27L153,330.43C153,330.43 153,330.43 153,330.43C153.76,327.62 156.67,325.95 159.51,326.7C161.89,327.33 163.44,329.47 163.44,331.8C163.44,332.25 163.39,332.7 163.26,333.17C163.26,333.17 163.26,333.17 163.26,333.17L155.2,363C154.44,365.81 151.53,367.49 148.68,366.73Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M127.97,323.42L127.97,323.42C129.11,322.38 129.68,320.97 129.68,319.55C129.68,318.27 129.22,316.98 128.28,315.98L128.27,315.97C126.29,313.83 122.93,313.69 120.76,315.67C120.76,315.67 120.76,315.67 120.76,315.67L97.91,336.6L127.97,323.42ZM127.97,323.42L105.12,344.36C105.12,344.36 105.12,344.36 105.12,344.36C102.96,346.34 99.59,346.2 97.61,344.05L97.61,344.05M127.97,323.42L97.61,344.05M97.61,344.05C95.62,341.91 95.75,338.58 97.91,336.6L97.61,344.05Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<group>
<clip-path
android:pathData="M111.21,299.65H70.5V281.36H111.21V299.65Z"/>
<path
android:pathData="M107.23,291.74L107.23,291.74L77.15,299.48L77.15,299.48C74.3,300.21 71.4,298.51 70.67,295.69C69.94,292.88 71.64,290 74.49,289.26L104.57,281.53L104.57,281.53C107.42,280.8 110.31,282.5 111.04,285.32L111.04,285.33C111.16,285.77 111.21,286.21 111.21,286.65C111.21,288.99 109.63,291.13 107.23,291.74Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
</group>
<path
android:pathData="M112,250L112,250.01C111.27,252.82 108.38,254.53 105.53,253.8L75.43,246.1L75.43,246.1C72.59,245.37 70.89,242.48 71.62,239.67C72.34,236.86 75.24,235.15 78.09,235.88C78.09,235.88 78.09,235.88 78.09,235.88L108.18,243.58C110.58,244.2 112.17,246.34 112.17,248.69C112.17,249.12 112.12,249.56 112,250Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M128.02,219.36L128.02,219.35C129.02,218.34 129.51,217.02 129.51,215.7C129.51,214.31 128.98,212.94 127.9,211.9L127.89,211.9L105.58,190.41C105.58,190.41 105.58,190.41 105.58,190.41C103.47,188.38 100.1,188.43 98.06,190.53C96.02,192.63 96.07,195.96 98.18,197.98L98.18,197.98L120.49,219.48L128.02,219.36ZM128.02,219.36C125.98,221.45 122.61,221.51 120.5,219.48L128.02,219.36Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M157.15,201.96L157.15,201.96C154.3,202.68 151.41,200.96 150.7,198.13L150.7,198.13L143.11,168.17C143.11,168.17 143.11,168.17 143.11,168.17C142.4,165.35 144.11,162.49 146.96,161.77C149.82,161.06 152.7,162.79 153.41,165.6L153.41,165.6L161.01,195.55C161.01,195.55 161.01,195.55 161.01,195.56C161.12,196 161.17,196.43 161.17,196.86C161.17,199.21 159.57,201.36 157.15,201.96Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M198.72,200.33L198.72,200.33L206.61,170.45C206.61,170.45 206.61,170.45 206.61,170.45C206.73,169.99 206.79,169.54 206.79,169.1C206.79,166.77 205.22,164.63 202.83,164L198.72,200.33ZM198.72,200.33C197.98,203.14 195.07,204.83 192.22,204.09C189.38,203.34 187.69,200.45 188.43,197.65L188.43,197.65M198.72,200.33L188.43,197.65M188.43,197.65L196.33,167.76M188.43,197.65L196.33,167.76M196.33,167.76C196.33,167.76 196.33,167.76 196.33,167.76M196.33,167.76L196.33,167.76M196.33,167.76C197.07,164.96 199.98,163.26 202.83,164L196.33,167.76Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M254.38,198.88L254.38,198.88L230.93,219.16C228.72,221.07 225.36,220.84 223.43,218.64L223.43,218.64C221.51,216.44 221.74,213.12 223.96,211.2C223.96,211.2 223.96,211.2 223.96,211.2L247.4,190.92L247.4,190.92C249.61,189.01 252.97,189.24 254.9,191.44L254.9,191.45C255.78,192.44 256.2,193.68 256.2,194.9C256.2,196.37 255.59,197.83 254.38,198.88Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M246.5,253.37L246.49,253.37C243.65,254.15 240.73,252.49 239.96,249.68L239.96,249.68C239.19,246.88 240.84,243.98 243.68,243.2C243.68,243.2 243.68,243.2 243.68,243.2L273.64,235.03L273.64,235.03C276.48,234.25 279.4,235.91 280.17,238.72C280.3,239.19 280.36,239.66 280.36,240.12C280.36,242.43 278.82,244.56 276.45,245.2C276.45,245.2 276.45,245.2 276.45,245.2L246.5,253.37Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M279.68,294.26L279.68,294.26C278.95,297.08 276.06,298.78 273.21,298.06C273.21,298.06 273.21,298.06 273.21,298.06L243.11,290.35L243.11,290.35C240.27,289.63 238.56,286.75 239.29,283.93L239.29,283.93C240.02,281.12 242.91,279.41 245.77,280.14C245.77,280.14 245.77,280.14 245.77,280.14L275.86,287.84C275.86,287.84 275.86,287.84 275.86,287.84C278.26,288.46 279.85,290.6 279.85,292.95C279.85,293.38 279.8,293.82 279.68,294.26Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M252.81,343.23L252.81,343.23C250.71,345.28 247.34,345.24 245.29,343.15L245.28,343.15L223.56,321.06C223.56,321.06 223.56,321.06 223.56,321.06C221.51,318.98 221.55,315.64 223.65,313.61L223.65,313.61C225.75,311.56 229.12,311.6 231.17,313.69L231.17,313.69L252.89,335.78L252.89,335.78C253.9,336.81 254.4,338.14 254.4,339.47C254.4,340.84 253.87,342.2 252.81,343.23Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<path
android:pathData="M197.04,365.82L197.04,365.82L188.95,335.99C188.95,335.99 188.95,335.99 188.95,335.99C188.2,333.18 189.86,330.29 192.7,329.52C195.55,328.76 198.46,330.44 199.22,333.25L199.22,333.25L207.3,363.08L207.3,363.08L207.3,363.09C207.43,363.54 207.49,364 207.49,364.46C207.49,366.78 205.93,368.91 203.56,369.54C200.72,370.3 197.8,368.63 197.04,365.82Z"
android:strokeWidth="3"
android:fillColor="#F9C900"
android:strokeColor="#ffffff"/>
<group>
<clip-path
android:pathData="M128,375.63H388.25V227H128V375.63Z"
android:fillType="evenOdd"/>
<path
android:pathData="M317.33,280.76L318.46,283.96L321.69,282.93L324.85,281.92C324.85,281.92 324.86,281.92 324.86,281.91C329.38,280.48 334.07,279.75 338.78,279.75C364.12,279.75 384.75,300.46 384.75,325.94C384.75,351.42 364.12,372.13 338.78,372.13H167.41C147.63,372.13 131.5,355.95 131.5,336.05C131.5,316.14 147.63,299.97 167.41,299.97C168.7,299.97 170.13,300.07 171.96,300.3L171.97,300.3L175.47,300.73L179.02,301.17L179.38,297.61L179.74,294.08C183.4,257.82 213.54,230.5 249.86,230.5C279.55,230.5 306.22,249.42 316.2,277.61L316.21,277.61L317.33,280.76Z"
android:strokeWidth="7"
android:fillColor="#D6E3F2"
android:strokeColor="#ffffff"/>
</group>
</group>
</group>
<group>
<clip-path
android:pathData="M496,83H174.95V404.3H496V83Z"/>
<path
android:strokeWidth="1"
android:pathData="M494.39,84.61H176.55V402.69H494.39V84.61Z"
android:strokeAlpha="0.0229128"
android:fillColor="#00000000"
android:strokeColor="#2B2B2B"
android:fillAlpha="0.0229128"/>
<group>
<clip-path
android:pathData="M437.73,254.13C437.26,252.28 436.09,250.72 434.43,249.75C432.79,248.77 430.84,248.5 428.97,248.98L382.23,261.51L351.32,243.65L382.23,225.78L429.08,238.34C432.94,239.23 436.73,236.98 437.72,233.23C438.23,231.36 437.98,229.41 437,227.73C436.03,226.06 434.49,224.88 432.66,224.42L399.85,215.61L431.73,197.21C435.11,195.15 436.23,190.86 434.27,187.44C432.31,184 427.89,182.8 424.45,184.77L392.67,203.16L401.47,170.3C401.95,168.46 401.67,166.46 400.7,164.82C399.73,163.16 398.17,161.99 396.32,161.52C392.56,160.55 388.64,162.83 387.58,166.61L375.07,213.34L344.15,231.2V195.47L378.36,161.24C381.1,158.45 381.09,153.91 378.34,151.1C376.99,149.74 375.18,148.98 373.25,148.98C371.33,148.98 369.52,149.74 368.17,151.09L344.15,175.13V138.37C344.15,134.42 340.92,131.19 336.95,131.19C332.99,131.19 329.78,134.42 329.78,138.37V175.13L305.77,151.1C304.41,149.74 302.6,148.98 300.67,148.98C298.76,148.98 296.94,149.74 295.57,151.12C292.83,153.91 292.85,158.46 295.59,161.26L329.78,195.47V231.2L298.86,213.34L286.33,166.57C285.29,162.82 281.37,160.55 277.61,161.52C273.77,162.5 271.47,166.45 272.45,170.33L281.26,203.17L249.43,184.76C245.99,182.8 241.68,183.97 239.62,187.44C237.66,190.89 238.82,195.2 242.28,197.25L274.08,215.61L241.34,224.4C239.48,224.87 237.92,226.03 236.95,227.68C235.97,229.33 235.69,231.36 236.19,233.25C237.06,236.37 239.9,238.54 243.11,238.54C243.71,238.54 244.31,238.46 244.99,238.3L291.66,225.78L322.6,243.65L291.69,261.51L244.96,248.99C243.12,248.5 241.12,248.78 239.47,249.76C237.85,250.72 236.66,252.29 236.22,254.05C235.7,255.9 235.95,257.85 236.91,259.53C237.87,261.19 239.45,262.41 241.27,262.89L274.08,271.69L242.24,290.08C238.82,292.1 237.66,296.5 239.66,299.86C240.95,302.12 243.28,303.47 245.9,303.47C247.15,303.47 248.39,303.15 249.48,302.52L281.29,284.13L272.5,316.95C271.99,318.79 272.24,320.74 273.21,322.42C274.16,324.07 275.69,325.25 277.48,325.74C278.1,325.93 278.74,326.02 279.43,326.02C282.61,326.02 285.47,323.83 286.38,320.69L298.86,273.95L329.78,256.09V291.83L295.62,326.03C292.82,328.84 292.82,333.4 295.62,336.2C296.98,337.56 298.78,338.31 300.72,338.31C302.64,338.31 304.44,337.56 305.79,336.2L329.78,312.17V348.93C329.78,352.89 333.01,356.1 336.98,356.1C340.93,356.1 344.15,352.89 344.15,348.93V312.17L368.15,336.19C369.51,337.56 371.32,338.31 373.23,338.31C375.18,338.31 376.99,337.56 378.33,336.2C381.13,333.4 381.13,328.84 378.33,326.03L344.15,291.83V256.1L375.07,273.99L387.57,320.74C388.45,323.85 391.29,326.02 394.5,326.02C395.18,326.02 395.82,325.93 396.42,325.75C398.24,325.26 399.77,324.08 400.72,322.45C401.68,320.79 401.94,318.79 401.44,316.97L392.64,284.16L424.48,302.55C425.58,303.17 426.81,303.49 428.03,303.49C430.62,303.49 432.95,302.14 434.26,299.89C436.23,296.5 435.06,292.09 431.64,290.05L399.85,271.69L432.63,262.89C434.49,262.4 436.05,261.21 437.02,259.53C437.97,257.88 438.23,255.9 437.73,254.13Z"
android:fillType="evenOdd"/>
<path
android:pathData="M437.73,254.13C437.26,252.28 436.09,250.72 434.43,249.75C432.79,248.77 430.84,248.5 428.97,248.98L382.23,261.51L351.32,243.65L382.23,225.78L429.08,238.34C432.94,239.23 436.73,236.98 437.72,233.23C438.23,231.36 437.98,229.41 437,227.73C436.03,226.06 434.49,224.88 432.66,224.42L399.85,215.61L431.73,197.21C435.11,195.15 436.23,190.86 434.27,187.44C432.31,184 427.89,182.8 424.45,184.77L392.67,203.16L401.47,170.3C401.95,168.46 401.67,166.46 400.7,164.82C399.73,163.16 398.17,161.99 396.32,161.52C392.56,160.55 388.64,162.83 387.58,166.61L375.07,213.34L344.15,231.2V195.47L378.36,161.24C381.1,158.45 381.09,153.91 378.34,151.1C376.99,149.74 375.18,148.98 373.25,148.98C371.33,148.98 369.52,149.74 368.17,151.09L344.15,175.13V138.37C344.15,134.42 340.92,131.19 336.95,131.19C332.99,131.19 329.78,134.42 329.78,138.37V175.13L305.77,151.1C304.41,149.74 302.6,148.98 300.67,148.98C298.76,148.98 296.94,149.74 295.57,151.12C292.83,153.91 292.85,158.46 295.59,161.26L329.78,195.47V231.2L298.86,213.34L286.33,166.57C285.29,162.82 281.37,160.55 277.61,161.52C273.77,162.5 271.47,166.45 272.45,170.33L281.26,203.17L249.43,184.76C245.99,182.8 241.68,183.97 239.62,187.44C237.66,190.89 238.82,195.2 242.28,197.25L274.08,215.61L241.34,224.4C239.48,224.87 237.92,226.03 236.95,227.68C235.97,229.33 235.69,231.36 236.19,233.25C237.06,236.37 239.9,238.54 243.11,238.54C243.71,238.54 244.31,238.46 244.99,238.3L291.66,225.78L322.6,243.65L291.69,261.51L244.96,248.99C243.12,248.5 241.12,248.78 239.47,249.76C237.85,250.72 236.66,252.29 236.22,254.05C235.7,255.9 235.95,257.85 236.91,259.53C237.87,261.19 239.45,262.41 241.27,262.89L274.08,271.69L242.24,290.08C238.82,292.1 237.66,296.5 239.66,299.86C240.95,302.12 243.28,303.47 245.9,303.47C247.15,303.47 248.39,303.15 249.48,302.52L281.29,284.13L272.5,316.95C271.99,318.79 272.24,320.74 273.21,322.42C274.16,324.07 275.69,325.25 277.48,325.74C278.1,325.93 278.74,326.02 279.43,326.02C282.61,326.02 285.47,323.83 286.38,320.69L298.86,273.95L329.78,256.09V291.83L295.62,326.03C292.82,328.84 292.82,333.4 295.62,336.2C296.98,337.56 298.78,338.31 300.72,338.31C302.64,338.31 304.44,337.56 305.79,336.2L329.78,312.17V348.93C329.78,352.89 333.01,356.1 336.98,356.1C340.93,356.1 344.15,352.89 344.15,348.93V312.17L368.15,336.19C369.51,337.56 371.32,338.31 373.23,338.31C375.18,338.31 376.99,337.56 378.33,336.2C381.13,333.4 381.13,328.84 378.33,326.03L344.15,291.83V256.1L375.07,273.99L387.57,320.74C388.45,323.85 391.29,326.02 394.5,326.02C395.18,326.02 395.82,325.93 396.42,325.75C398.24,325.26 399.77,324.08 400.72,322.45C401.68,320.79 401.94,318.79 401.44,316.97L392.64,284.16L424.48,302.55C425.58,303.17 426.81,303.49 428.03,303.49C430.62,303.49 432.95,302.14 434.26,299.89C436.23,296.5 435.06,292.09 431.64,290.05L399.85,271.69L432.63,262.89C434.49,262.4 436.05,261.21 437.02,259.53C437.97,257.88 438.23,255.9 437.73,254.13Z"
android:strokeWidth="6"
android:fillColor="#4FB4ED"
android:fillType="evenOdd"
android:strokeColor="#ffffff"/>
</group>
</group>
</group>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#B5AADD</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">WeatherWidget</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.WeatherWidget" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:previewImage="@mipmap/ic_launcher"
android:initialLayout="@layout/init_widget_layout"
android:resizeMode="none"
android:minHeight="50dp"
android:minWidth="100dp">
</appwidget-provider>

View File

@ -0,0 +1,20 @@
package com.kassaev.weatherwidget
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(
4,
2 + 2
)
}
}

BIN
assets/activity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
assets/main_screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

6
build.gradle.kts Normal file
View File

@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
id("com.google.dagger.hilt.android") version "2.51" apply false
}

23
gradle.properties Normal file
View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

31
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,31 @@
[versions]
agp = "8.3.0"
kotlin = "1.9.0"
coreKtx = "1.12.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.8.2"
composeBom = "2023.08.00"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sun Mar 24 18:20:59 SAMT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

24
settings.gradle.kts Normal file
View File

@ -0,0 +1,24 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "WeatherWidget"
include(":app")