Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 74 additions & 42 deletions fdroid/metadata/com.devasy.repforge.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,88 @@
AntiFeatures:
NonFreeNet:
en-US: "The optional AI Coach and Routine Optimizer features send data to Google's
Gemini API. These features are fully disabled unless the user provides their
own Gemini API key in Settings → AI Settings."
Categories:
- Sports & Health
License: Apache-2.0
AuthorName: Devasy Patel
SourceCode: https://github.com/Devasy23/RepForge
IssueTracker: https://github.com/Devasy23/RepForge/issues
SourceCode: https://github.com/Devasy/RepForge
IssueTracker: https://github.com/Devasy/RepForge/issues

AutoName: RepForge
Summary: Open-source workout logger with AI coaching and progress analytics
Description: |-
RepForge is a privacy-first workout logging app that helps you track sets,
reps, and weights across customizable routines.

Features:
* Log workout sessions with sets, reps, and weights
* Visualise progress with charts (volume, strength curves)
* AI-powered set recommendations using linear regression
* Customisable exercise library with 50+ built-in exercises
* Goal tracking with ML-estimated completion dates
* Reusable workout routines
* Health Connect integration for sleep and heart rate readiness scores
* Optional AI Coach powered by Google Gemini (requires user-supplied API key)
* Full data export/import for portability

All workout data is stored locally on-device using Hive. No account required.
No data is sent to any server unless you enable the optional AI Coach feature.

RepoType: git
Repo: https://github.com/Devasy23/RepForge

AntiFeatures:
NonFreeNet:
- description: >
The optional AI Coach and Routine Optimizer features send data to
Google's Gemini API (a proprietary cloud service). These features are
fully disabled unless the user provides their own Gemini API key in
Settings → AI Settings. The app is fully functional as an offline
workout logger without configuring a key.
Repo: https://github.com/Devasy/RepForge

Builds:
- versionName: 2.0.1
versionCode: 21
commit: v2.0.1
- versionName: 2.0.5
versionCode: 251
commit: ed8be2420595337f69cf550aeb4d77cb1ae943ef
subdir: workout-logger
output: build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
srclibs:
- flutter@stable
prebuild:
- export PUB_CACHE=$(pwd)/.pub-cache
- 'export FLUTTER_VERSION=$(sed -n -E "s/^ flutter: (.+)/\1/p" pubspec.yaml)'
- '[[ $FLUTTER_VERSION ]]'
- git -C $$flutter$$ checkout -f $FLUTTER_VERSION
- $$flutter$$/bin/flutter config --no-analytics
- $$flutter$$/bin/flutter pub get --enforce-lockfile
scandelete:
- workout-logger/.pub-cache
build:
- export PUB_CACHE=$(pwd)/.pub-cache
- $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-arm"

- versionName: 2.0.5
versionCode: 252
commit: ed8be2420595337f69cf550aeb4d77cb1ae943ef
subdir: workout-logger
output: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
srclibs:
- flutter@stable
prebuild:
- export PUB_CACHE=$(pwd)/.pub-cache
- 'export FLUTTER_VERSION=$(sed -n -E "s/^ flutter: (.+)/\1/p" pubspec.yaml)'
- '[[ $FLUTTER_VERSION ]]'
- git -C $$flutter$$ checkout -f $FLUTTER_VERSION
- $$flutter$$/bin/flutter config --no-analytics
- $$flutter$$/bin/flutter pub get --enforce-lockfile
scandelete:
- workout-logger/.pub-cache
build:
- export PUB_CACHE=$(pwd)/.pub-cache
- $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-arm64"

- versionName: 2.0.5
versionCode: 253
commit: ed8be2420595337f69cf550aeb4d77cb1ae943ef
subdir: workout-logger
gradle:
- release
output: build/app/outputs/flutter-apk/app-x86_64-release.apk
srclibs:
- flutter@stable
prebuild:
- flutter pub get
- export PUB_CACHE=$(pwd)/.pub-cache
- 'export FLUTTER_VERSION=$(sed -n -E "s/^ flutter: (.+)/\1/p" pubspec.yaml)'
- '[[ $FLUTTER_VERSION ]]'
- git -C $$flutter$$ checkout -f $FLUTTER_VERSION
- $$flutter$$/bin/flutter config --no-analytics
- $$flutter$$/bin/flutter pub get --enforce-lockfile
scandelete:
- workout-logger/.pub-cache
build:
- flutter build apk --release --split-per-abi
- export PUB_CACHE=$(pwd)/.pub-cache
- $$flutter$$/bin/flutter build apk --release --split-per-abi --target-platform="android-x64"

AutoUpdateMode: Version v%v
UpdateCheckMode: Tags
UpdateCheckData: workout-logger/pubspec.yaml|^version:\s+([\d.]+)\+|.|
CurrentVersion: 2.0.1
CurrentVersionCode: 21
AutoUpdateMode: Version
UpdateCheckMode: Tags ^v[\d.]+$
VercodeOperation:
- 10 * %c + 1
- 10 * %c + 2
- 10 * %c + 3
UpdateCheckData:
workout-logger/pubspec.yaml|version:\s.*\+(\d+)|workout-logger/pubspec.yaml|version:\s([\d.]+)\+
CurrentVersion: 2.0.5
CurrentVersionCode: 253
9 changes: 9 additions & 0 deletions workout-logger/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## graphify
Comment thread
Devasy marked this conversation as resolved.

This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.

Rules:
- ALWAYS read graphify-out/GRAPH_REPORT.md before reading any source files, running grep/glob searches, or answering codebase questions. The graph is your primary map of the codebase.
- IF graphify-out/wiki/index.md EXISTS, navigate it instead of reading raw files
- For cross-module "how does X relate to Y" questions, prefer `graphify query "<question>"`, `graphify path "<A>" "<B>"`, or `graphify explain "<concept>"` over grep — these traverse the graph's EXTRACTED + INFERRED edges instead of scanning files
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
14 changes: 14 additions & 0 deletions workout-logger/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.android.build.gradle.internal.api.ApkVariantOutputImpl

plugins {
id("com.android.application")
id("kotlin-android")
Expand Down Expand Up @@ -87,3 +89,15 @@ android {
flutter {
source = "../.."
}

val abiCodes = mapOf("armeabi-v7a" to 1, "arm64-v8a" to 2, "x86_64" to 3)
android.applicationVariants.configureEach {
val variant = this
variant.outputs.forEach { output ->
val abiVersionCode = abiCodes[output.filters.find { it.filterType == "ABI" }?.identifier]
if (abiVersionCode != null) {
(output as ApkVariantOutputImpl).versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
}

7 changes: 7 additions & 0 deletions workout-logger/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true" />
<meta-data
android:name="io.flutter.embedding.android.EnableHcpp"
android:value="true" />
Comment thread
Devasy marked this conversation as resolved.

<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
Expand Down
12 changes: 9 additions & 3 deletions workout-logger/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ void main() async {
DeviceOrientation.portraitDown,
]);

// Set system overlay style
// Enable edge-to-edge: draw behind status bar AND nav bar
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);

// Keep all system overlays transparent; content uses SafeArea for insets
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: AppTheme.backgroundColor,
systemNavigationBarIconBrightness: Brightness.light,
systemStatusBarContrastEnforced: false,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.dark,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
systemNavigationBarContrastEnforced: false,
),
);

Expand Down
2 changes: 1 addition & 1 deletion workout-logger/lib/screens/ai_coach_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ class _ConversationsSheet extends StatelessWidget {
child: ListView.separated(
shrinkWrap: true,
itemCount: conversations.length,
separatorBuilder: (_, __) =>
separatorBuilder: (_, _) =>
const SizedBox(height: AppSpacing.sm),
itemBuilder: (_, i) {
final c = conversations[i];
Expand Down
6 changes: 3 additions & 3 deletions workout-logger/lib/screens/analytics_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: AppColors.glass2,
borderRadius: BorderRadius.circular(14),
borderRadius: BorderRadius.circular(AppRadius.button),
border: Border.all(color: AppColors.glassBorder),
),
child: Row(
Expand All @@ -99,7 +99,7 @@ class _AnalyticsScreenState extends State<AnalyticsScreen> {
child: GestureDetector(
onTap: () => setState(() => _tab = i),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
duration: AppDurations.normal,
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
Expand Down Expand Up @@ -546,7 +546,7 @@ class _FilterChip extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
duration: AppDurations.fast,
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
Expand Down
4 changes: 2 additions & 2 deletions workout-logger/lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ class _DashboardTab extends StatelessWidget {
),
const SizedBox(height: 6),
AnimatedContainer(
duration: const Duration(milliseconds: 300),
duration: AppDurations.medium,
height: 6,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
Expand Down Expand Up @@ -1298,7 +1298,7 @@ class _WeeklyInsightsCardState extends State<_WeeklyInsightsCard> {
GestureDetector(
onTap: _loading ? null : _refresh,
child: AnimatedContainer(
duration: const Duration(milliseconds: 150),
duration: AppDurations.fast,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColors.primary.withValues(alpha: 0.12),
Expand Down
2 changes: 1 addition & 1 deletion workout-logger/lib/screens/profile_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class _ProfileScreenState extends State<ProfileScreen>

setState(() => _isImporting = true);
try {
final result = await FilePicker.platform.pickFiles(
final result = await FilePicker.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class _ImportProgramScreenState extends State<ImportProgramScreen> {

Future<void> _pickFile() async {
try {
final result = await FilePicker.platform.pickFiles(
final result = await FilePicker.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
allowMultiple: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ class _ProgramDesignerScreenState extends State<ProgramDesignerScreen> {
height: 32,
decoration: BoxDecoration(
color: week.isDeload
? Colors.amber.withOpacity(0.2)
: AppTheme.primaryColor.withOpacity(0.2),
? Colors.amber.withValues(alpha: 0.2)
: AppTheme.primaryColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(6),
),
alignment: Alignment.center,
Expand Down Expand Up @@ -875,6 +875,7 @@ class _ProgramDesignerScreenState extends State<ProgramDesignerScreen> {
);
if (proceed != true) return;
}
if (!mounted) return;

final provider = context.read<WorkoutProvider>();
final program = TrainingProgram(
Expand Down
2 changes: 1 addition & 1 deletion workout-logger/lib/screens/routine_optimizer_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ class _ConversationsSheet extends StatelessWidget {
child: ListView.separated(
shrinkWrap: true,
itemCount: conversations.length,
separatorBuilder: (_, __) =>
separatorBuilder: (_, _) =>
const SizedBox(height: AppSpacing.sm),
itemBuilder: (_, i) {
final c = conversations[i];
Expand Down
Loading
Loading