Refactor: More widget stuff and fixing of files.

Signed-off-by: HeCodes2Much <wayne6324@gmail.com>
This commit is contained in:
HeCodes2Much
2024-06-07 20:03:19 +01:00
parent 3b4af4e231
commit 327f1447a7
38 changed files with 976 additions and 422 deletions

View File

@@ -39,11 +39,18 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean && ./gradlew assembleDebug
run: ./gradlew clean && ./gradlew assembleAppWithInternetDebug && ./gradlew assembleAppWithoutInternetDebug
- name: Upload Artifact
- name: Upload Artifact - WithInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle
path: app/build/outputs/apk/debug/*.apk
name: Signed app bundle - WithInternet
path: app/build/outputs/apk/withInternet/debug/*.apk
retention-days: 3
- name: Upload Artifact - WithoutInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle - WithoutInternet
path: app/build/outputs/apk/withoutInternet/debug/*.apk
retention-days: 3

View File

@@ -37,11 +37,18 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean && ./gradlew assembleDebug
run: ./gradlew clean && ./gradlew assembleAppWithInternetDebug && ./gradlew assembleAppWithoutInternetDebug
- name: Upload Artifact
- name: Upload Artifact - WithInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle
path: app/build/outputs/apk/debug/*.apk
name: Signed app bundle - WithInternet
path: app/build/outputs/apk/withInternet/debug/*.apk
retention-days: 3
- name: Upload Artifact - WithoutInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle - WithoutInternet
path: app/build/outputs/apk/withoutInternet/debug/*.apk
retention-days: 3

View File

@@ -37,11 +37,18 @@ jobs:
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean && ./gradlew assembleDebug
run: ./gradlew clean && ./gradlew assembleAppWithInternetDebug && ./gradlew assembleAppWithoutInternetDebug
- name: Upload Artifact
- name: Upload Artifact - WithInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle
path: app/build/outputs/apk/debug/*.apk
name: Signed app bundle - WithInternet
path: app/build/outputs/apk/withInternet/debug/*.apk
retention-days: 3
- name: Upload Artifact - WithoutInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle - WithoutInternet
path: app/build/outputs/apk/withoutInternet/debug/*.apk
retention-days: 3

View File

@@ -33,24 +33,43 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build
run: ./gradlew clean && ./gradlew assembleRelease
- name: Build with Gradle
run: ./gradlew clean && ./gradlew assembleAppWithInternetRelease && ./gradlew assembleAppWithoutInternetRelease
- name: Sign APK
- name: Sign APK - WithInternet
uses: ilharp/sign-android-release@v1
# ID used to access action output
id: sign_app
id: sign_app_withInternet
with:
releaseDir: app/build/outputs/apk/release
releaseDir: app/build/outputs/apk/withInternet/release
signingKey: ${{ secrets.SIGNINGKEY_BASE64 }}
keyAlias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
buildToolsVersion: 33.0.0
- name: Upload Artifact
- name: Upload Artifact - WithInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle
path: app/build/outputs/apk/release/*.apk
name: Signed app bundle - WithInternet
path: app/build/outputs/apk/withInternet/release/*.apk
retention-days: 3
- name: Sign APK - WithInternet
uses: ilharp/sign-android-release@v1
# ID used to access action output
id: sign_app_withoutInternet
with:
releaseDir: app/build/outputs/apk/withoutInternet/release
signingKey: ${{ secrets.SIGNINGKEY_BASE64 }}
keyAlias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
buildToolsVersion: 33.0.0
- name: Upload Artifact - WithoutInternet
uses: actions/upload-artifact@v4
with:
name: Signed app bundle - WithoutInternet
path: app/build/outputs/apk/withoutInternet/release/*.apk
retention-days: 3

View File

@@ -35,15 +35,15 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build
run: ./gradlew clean && ./gradlew assembleRelease
- name: Build with Gradle
run: ./gradlew clean && ./gradlew assembleAppWithInternetRelease && ./gradlew assembleAppWithoutInternetRelease
- name: Sign APK
- name: Sign APK - WithInternet
uses: ilharp/sign-android-release@v1
# ID used to access action output
id: sign_app
id: sign_app_withInternet
with:
releaseDir: app/build/outputs/apk/release
releaseDir: app/build/outputs/apk/withInternet/release
signingKey: ${{ secrets.SIGNINGKEY_BASE64 }}
keyAlias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
@@ -54,7 +54,28 @@ jobs:
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GIT_BOT_TOKEN }}
file: ${{steps.sign_app.outputs.signedFile}}
file: ${{steps.sign_app_withInternet.outputs.signedFile}}
asset_name: EasyLauncher-Internet-${{ github.ref_name }}-Signed.apk
tag: ${{ github.ref }}
overwrite: true
- name: Sign APK - WithInternet
uses: ilharp/sign-android-release@v1
# ID used to access action output
id: sign_app_withoutInternet
with:
releaseDir: app/build/outputs/apk/withoutInternet/release
signingKey: ${{ secrets.SIGNINGKEY_BASE64 }}
keyAlias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
buildToolsVersion: 33.0.0
- name: Release to GitHub
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GIT_BOT_TOKEN }}
file: ${{steps.sign_app_withoutInternet.outputs.signedFile}}
asset_name: EasyLauncher-${{ github.ref_name }}-Signed.apk
tag: ${{ github.ref }}
overwrite: true

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -1,3 +1,5 @@
import java.util.Properties
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
@@ -21,6 +23,17 @@ android {
versionName = "0.1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["internetPermission"] = "android.permission.INTERNET"
val weatherFile = project.rootProject.file("weather.properties")
val properties = Properties()
properties.load(weatherFile.inputStream())
val apiKey = properties.getProperty("WEATHER_API_KEY") ?: ""
buildConfigField(
type = "String",
name = "API_KEY",
value = "\"$apiKey\""
)
}
buildTypes {
@@ -65,6 +78,21 @@ android {
buildConfig = true
}
flavorDimensions.add("internet")
productFlavors {
create("withInternet") {
dimension = "internet"
manifestPlaceholders["hasInternetPermission"] = true
manifestPlaceholders["internetPermission"] = "android.permission.INTERNET"
}
create("withoutInternet") {
dimension = "internet"
manifestPlaceholders["hasInternetPermission"] = false
manifestPlaceholders["internetPermission"] = "REMOVE"
}
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
@@ -96,6 +124,8 @@ dependencies {
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.retrofit)
implementation(libs.converter.gson)
implementation(libs.constraintlayout)
implementation(libs.navigation.fragment.ktx)
implementation(libs.navigation.ui.ktx)

View File

@@ -5,6 +5,8 @@
<dist:module dist:instant="true" />
<uses-permission android:name="${internetPermission}" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
@@ -18,6 +20,9 @@
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
@@ -32,7 +37,9 @@
android:icon="@drawable/app_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Launcher">
android:theme="@style/Theme.Launcher"
android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu">
<activity
android:name=".ui.activities.LauncherActivity"
android:enabled="false"

View File

@@ -1,5 +1,6 @@
package com.github.droidworksstudio.common
import android.Manifest
import android.app.SearchManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
@@ -432,6 +433,12 @@ private fun isExternalStorageReadable(): Boolean {
return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state
}
fun Context.hasInternetPermission(): Boolean {
val permission = Manifest.permission.INTERNET
val result = ContextCompat.checkSelfPermission(this, permission)
return result == PackageManager.PERMISSION_GRANTED
}
fun Context.isPackageInstalled(
packageName: String,
userHandle: UserHandle = android.os.Process.myUserHandle()

View File

@@ -9,7 +9,6 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.view.Gravity
import android.view.View
import android.view.Window
@@ -20,11 +19,19 @@ import androidx.navigation.NavOptions
import com.github.droidworksstudio.common.backupSharedPreferences
import com.github.droidworksstudio.common.restoreSharedPreferences
import com.github.droidworksstudio.common.showLongToast
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.github.droidworksstudio.launcher.BuildConfig
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.accessibility.ActionService
import com.github.droidworksstudio.launcher.helper.weather.WeatherResponse
import com.github.droidworksstudio.launcher.utils.WeatherApiService
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.Calendar
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class AppHelper @Inject constructor() {
@@ -64,7 +71,7 @@ class AppHelper @Inject constructor() {
Configuration.UI_MODE_NIGHT_YES -> {
view.setBackgroundColor(
context.resources.getColor(
R.color.blackTrans25,
R.color.blackTrans10,
context.theme
)
)
@@ -73,7 +80,7 @@ class AppHelper @Inject constructor() {
Configuration.UI_MODE_NIGHT_NO -> {
view.setBackgroundColor(
context.resources.getColor(
R.color.whiteTrans25,
R.color.whiteTrans10,
context.theme
)
)
@@ -242,4 +249,73 @@ class AppHelper @Inject constructor() {
}
}
}
suspend fun fetchWeatherData(
context: Context,
latitude: Float,
longitude: Float
): WeatherResponse {
// Check if cached data is available and not expired
val cachedWeatherData = context.getWeatherDataFromCache()
if (cachedWeatherData != null && System.currentTimeMillis() - cachedWeatherData.timestamp < TimeUnit.MINUTES.toMillis(
5
)
) {
return cachedWeatherData.weatherResponse
}
// Fetch weather data from the network
val apiKey = BuildConfig.API_KEY
val units = "metric"
val retrofit = Retrofit.Builder()
.baseUrl("https://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(WeatherApiService::class.java)
return withContext(Dispatchers.IO) {
val response = service.getWeather("$latitude", "$longitude", units, apiKey).execute()
if (response.isSuccessful) {
val weatherResponse = response.body()
if (weatherResponse != null) {
// Cache the fetched weather data
context.cacheWeatherData(weatherResponse)
weatherResponse
} else {
throw NullPointerException("Weather response body is null")
}
} else {
throw Exception("Failed to fetch weather data: ${response.errorBody()}")
}
}
}
// Function to cache weather data
private fun Context.cacheWeatherData(weatherResponse: WeatherResponse) {
val timestamp = System.currentTimeMillis()
val cachedData = CachedWeatherData(timestamp, weatherResponse)
// Save cached data to SharedPreferences
val sharedPreferences = getSharedPreferences(Constants.WEATHER_PREFS, Context.MODE_PRIVATE)
sharedPreferences.edit()
.putLong("cachedDataTimestamp", cachedData.timestamp)
.putString("weatherResponse", Gson().toJson(cachedData.weatherResponse))
.apply()
}
// Function to retrieve weather data from cache
private fun Context.getWeatherDataFromCache(): CachedWeatherData? {
val sharedPreferences = getSharedPreferences("WeatherCache", Context.MODE_PRIVATE)
val timestamp = sharedPreferences.getLong("timestamp", -1)
val weatherResponseJson = sharedPreferences.getString("weatherResponse", null)
if (timestamp != -1L && weatherResponseJson != null) {
val weatherResponse = Gson().fromJson(weatherResponseJson, WeatherResponse::class.java)
return CachedWeatherData(timestamp, weatherResponse)
}
return null
}
// Data class to hold cached weather data along with timestamp
data class CachedWeatherData(val timestamp: Long, val weatherResponse: WeatherResponse)
}

View File

@@ -50,6 +50,14 @@ class PreferenceHelper @Inject constructor(@ApplicationContext context: Context)
get() = prefs.getInt(Constants.DAILY_WORD_COLOR, 0xFFFFFFFF.toInt())
set(value) = prefs.edit().putInt(Constants.DAILY_WORD_COLOR, value).apply()
var widgetBackgroundColor: Int
get() = prefs.getInt(Constants.WIDGET_BACKGROUND_COLOR, 0xFF000000.toInt())
set(value) = prefs.edit().putInt(Constants.WIDGET_BACKGROUND_COLOR, value).apply()
var widgetTextColor: Int
get() = prefs.getInt(Constants.WIDGET_TEXT_COLOR, 0xFFFFFFFF.toInt())
set(value) = prefs.edit().putInt(Constants.WIDGET_TEXT_COLOR, value).apply()
var appColor: Int
get() = prefs.getInt(Constants.APP_COLOR, 0xFFFFFFFF.toInt())
set(value) = prefs.edit().putInt(Constants.APP_COLOR, value).apply()
@@ -145,6 +153,21 @@ class PreferenceHelper @Inject constructor(@ApplicationContext context: Context)
get() = loadAction(Constants.DOUBLE_TAP_ACTION, Constants.Action.LockScreen)
set(value) = storeAction(Constants.DOUBLE_TAP_ACTION, value)
var weatherUnits: Constants.Units
get() {
return try {
Constants.Units.valueOf(
prefs.getString(
Constants.WEATHER_UNITS,
Constants.Units.Metric.name
).toString()
)
} catch (_: Exception) {
Constants.Units.Metric
}
}
set(value) = prefs.edit().putString(Constants.WEATHER_UNITS, value.name).apply()
private fun loadAction(prefString: String, default: Constants.Action): Constants.Action {
val string = prefs.getString(
prefString,

View File

@@ -0,0 +1,26 @@
package com.github.droidworksstudio.launcher.helper.weather
data class WeatherResponse(
val name: String,
val main: Main,
val wind: Wind,
val weather: List<Weather>
)
data class Wind(
val speed: Double,
val deg: Int
)
data class Main(
val temp: Double,
val feels_like: Double,
val temp_min: Double,
val temp_max: Double,
val pressure: Int,
val humidity: Int
)
data class Weather(
val description: String
)

View File

@@ -1,26 +1,42 @@
package com.github.droidworksstudio.launcher.ui.activities
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import com.github.droidworksstudio.common.hasInternetPermission
import com.github.droidworksstudio.common.isTablet
import com.github.droidworksstudio.launcher.BuildConfig
import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding
import com.github.droidworksstudio.launcher.helper.AppHelper
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel
import dagger.hilt.android.AndroidEntryPoint
@@ -29,7 +45,13 @@ import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
class MainActivity : AppCompatActivity(), LocationListener {
private lateinit var locationManager: LocationManager
companion object {
private const val REQUEST_LOCATION_PERMISSION_CODE = 1001
}
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
@@ -45,15 +67,78 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var appHelper: AppHelper
private lateinit var sharedPreferences: SharedPreferences
private lateinit var handler: Handler
private var lastKnownLocation: Location? = null
private val saveLocationRunnable = object : Runnable {
override fun run() {
lastKnownLocation?.let { location ->
saveLocation(location.latitude.toFloat(), location.longitude.toFloat())
}
handler.postDelayed(this, 30000) // Run every 30 seconds
}
}
private fun saveLocation(latitude: Float = 0f, longitude: Float = 0f) {
with(sharedPreferences.edit()) {
putFloat(Constants.LATITUDE, latitude)
putFloat(Constants.LONGITUDE, longitude)
apply()
}
}
@SuppressLint("InlinedApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BuildConfig.FLAVOR", BuildConfig.FLAVOR)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
sharedPreferences = getSharedPreferences(Constants.WEATHER_PREFS, Context.MODE_PRIVATE)
handler = Handler(Looper.getMainLooper())
initializeDependencies()
setupNavController()
setupOrientation()
if (applicationContext.hasInternetPermission()) {
checkLocationPermission()
}
}
override fun onPause() {
super.onPause()
if (applicationContext.hasInternetPermission()) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationManager.removeUpdates(this)
}
handler.removeCallbacks(saveLocationRunnable)
}
}
override fun onDestroy() {
super.onDestroy()
if (applicationContext.hasInternetPermission()) {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationManager.removeUpdates(this)
}
handler.removeCallbacks(saveLocationRunnable)
}
}
private fun initializeDependencies() {
@@ -98,6 +183,74 @@ class MainActivity : AppCompatActivity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
private fun checkLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION_CODE
)
} else {
// Permission already granted, proceed with getting location
getLocation()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_LOCATION_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted, proceed with getting location
getLocation()
} else {
// Permission denied, show a message to the user
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
}
}
}
private fun getLocation() {
try {
// Check if permission is granted
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0,
0f,
this
)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
0,
0f,
this
)
// Start the handler to save location every 30 seconds
handler.post(saveLocationRunnable)
}
} catch (e: SecurityException) {
e.printStackTrace()
}
}
override fun onLocationChanged(location: Location) {
// Get latitude and longitude
val latitude = location.latitude.toFloat()
val longitude = location.longitude.toFloat()
// Save the location data immediately
saveLocation(latitude, longitude)
lastKnownLocation = location
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true

View File

@@ -79,6 +79,16 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() {
text = bottomDialogHelper.getColorText(preferenceHelper.dailyWordColor)
setTextColor(preferenceHelper.dailyWordColor)
}
binding.selectWidgetBackgroundColor.apply {
text = bottomDialogHelper.getColorText(preferenceHelper.widgetBackgroundColor)
setTextColor(preferenceHelper.widgetBackgroundColor)
}
binding.selectWidgetTextColor.apply {
text = bottomDialogHelper.getColorText(preferenceHelper.widgetTextColor)
setTextColor(preferenceHelper.widgetTextColor)
}
}
@@ -122,6 +132,22 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() {
preferenceHelper.dailyWordColor
)
}
binding.bottomColorWidgetBackgroundView.setOnClickListener {
showColorPickerDialog(
binding.selectWidgetBackgroundColor,
REQUEST_KEY_WIDGET_BACKGROUND_COLOR,
preferenceHelper.widgetBackgroundColor
)
}
binding.bottomColorWidgetTextView.setOnClickListener {
showColorPickerDialog(
binding.selectWidgetTextColor,
REQUEST_KEY_WIDGET_TEXT_COLOR,
preferenceHelper.widgetTextColor
)
}
}
private fun showColorPickerDialog(view: View, requestCode: String, color: Int) {
@@ -163,6 +189,16 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() {
preferenceViewModel.setTimeColor(pickedColor)
Log.d("Tag", "Settings Time Color: ${Integer.toHexString(color)}")
}
REQUEST_KEY_WIDGET_BACKGROUND_COLOR -> {
preferenceViewModel.setWidgetBackgroundColor(pickedColor)
Log.d("Tag", "Settings Widget Background Color: ${Integer.toHexString(color)}")
}
REQUEST_KEY_WIDGET_TEXT_COLOR -> {
preferenceViewModel.setWidgetTextColor(pickedColor)
Log.d("Tag", "Settings Widget Text Color: ${Integer.toHexString(color)}")
}
}
}) {
context?.showLongToast("onCancel")
@@ -175,5 +211,7 @@ class ColorBottomSheetDialogFragment : BottomSheetDialogFragment() {
private const val REQUEST_KEY_DAILY_WORD_COLOR = "REQUEST_DAILY_WORD_COLOR"
private const val REQUEST_KEY_APP_COLOR = "REQUEST_APP_COLOR"
private const val REQUEST_KEY_BATTERY_COLOR = "REQUEST_BATTERY_COLOR"
private const val REQUEST_KEY_WIDGET_BACKGROUND_COLOR = "REQUEST_WIDGET_BACKGROUND_COLOR"
private const val REQUEST_KEY_WIDGET_TEXT_COLOR = "REQUEST_WIDGET_TEXT_COLOR"
}
}

View File

@@ -1,58 +0,0 @@
package com.github.droidworksstudio.launcher.ui.widgetmanager
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.recyclerview.widget.RecyclerView
import com.github.droidworksstudio.launcher.R
class WidgetAdapter(
private val context: Context,
private val appWidgetHost: AppWidgetHost,
private val onWidgetClick: (Int) -> Unit
) : RecyclerView.Adapter<WidgetAdapter.WidgetViewHolder>() {
private val widgetItems = mutableListOf<WidgetItem>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WidgetViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_widget, parent, false)
return WidgetViewHolder(itemView)
}
override fun onBindViewHolder(holder: WidgetViewHolder, position: Int) {
val widgetItem = widgetItems[position]
holder.bind(widgetItem)
}
override fun getItemCount(): Int = widgetItems.size
fun addWidget(widgetItem: WidgetItem) {
widgetItems.add(widgetItem)
notifyItemInserted(widgetItems.size - 1)
}
fun removeWidget(appWidgetId: Int) {
val position = widgetItems.indexOfFirst { it.appWidgetId == appWidgetId }
if (position != -1) {
widgetItems.removeAt(position)
notifyItemRemoved(position)
}
}
fun getWidgetIds(): List<Int> = widgetItems.map { it.appWidgetId }
inner class WidgetViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(widgetItem: WidgetItem) {
val hostView = appWidgetHost.createView(context, widgetItem.appWidgetId, widgetItem.appWidgetInfo)
itemView.findViewById<FrameLayout>(R.id.widget_frame).addView(hostView)
itemView.setOnClickListener {
onWidgetClick(widgetItem.appWidgetId)
}
}
}
}

View File

@@ -1,5 +0,0 @@
package com.github.droidworksstudio.launcher.ui.widgetmanager
import android.appwidget.AppWidgetProviderInfo
data class WidgetItem(val appWidgetId: Int, val appWidgetInfo: AppWidgetProviderInfo?)

View File

@@ -1,208 +0,0 @@
package com.github.droidworksstudio.launcher.ui.widgetmanager
import android.annotation.SuppressLint
import android.app.Activity
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.databinding.FragmentWidgetManagerBinding
import com.github.droidworksstudio.launcher.helper.AppHelper
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
/**
* A simple [Fragment] subclass as the second destination in the navigation.
*/
@AndroidEntryPoint
class WidgetManagerFragment : Fragment(),
WidgetOptionsDialogFragment.WidgetOptionsListener {
private var _binding: FragmentWidgetManagerBinding? = null
private val binding get() = _binding!!
private lateinit var pickWidgetLauncher: ActivityResultLauncher<Intent>
private lateinit var createWidgetLauncher: ActivityResultLauncher<Intent>
private lateinit var appWidgetManager: AppWidgetManager
private lateinit var appWidgetHost: AppWidgetHost
private lateinit var widgetAdapter: WidgetAdapter
@Inject
lateinit var appHelper: AppHelper
private lateinit var context: Context
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentWidgetManagerBinding.inflate(inflater, container, false)
return binding.root
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
appHelper.dayNightMod(requireContext(), binding.drawBackground)
super.onViewCreated(view, savedInstanceState)
context = requireContext()
appWidgetManager = AppWidgetManager.getInstance(requireContext())
appWidgetHost = AppWidgetHost(requireContext(), Constants.APP_WIDGET_HOST_ID)
widgetAdapter = WidgetAdapter(requireContext(), appWidgetHost) { appWidgetId ->
showWidgetOptionsDialog(appWidgetId)
}
binding.widgetContainer.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = widgetAdapter
}
// Load saved widgets
val savedWidgetIds = loadWidgetIds()
for (widgetId in savedWidgetIds) {
addWidget(widgetId)
}
// Initialize the ActivityResultLaunchers
pickWidgetLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
data?.let {
val appWidgetId = it.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId != -1) {
val appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
if (appWidgetInfo?.configure != null) {
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
intent.component = appWidgetInfo.configure
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
createWidgetLauncher.launch(intent)
} else {
addWidget(appWidgetId)
}
}
}
} else if (result.resultCode == Activity.RESULT_CANCELED) {
val appWidgetId =
result.data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId != null && appWidgetId != -1) {
appWidgetHost.deleteAppWidgetId(appWidgetId)
}
}
}
createWidgetLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
data?.let {
val appWidgetId = it.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId != -1) {
addWidget(appWidgetId)
}
}
} else if (result.resultCode == Activity.RESULT_CANCELED) {
val appWidgetId =
result.data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
if (appWidgetId != null && appWidgetId != -1) {
appWidgetHost.deleteAppWidgetId(appWidgetId)
}
}
}
binding.widgetParent.setOnTouchListener(getSwipeGestureListener(context))
}
private fun getSwipeGestureListener(context: Context): View.OnTouchListener {
return object : OnSwipeTouchListener(context) {
override fun onLongClick() {
super.onLongClick()
selectWidget()
}
override fun onSwipeLeft() {
super.onSwipeLeft()
findNavController().popBackStack()
}
override fun onSwipeRight() {
super.onSwipeRight()
findNavController().popBackStack()
}
}
}
private fun selectWidget() {
val appWidgetId = appWidgetHost.allocateAppWidgetId()
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
pickWidgetLauncher.launch(intent)
}
private fun addWidget(appWidgetId: Int) {
val appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId)
appWidgetInfo?.let {
val widgetItem = WidgetItem(appWidgetId, it)
widgetAdapter.addWidget(widgetItem)
saveWidgetIds(widgetAdapter.getWidgetIds()) // Save the updated widget IDs
} ?: Log.e("WidgetManagerFragment", "Failed to get app widget info for ID: $appWidgetId")
}
override fun onRemoveWidget(appWidgetId: Int) {
appWidgetHost.deleteAppWidgetId(appWidgetId)
widgetAdapter.removeWidget(appWidgetId)
saveWidgetIds(widgetAdapter.getWidgetIds())
}
private fun showWidgetOptionsDialog(appWidgetId: Int) {
val dialog = WidgetOptionsDialogFragment.newInstance(appWidgetId)
dialog.show(childFragmentManager, "WidgetOptionsDialog")
}
override fun onOpenAppWidget(appWidgetId: Int) {
// Implement the logic to open the widget's configuration or app
// This is typically dependent on the widget and its associated app
}
private fun saveWidgetIds(widgetIds: List<Int>) {
val sharedPreferences =
requireContext().getSharedPreferences(Constants.WIDGETS_PREFS, Activity.MODE_PRIVATE)
val editor = sharedPreferences.edit()
val widgetIdsSet = widgetIds.map { it.toString() }.toSet()
editor.putStringSet(Constants.APP_WIDGETS_ID, widgetIdsSet)
editor.apply()
}
private fun loadWidgetIds(): List<Int> {
val sharedPreferences =
requireContext().getSharedPreferences(Constants.WIDGETS_PREFS, Activity.MODE_PRIVATE)
val widgetIdsSet = sharedPreferences.getStringSet(Constants.APP_WIDGETS_ID, emptySet())
return widgetIdsSet?.map { it.toInt() } ?: emptyList()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,44 +0,0 @@
package com.github.droidworksstudio.launcher.ui.widgetmanager
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.github.droidworksstudio.launcher.utils.Constants
class WidgetOptionsDialogFragment : DialogFragment() {
interface WidgetOptionsListener {
fun onRemoveWidget(appWidgetId: Int)
fun onOpenAppWidget(appWidgetId: Int)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appWidgetId =
arguments?.getInt(ARG_APP_WIDGET_ID) ?: return super.onCreateDialog(savedInstanceState)
val builder = AlertDialog.Builder(requireContext())
builder.setTitle("Widget Options")
.setItems(arrayOf("Remove Widget", "Open Widget")) { _, which ->
val listener = parentFragment as? WidgetOptionsListener
when (which) {
0 -> listener?.onRemoveWidget(appWidgetId)
1 -> listener?.onOpenAppWidget(appWidgetId)
}
}
return builder.create()
}
companion object {
private const val ARG_APP_WIDGET_ID = Constants.APP_WIDGETS_ID
fun newInstance(appWidgetId: Int): WidgetOptionsDialogFragment {
val fragment = WidgetOptionsDialogFragment()
val args = Bundle().apply {
putInt(ARG_APP_WIDGET_ID, appWidgetId)
}
fragment.arguments = args
return fragment
}
}
}

View File

@@ -0,0 +1,158 @@
package com.github.droidworksstudio.launcher.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.github.droidworksstudio.common.hideKeyboard
import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.databinding.FragmentWidgetsBinding
import com.github.droidworksstudio.launcher.helper.AppHelper
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.listener.OnSwipeTouchListener
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
import com.github.droidworksstudio.launcher.utils.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class WidgetFragment : Fragment(),
ScrollEventListener {
private var _binding: FragmentWidgetsBinding? = null
private val binding get() = _binding!!
@Inject
lateinit var preferenceHelper: PreferenceHelper
@Inject
lateinit var appHelper: AppHelper
private lateinit var navController: NavController
private lateinit var context: Context
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentWidgetsBinding.inflate(inflater, container, false)
_binding = binding
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navController = findNavController()
// Set according to the system theme mode
appHelper.dayNightMod(requireContext(), binding.mainLayout)
super.onViewCreated(view, savedInstanceState)
initializeInjectedDependencies()
setupWeatherWidget()
observeSwipeTouchListener()
observeClickListener()
}
private fun initializeInjectedDependencies() {
context = requireContext()
binding.nestScrollView.hideKeyboard()
binding.nestScrollView.scrollEventListener = this
}
private fun setupWeatherWidget() {
val sharedPreferences =
context.getSharedPreferences(Constants.WEATHER_PREFS, Context.MODE_PRIVATE)
val latitude = sharedPreferences.getFloat(Constants.LATITUDE, 0f)
val longitude = sharedPreferences.getFloat(Constants.LONGITUDE, 0f)
lifecycleScope.launch {
try {
binding.weatherRoot.visibility = View.VISIBLE
val weatherResponse = appHelper.fetchWeatherData(context, latitude, longitude)
val temperatureScale =
if (preferenceHelper.weatherUnits == Constants.Units.Metric) getString(R.string.weather_c) else getString(
R.string.weather_f
)
val speedScale =
if (preferenceHelper.weatherUnits == Constants.Units.Metric) getString(R.string.weather_mps) else getString(
R.string.weather_mph
)
binding.weatherCity.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherTemperature.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherDescription.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherWind.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherHumidity.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherPressure.setTextColor(preferenceHelper.widgetTextColor)
binding.weatherCity.text =
getString(R.string.widget_weather_location, weatherResponse.name)
binding.weatherTemperature.text =
getString(
R.string.widget_weather_temp,
weatherResponse.main.temp,
temperatureScale
)
binding.weatherDescription.text =
getString(
R.string.widget_weather_description,
weatherResponse.weather[0].description
)
binding.weatherWind.text =
getString(R.string.widget_weather_wind, weatherResponse.wind.speed, speedScale)
binding.weatherHumidity.text =
getString(R.string.widget_weather_humidity, weatherResponse.main.humidity)
binding.weatherPressure.text =
getString(R.string.widget_weather_pressure, weatherResponse.main.pressure)
val weatherWidgetDrawable = binding.weatherButtonRefresh.background
if (weatherWidgetDrawable is GradientDrawable) {
weatherWidgetDrawable.setColor(preferenceHelper.widgetBackgroundColor)
}
binding.weatherButtonRefresh.setColorFilter(preferenceHelper.widgetTextColor)
} catch (e: Exception) {
binding.weatherRoot.visibility = View.GONE
Log.e("Weather", "Failed to fetch weather data: ${e.message}")
}
}
}
@SuppressLint("ClickableViewAccessibility")
private fun observeSwipeTouchListener() {
binding.touchArea.setOnTouchListener(getSwipeGestureListener(context))
binding.nestScrollView.setOnTouchListener(getSwipeGestureListener(context))
}
private fun getSwipeGestureListener(context: Context): View.OnTouchListener {
return object : OnSwipeTouchListener(context) {
override fun onSwipeLeft() {
super.onSwipeLeft()
findNavController().popBackStack()
}
override fun onSwipeRight() {
super.onSwipeRight()
findNavController().popBackStack()
}
}
}
private fun observeClickListener() {
binding.weatherButtonRefresh.setOnClickListener {
setupWeatherWidget()
}
}
}

View File

@@ -1,7 +1,6 @@
package com.github.droidworksstudio.launcher.utils
import android.content.Context
import androidx.compose.ui.res.stringResource
import com.github.droidworksstudio.launcher.R
object Constants {
@@ -9,7 +8,10 @@ object Constants {
const val PACKAGE_NAME_DEBUG = "$PACKAGE_NAME.debug"
const val WIDGETS_PREFS = "EasyLauncherWidgets.pref"
const val APP_WIDGETS_ID = "APP_WIDGETS_ID"
const val WEATHER_PREFS = "EasyWeather.pref"
const val LATITUDE = "LATITUDE"
const val LONGITUDE = "LONGITUDE"
const val PREFS_FILENAME = "EasyLauncher.pref"
const val FIRST_LAUNCH = "FIRST_LAUNCH"
@@ -23,6 +25,8 @@ object Constants {
const val TIME_COLOR = "TIME_COLOR"
const val BATTERY_COLOR = "BATTERY_COLOR"
const val DAILY_WORD_COLOR = "DAILY_WORD_COLOR"
const val WIDGET_BACKGROUND_COLOR = "WIDGET_BACKGROUND_COLOR"
const val WIDGET_TEXT_COLOR = "WIDGET_TEXT_COLOR"
const val APP_COLOR = "APP_COLOR"
const val BATTERY_TEXT_SIZE = "BATTERY_TEXT_SIZE"
@@ -63,7 +67,8 @@ object Constants {
const val URL_GOOGLE_PLAY_STORE = "https://play.google.com/store/search?c=apps&q"
const val APP_GOOGLE_PLAY_STORE = "market://search?c=apps&q"
const val APP_WIDGET_HOST_ID = 1024
const val WEATHER_UNITS = "WEATHER_UNITS"
const val TRIPLE_TAP_DELAY_MS = 300
const val LONG_PRESS_DELAY_MS = 500
@@ -130,4 +135,16 @@ object Constants {
Up,
Down;
}
enum class Units {
Metric,
Imperial;
fun getString(context: Context): String {
return when (this) {
Metric -> context.getString(R.string.settings_units_metric)
Imperial -> context.getString(R.string.settings_units_imperial)
}
}
}
}

View File

@@ -0,0 +1,16 @@
package com.github.droidworksstudio.launcher.utils
import com.github.droidworksstudio.launcher.helper.weather.WeatherResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface WeatherApiService {
@GET("weather")
fun getWeather(
@Query("lat") latitude: String,
@Query("lon") longitude: String,
@Query("units") units: String,
@Query("apiKey") apiKey: String
): Call<WeatherResponse>
}

View File

@@ -27,6 +27,8 @@ class PreferenceViewModel @Inject constructor(
private val timeColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val batteryColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val dailyWordColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val widgetBackgroundColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val widgetTextColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val appColorLiveData: MutableLiveData<Int> = MutableLiveData()
private val dateTextSizeLiveData: MutableLiveData<Float> = MutableLiveData()
private val timeTextSizeLiveData: MutableLiveData<Float> = MutableLiveData()
@@ -99,6 +101,16 @@ class PreferenceViewModel @Inject constructor(
timeColorLiveData.postValue(preferenceHelper.timeColor)
}
fun setWidgetBackgroundColor(timeColor: Int) {
preferenceHelper.widgetBackgroundColor = timeColor
widgetBackgroundColorLiveData.postValue(preferenceHelper.widgetBackgroundColor)
}
fun setWidgetTextColor(timeColor: Int) {
preferenceHelper.widgetTextColor = timeColor
widgetTextColorLiveData.postValue(preferenceHelper.widgetTextColor)
}
fun setBatteryColor(batteryColor: Int) {
preferenceHelper.batteryColor = batteryColor
batteryColorLiveData.postValue(preferenceHelper.batteryColor)

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.65,6.35c-1.63,-1.63 -3.94,-2.57 -6.48,-2.31c-3.67,0.37 -6.69,3.35 -7.1,7.02C3.52,15.91 7.27,20 12,20c3.19,0 5.93,-1.87 7.21,-4.57c0.31,-0.66 -0.16,-1.43 -0.89,-1.43h-0.01c-0.37,0 -0.72,0.2 -0.88,0.53c-1.13,2.43 -3.84,3.97 -6.81,3.32c-2.22,-0.49 -4.01,-2.3 -4.49,-4.52C5.31,9.44 8.26,6 12,6c1.66,0 3.14,0.69 4.22,1.78l-2.37,2.37C13.54,10.46 13.76,11 14.21,11H19c0.55,0 1,-0.45 1,-1V5.21c0,-0.45 -0.54,-0.67 -0.85,-0.35L17.65,6.35z" />
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffff" />
<corners android:radius="8dp" />
<padding
android:left="16dp"
android:top="16dp"
android:right="16dp"
android:bottom="16dp" />
</shape>

View File

@@ -22,7 +22,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_color_date_view"
@@ -56,12 +56,11 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_color_time_view"
android:layout_width="match_parent"
@@ -94,7 +93,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
@@ -130,7 +129,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
@@ -166,7 +165,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
@@ -202,7 +201,79 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle"/>
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_color_widget_background_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/select_widget_background_title"
style="@style/TextDefaultStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:background="@android:color/transparent"
android:gravity="left|center"
android:inputType="number"
android:text="@string/appearance_bottom_dialog_desc_widget_background"
android:textSize="@dimen/text_large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,TouchTargetSizeCheck" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/select_widget_background_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:background="@android:color/transparent"
android:textSize="@dimen/text_large"
android:gravity="left|center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_color_widget_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/select_widget_text_title"
style="@style/TextDefaultStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:background="@android:color/transparent"
android:gravity="left|center"
android:inputType="number"
android:text="@string/appearance_bottom_dialog_desc_widget_text"
android:textSize="@dimen/text_large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,TouchTargetSizeCheck" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/select_widget_text_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:background="@android:color/transparent"
android:textSize="@dimen/text_large"
android:gravity="left|center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:ignore="RtlHardcoded"
style="@style/TextDefaultStyle" />
</androidx.appcompat.widget.LinearLayoutCompat>

View File

@@ -9,8 +9,7 @@
<FrameLayout
android:id="@+id/touchArea"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginVertical="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -9,8 +9,7 @@
<FrameLayout
android:id="@+id/touchArea"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginVertical="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -11,8 +11,7 @@
<FrameLayout
android:id="@+id/touchArea"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginVertical="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -8,8 +8,7 @@
<FrameLayout
android:id="@+id/touchArea"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginVertical="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/draw_background"
tools:context=".ui.widgetmanager.WidgetManagerFragment">
<FrameLayout
android:id="@+id/widget_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:longClickable="true"
android:orientation="vertical"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/widget_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical"
tools:context=".ui.widgets.WidgetFragment">
<FrameLayout
android:id="@+id/touchArea"
android:layout_marginVertical="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.github.droidworksstudio.launcher.view.GestureNestedScrollView
android:id="@+id/nestScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:clipChildren="false"
android:clipToPadding="false"
android:fadingEdgeLength="48dp"
android:overScrollMode="never"
android:requiresFadingEdge="vertical"
android:scrollbars="none">
<RelativeLayout
android:id="@+id/weather_root"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="50dp"
android:background="@drawable/widget_background">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_alignParentStart="true"
android:layout_marginBottom="16dp"
android:id="@+id/topHeader"
android:layout_height="wrap_content">
<TextView
android:id="@+id/weather_city"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
android:ellipsize="end"
android:gravity="center"
android:lines="1"
android:textSize="18sp"
tools:text="Kayamkulam, IN" />
<ImageButton
android:id="@+id/weather_button_refresh"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:contentDescription="@string/widget_weather_refresh"
android:src="@drawable/icon_refresh" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_below="@id/topHeader"
android:gravity="center_vertical"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingStart="8dp">
<ImageView
android:id="@+id/weather_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="0.3"
android:contentDescription="@string/widget_weather_description" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:orientation="vertical">
<TextView
android:id="@+id/weather_temperature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAlignment="viewStart"
android:lines="1"
android:text="@string/widget_weather_temp"
android:textSize="30sp" />
<TextView
android:id="@+id/weather_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:paddingTop="2dp"
android:text="@string/widget_weather_description"
android:textSize="16sp" />
<TextView
android:id="@+id/weather_wind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingTop="2dp"
android:text="@string/widget_weather_wind"
android:lines="1" />
<TextView
android:id="@+id/weather_humidity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingTop="2dp"
android:text="@string/widget_weather_humidity"
android:lines="1" />
<TextView
android:id="@+id/weather_pressure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:ellipsize="end"
android:text="@string/widget_weather_pressure"
android:lines="1" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</com.github.droidworksstudio.launcher.view.GestureNestedScrollView>
</FrameLayout>

View File

@@ -1,29 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/button_open_app"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open App"
android:textColor="?attr/colorPrimary"
android:backgroundTint="?attr/colorAccent"
app:backgroundTint="?attr/colorAccent"
app:rippleColor="?attr/colorAccent" />
<Button
android:id="@+id/button_remove_widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Remove Widget"
android:textColor="?attr/colorPrimary"
android:backgroundTint="?attr/colorAccent"
app:backgroundTint="?attr/colorAccent"
app:rippleColor="?attr/colorAccent" />
</LinearLayout>

View File

@@ -39,9 +39,9 @@
<fragment
android:id="@+id/WidgetsFragment"
android:name="com.github.droidworksstudio.launcher.ui.widgetmanager.WidgetManagerFragment"
android:name="com.github.droidworksstudio.launcher.ui.widgets.WidgetFragment"
android:label="@string/widgets_fragment_label"
tools:layout="@layout/fragment_widget_manager" />
tools:layout="@layout/fragment_widgets" />
<fragment
android:id="@+id/DrawFragment"

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="search_view_hint">#FFFFFFFF</color>
<color name="search_button_background">#56A1E6</color>
<color name="dialog_background">#333333</color>
<!-- Black -->
<color name="black">#000000</color>
<color name="blackTrans10">#1A000000</color>
<color name="blackTrans25">#30000000</color>
<color name="blackTrans50">#80000000</color>
<color name="blackTrans80">#CC000000</color>
<color name="blackTrans90">#E6000000</color>
<!-- White -->
<color name="white">#FFFFFF</color>
<color name="whiteTrans10">#1AFFFFFF</color>
<color name="whiteTrans25">#30FFFFFF</color>
<color name="whiteTrans50">#65FFFFFF</color>
<color name="whiteTrans80">#CCFFFFFF</color>
<color name="whiteTrans90">#E6FFFFFF</color>
<color name="green">#4CAF50</color>
<color name="orange">#AF684C</color>
<color name="red">#AF4C4C</color>
</resources>

View File

@@ -0,0 +1,20 @@
<resources>
<string name="battery_level" translatable="false">%s%%</string>
<string name="widget_weather_refresh" translatable="false">%1$s</string>
<string name="widget_weather_location" translatable="false">%1$s</string>
<string name="widget_weather_temp" translatable="false">%1$.2f %2$s</string>
<string name="widget_weather_description" translatable="false">%1$s</string>
<string name="weather_mps" translatable="false">m/s</string>
<string name="weather_mph" translatable="false">mil/h</string>
<string name="weather_mm" translatable="false">mm</string>
<string name="weather_c" translatable="false">°C</string>
<string name="weather_f" translatable="false">°F</string>
<string name="widget_weather_feels_like" translatable="false">Feels Like : %s%</string>
<string name="widget_weather_wind" translatable="false">Wind : %1$.2f %2$s</string>
<string name="widget_weather_humidity" translatable="false">Humidity : %d %%</string>
<string name="widget_weather_pressure" translatable="false">Pressure : %d hPa</string>
</resources>

View File

@@ -13,8 +13,7 @@
<!-- Strings used for fragments header labels -->
<string name="favorite_fragment_name">Favorite Apps</string>
<string name="hidden_fragment_name">Hidden Apps</string>
<string name="battery_level">%s%%</string>
<string name="search">Search</string>
<string name="bottom_dialog_app_rename">Rename</string>
@@ -72,6 +71,9 @@
<string name="appearance_bottom_dialog_desc_word">Word\u0020:\u0020</string>
<string name="appearance_bottom_dialog_desc_battery">Battery\u0020:\u0020 </string>
<string name="appearance_bottom_dialog_desc_widget_background">Widget Background\u0020:\u0020</string>
<string name="appearance_bottom_dialog_desc_widget_text">Widget Text\u0020:\u0020</string>
<string name="settings_manager_favorite">Favorite Apps</string>
<string name="settings_manager_hidden">Hidden Apps</string>
@@ -107,6 +109,9 @@
<string name="settings_actions_take_a_screenshot">Take a ScreenShot</string>
<string name="settings_actions_disabled">Disabled</string>
<string name="settings_units_metric">Metric</string>
<string name="settings_units_imperial">Imperial</string>
<string name="authentication_title">Authentication</string>
<string name="authentication_subtitle">Log in using your biometric credentials.</string>
<string name="authentication_cancel">Authentication Cancel</string>

View File

@@ -14,6 +14,7 @@ dagger = "2.51.1"
work = "2.9.0"
recyclerview = "1.3.2"
preference = "1.2.1"
retrofit = "2.11.0"
room = "2.6.1"
biometric = "1.2.0-alpha05"
color-chooser = "0.7.3"
@@ -35,6 +36,9 @@ preference = { group = "androidx.preference", name = "preference-ktx", version.r
biometric-ktx = { group = "androidx.biometric", name = "biometric-ktx", version.ref = "biometric" }
color-chooser = { group = "net.mm2d.color-chooser", name = "color-chooser", version.ref = "color-chooser" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
# Room Packages
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }

1
weather.properties Normal file
View File

@@ -0,0 +1 @@
WEATHER_API_KEY=ba0442331f7232028a3efc3f90c1fa84