Initial Commit

This commit is contained in:
nabeel
2026-09-18 15:56:54 +10:00
commit dd59dcb605
598 changed files with 49076 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,2 @@
#Tue Sep 15 14:16:51 AEST 2026
gradle.version=8.7

Binary file not shown.

View File

@@ -0,0 +1,2 @@
#Fri Sep 18 12:48:55 AEST 2026
java.home=/opt/android-studio/jbr

BIN
.gradle/file-system.probe Normal file

Binary file not shown.

View File

3
.idea/.gitignore generated vendored Normal file
View File

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

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

2385
.idea/caches/deviceStreaming.xml generated Normal file

File diff suppressed because it is too large Load Diff

123
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/compiler.xml generated Normal file
View File

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

26
.idea/deploymentTargetSelector.xml generated Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-09-18T05:41:26.641160957Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=46271FDAS00EMB" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection>
<targets>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Nabeel\.android\avd\Pixel_9_Pro_XL.avd" />
</handle>
</Target>
</targets>
</DialogSelection>
</SelectionState>
</selectionStates>
</component>
</project>

13
.idea/deviceManager.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

19
.idea/gradle.xml generated 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="testRunner" value="CHOOSE_PER_TEST" />
<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>
</GradleProjectSettings>
</option>
</component>
</project>

8
.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>

10
.idea/migrations.xml generated 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>

16
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,16 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="0.250299072265625" />
</ProjectState>
</option>
</component>
</project>

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

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/studiobot.xml generated Normal file
View File

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

86
README.md Normal file
View File

@@ -0,0 +1,86 @@
# Budget Tracker Widget
An Android home-screen widget that shows actual spend vs. budget: an overall
progress bar (color-coded green/amber/red) plus a per-category breakdown
chart, rendered for 7 sample budget items.
## Opening the project
1. Open Android Studio → **Open** → select the `BudgetTrackerWidget` folder.
2. Let Gradle sync (Android Studio will fetch Gradle 8.7 per
`gradle/wrapper/gradle-wrapper.properties` — no wrapper jar is bundled, so
the first sync needs an internet connection).
3. Run the `app` module on an emulator or device (API 26+).
4. Long-press the home screen → **Widgets** → find **Budget Tracker** → drag
it onto the home screen.
## How it's wired together
- **`BudgetItem.kt`** — data model for one budget line (category, budgeted, spent).
- **`BudgetRepository.kt`** — the single data source. `fetchBudgetData()` is a
placeholder that simulates a network call and returns 7 sample categories
(Rent, Groceries, Dining Out, Transport, Utilities, Entertainment, Savings)
mixing under-, at-, and over-budget items. A commented-out `parseBudgetJson()`
shows how to parse a real API response — swap the body of `fetchBudgetData()`
for an OkHttp/Retrofit/Ktor call when you have a real endpoint, and nothing
else in the app needs to change.
- **`BudgetColors.kt`** — the single green/amber/red threshold definition, shared
by the chart renderer, the widget's progress bar, and the app's progress bar,
so status colors are always consistent.
- **`BudgetChartRenderer.kt`** — draws the category bar chart onto a `Bitmap`
using `Canvas`. Widgets can't host arbitrary custom Views (RemoteViews only
supports a fixed set of native widgets), so this is the standard way to get
a custom visualization into a widget: draw it yourself, then push the
bitmap into an `ImageView`. The **app screen reuses this exact same
renderer**, so the in-app view and the widget are pixel-identical, just at
different sizes.
- **`WidgetLayoutPlanner.kt`** — pure logic that turns a widget's current
width/height (dp) into a display plan: whether to show the percent label,
whether there's room for the chart at all, how many category rows fit, and
whether text should shrink for narrow widths.
- **`BudgetWidgetProvider.kt`** — the `AppWidgetProvider`. Reads the widget's
live size from `onAppWidgetOptionsChanged()` (fired whenever the user drags
the resize handles) as well as `onUpdate()`, feeds it through
`WidgetLayoutPlanner`, and builds `RemoteViews` accordingly — hiding the
chart entirely at very small sizes, showing only the most over-budget
categories (with a "+N more" footer) when space is tight, and sizing the
rendered bitmap to match.
- **`widget_budget.xml`** — the widget layout: title, total, percent label,
three stacked (mutually-exclusive-visibility) `ProgressBar`s for the
green/amber/red states, an `ImageView` for the chart bitmap, and a "+N
more" footer.
- **`MainActivity.kt`** / **`activity_main.xml`** — a full-screen version of
the same card: same background, same title/total/percent, a single
`ProgressBar` tinted at runtime via `BudgetColors`, and the same chart
bitmap (rendered larger, showing every category since there's no space
constraint).
## Flexible widget sizing
The widget can be resized from a small ~2×1 cell (just title, total, and
progress bar) up to a full card with the complete chart:
- `minWidth`/`minHeight` in `budget_widget_info.xml` set the default placed
size; `minResizeWidth`/`minResizeHeight` set how small it can be dragged.
- At runtime, `onAppWidgetOptionsChanged()` reports the new size, and
`WidgetLayoutPlanner` decides what fits — hiding the percent label and
chart below certain heights, shrinking text below a certain width, and
showing the categories closest to (or over) budget first when not all 7
fit.
- Try dragging the widget to different sizes on the home screen to see it
adapt live.
## Extending it
- **Real data**: replace `BudgetRepository.fetchBudgetData()`'s body with an
actual HTTP call; keep the return type `List<BudgetItem>` and everything
downstream (widget + activity) keeps working unchanged.
- **Refresh cadence**: `updatePeriodMillis` in `budget_widget_info.xml` is set
to 30 minutes (Android's minimum useful floor is ~30 min for this
mechanism). For more frequent updates, trigger `ACTION_APPWIDGET_UPDATE`
yourself from a `WorkManager` periodic job.
- **Tap-to-open categories**: `setOnClickPendingIntent` is only wired to the
whole widget right now; you could add per-row click targets if you split
the chart into individual `ImageView`/`TextView` rows instead of one
bitmap.
- **Android 12+ responsive layouts**: for an even smoother resize experience
on API 31+, you could additionally supply a size-keyed
`RemoteViews(Map<SizeF, RemoteViews>)` so the system switches layouts
instantly during a drag, rather than waiting for the
`onAppWidgetOptionsChanged()` callback. The current approach already
covers all API levels (26+) and updates immediately once the drag ends.

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

@@ -0,0 +1,41 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.budgetwidget"
compileSdk = 34
defaultConfig {
applicationId = "com.example.budgetwidget"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}

Binary file not shown.

View File

@@ -0,0 +1,21 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.example.budgetwidget",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-debug.apk"
}
],
"elementType": "File",
"minSdkVersionForDexing": 26
}

View File

@@ -0,0 +1,2 @@
#- File Locator -
listingFile=../../../apk/debug/output-metadata.json

View File

@@ -0,0 +1,2 @@
appMetadataVersion=1.1
androidGradlePluginVersion=8.5.0

View File

@@ -0,0 +1,10 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.example.budgetwidget",
"variantName": "debug",
"elements": []
}

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