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
56 changes: 56 additions & 0 deletions fdroid/metadata/com.devasy.repforge.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
Categories:
- Sports & Health
License: Apache-2.0
AuthorName: Devasy Patel
SourceCode: https://github.com/Devasy23/RepForge
IssueTracker: https://github.com/Devasy23/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.

Builds:
- versionName: 2.0.1
versionCode: 21
commit: v2.0.1
subdir: workout-logger
gradle:
- release
prebuild:
- flutter pub get
build:
- flutter build apk --release --split-per-abi

AutoUpdateMode: Version v%v
UpdateCheckMode: Tags
UpdateCheckData: workout-logger/pubspec.yaml|^version:\s+([\d.]+)\+|.|
CurrentVersion: 2.0.1
CurrentVersionCode: 21
Binary file not shown.
Binary file not shown.
27 changes: 13 additions & 14 deletions workout-logger/lib/screens/ai_coach_screen.dart
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:gpt_markdown/gpt_markdown.dart';

import '../models/models.dart';
Expand DownExpand Up@@ -190,7 +189,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
children: [
Text(
'AI Coach',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
Expand All@@ -199,7 +198,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
),
Text(
'Powered by Gemini',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 11,
),
Expand DownExpand Up@@ -297,7 +296,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
const SizedBox(height: AppSpacing.lg),
Text(
name != null && name.isNotEmpty ? 'Hey $name 👋' : 'Your AI Coach',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 22,
fontWeight: FontWeight.w700,
Expand All@@ -308,7 +307,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
Text(
'Ask me anything — what to train today, how to break a plateau, reading your progress, anything.',
textAlign: TextAlign.center,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 14,
height: 1.5,
Expand DownExpand Up@@ -393,7 +392,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
),
child: TextField(
controller: _controller,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 14,
),
Expand All@@ -402,7 +401,7 @@ class _AiCoachViewState extends State<_AiCoachView> {
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
hintText: 'Ask your coach...',
hintStyle: GoogleFonts.geist(
hintStyle: TextStyle(fontFamily: 'Geist',
color: AppColors.textFaint,
fontSize: 14,
),
Expand DownExpand Up@@ -515,7 +514,7 @@ class _ConversationsSheet extends StatelessWidget {
children: [
Text(
'Conversations',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
Expand All@@ -534,7 +533,7 @@ class _ConversationsSheet extends StatelessWidget {
const SizedBox(width: 4),
Text(
'New chat',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.primary,
fontSize: 13,
fontWeight: FontWeight.w600,
Expand All@@ -551,7 +550,7 @@ class _ConversationsSheet extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg),
child: Text(
'No saved conversations yet.',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 13,
),
Expand DownExpand Up@@ -630,7 +629,7 @@ class _ConversationTile extends StatelessWidget {
conversation.title.isEmpty ? 'New chat' : conversation.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 13,
fontWeight: FontWeight.w500,
Expand DownExpand Up@@ -672,7 +671,7 @@ class _SuggestionChip extends StatelessWidget {
),
child: Text(
label,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.primary,
fontSize: 13,
fontWeight: FontWeight.w500,
Expand DownExpand Up@@ -740,7 +739,7 @@ class _MessageBubble extends StatelessWidget {
child: isUser
? Text(
message.text,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 14,
height: 1.55,
Expand DownExpand Up@@ -804,7 +803,7 @@ class _CoachMarkdown extends StatelessWidget {
Widget build(BuildContext context) {
return GptMarkdown(
text,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 14,
height: 1.55,
Expand Down
31 changes: 15 additions & 16 deletions workout-logger/lib/screens/ai_program_generator_screen.dart
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:google_fonts/google_fonts.dart';

import '../models/models.dart';
import '../services/ai/gemini_ai_service.dart';
Expand DownExpand Up@@ -196,7 +195,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
children: [
Text(
'AI Program Generator',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
Expand All@@ -205,7 +204,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
),
Text(
'Powered by Gemini',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 11,
),
Expand DownExpand Up@@ -233,7 +232,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(width: 8),
Text(
'Describe your program',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w700,
Expand All@@ -250,7 +249,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
),
child: TextField(
controller: _promptCtrl,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 14,
height: 1.5,
Expand All@@ -261,7 +260,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
decoration: InputDecoration(
hintText:
'e.g. "12-week hypertrophy program, 4 days/week, push-pull split, intermediate level"',
hintStyle: GoogleFonts.geist(
hintStyle: TextStyle(fontFamily: 'Geist',
color: AppColors.textFaint,
fontSize: 13,
height: 1.5,
Expand All@@ -274,7 +273,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(height: AppSpacing.md),
Text(
'QUICK PROMPTS',
style: GoogleFonts.geistMono(
style: TextStyle(fontFamily: 'GeistMono',
color: AppColors.textFaint,
fontSize: 9,
fontWeight: FontWeight.w600,
Expand All@@ -300,7 +299,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
),
child: Text(
s,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textSoft,
fontSize: 11,
),
Expand DownExpand Up@@ -337,7 +336,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(height: AppSpacing.md),
Text(
_statusText,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 15,
fontWeight: FontWeight.w600,
Expand All@@ -346,7 +345,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(height: AppSpacing.xs),
Text(
'Gemini is designing your training block…',
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 12,
),
Expand All@@ -367,7 +366,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
Expanded(
child: Text(
_error!,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.error,
fontSize: 13,
),
Expand DownExpand Up@@ -415,7 +414,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
children: [
Text(
p.name,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
Expand All@@ -425,7 +424,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
if (p.description != null)
Text(
p.description!,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textMuted,
fontSize: 12,
),
Expand DownExpand Up@@ -456,7 +455,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(height: AppSpacing.md),
Text(
'PHASES',
style: GoogleFonts.geistMono(
style: TextStyle(fontFamily: 'GeistMono',
color: AppColors.textFaint,
fontSize: 9,
fontWeight: FontWeight.w600,
Expand All@@ -480,7 +479,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const SizedBox(width: 10),
Text(
phase.name,
style: GoogleFonts.geist(
style: TextStyle(fontFamily: 'Geist',
color: AppColors.textPrimary,
fontSize: 13,
fontWeight: FontWeight.w500,
Expand All@@ -489,7 +488,7 @@ class _AiProgramGeneratorScreenState extends State<AiProgramGeneratorScreen> {
const Spacer(),
Text(
'Wk ${phase.startWeek}–${phase.endWeek}',
style: GoogleFonts.geistMono(
style: TextStyle(fontFamily: 'GeistMono',
color: AppColors.textMuted,
fontSize: 11,
),
Expand Down
Loading
Loading