diff --git a/lifetimer/android/app/build.gradle.kts b/lifetimer/android/app/build.gradle.kts index 9d562e8..85eaad2 100644 --- a/lifetimer/android/app/build.gradle.kts +++ b/lifetimer/android/app/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } android { - namespace = "com.example.lifetimer" + namespace = "com.example.app1356" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -23,7 +23,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.lifetimer" + applicationId = "com.example.app1356" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/lifetimer/android/app/src/main/AndroidManifest.xml b/lifetimer/android/app/src/main/AndroidManifest.xml index a1d0818..6b76221 100644 --- a/lifetimer/android/app/src/main/AndroidManifest.xml +++ b/lifetimer/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,13 @@ + + + + + + @@ -30,7 +36,7 @@ - + + + + + + + + + + + + + + + + diff --git a/lifetimer/android/app/src/main/res/values/strings.xml b/lifetimer/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..ed13e0c --- /dev/null +++ b/lifetimer/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + 1356 + + + YOUR_WEB_CLIENT_ID_HERE + diff --git a/lifetimer/lib/data/repositories/auth_repository.dart b/lifetimer/lib/data/repositories/auth_repository.dart index cf9bba9..5def163 100644 --- a/lifetimer/lib/data/repositories/auth_repository.dart +++ b/lifetimer/lib/data/repositories/auth_repository.dart @@ -84,27 +84,51 @@ class AuthRepository { } Future signInWithGoogle() async { - final GoogleSignIn googleSignIn = GoogleSignIn(); + try { + final GoogleSignIn googleSignIn = GoogleSignIn( + scopes: ['email', 'profile'], + ); - final googleUser = await googleSignIn.signIn(); - if (googleUser == null) { - throw Exception('Google sign-in was cancelled'); + // Check if user is already signed in + final googleUser = await googleSignIn.signInSilently(); + if (googleUser != null) { + await _handleGoogleUser(googleUser); + return; + } + + // Sign in interactively + final interactiveUser = await googleSignIn.signIn(); + if (interactiveUser == null) { + throw Exception('Google sign-in was cancelled'); + } + + await _handleGoogleUser(interactiveUser); + } catch (e) { + throw Exception('Google sign-in failed: ${e.toString()}'); } + } - final googleAuth = await googleUser.authentication; - final idToken = googleAuth.idToken; + Future _handleGoogleUser(dynamic googleUser) async { + try { + final googleAuth = await googleUser.authentication; + final idToken = googleAuth.idToken; + final accessToken = googleAuth.accessToken; - if (idToken == null) { - throw Exception('No ID token from Google sign-in'); - } + if (idToken == null && accessToken == null) { + throw Exception('No ID token or access token from Google sign-in'); + } - final response = await _client.auth.signInWithIdToken( - provider: supabase.OAuthProvider.google, - idToken: idToken, - ); + final response = await _client.auth.signInWithIdToken( + provider: supabase.OAuthProvider.google, + idToken: idToken, + accessToken: accessToken, + ); - if (response.user != null) { - await _ensureUserProfileExists(response.user!.id, response.user!); + if (response.user != null) { + await _ensureUserProfileExists(response.user!.id, response.user!); + } + } catch (e) { + throw Exception('Failed to authenticate with Google: ${e.toString()}'); } } diff --git a/lifetimer/lib/features/auth/presentation/auth_choice_screen.dart b/lifetimer/lib/features/auth/presentation/auth_choice_screen.dart index 244c105..3583cd2 100644 --- a/lifetimer/lib/features/auth/presentation/auth_choice_screen.dart +++ b/lifetimer/lib/features/auth/presentation/auth_choice_screen.dart @@ -102,7 +102,7 @@ class _AuthChoiceScreenState extends ConsumerState { ), const SizedBox(height: 24), Text( - 'LifeTimer', + '1356', style: Theme.of(context) .textTheme .headlineLarge diff --git a/lifetimer/lib/features/auth/presentation/auth_showcase_screen.dart b/lifetimer/lib/features/auth/presentation/auth_showcase_screen.dart index 4e95a09..9a10c06 100644 --- a/lifetimer/lib/features/auth/presentation/auth_showcase_screen.dart +++ b/lifetimer/lib/features/auth/presentation/auth_showcase_screen.dart @@ -64,7 +64,7 @@ class AuthShowcaseScreen extends ConsumerWidget { ), const SizedBox(height: 16), Text( - 'LifeTimer helps you design a 1356-day experiment, focus on a small set of meaningful goals, and see time as a single bold countdown.', + '1356 helps you design a 1356-day experiment, focus on a small set of meaningful goals, and see time as a single bold countdown.', style: theme.textTheme.bodyLarge?.copyWith( color: colorScheme.onSurface.withValues(alpha:0.7), height: 1.6, diff --git a/lifetimer/lib/features/auth/presentation/sign_in_screen.dart b/lifetimer/lib/features/auth/presentation/sign_in_screen.dart index 09349c0..75155a1 100644 --- a/lifetimer/lib/features/auth/presentation/sign_in_screen.dart +++ b/lifetimer/lib/features/auth/presentation/sign_in_screen.dart @@ -111,7 +111,7 @@ class _SignInScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'LifeTimer', + '1356', style: Theme.of(context) .textTheme .titleMedium diff --git a/lifetimer/lib/features/auth/presentation/sign_up_screen.dart b/lifetimer/lib/features/auth/presentation/sign_up_screen.dart index 8713cad..a717ef5 100644 --- a/lifetimer/lib/features/auth/presentation/sign_up_screen.dart +++ b/lifetimer/lib/features/auth/presentation/sign_up_screen.dart @@ -91,7 +91,7 @@ class _SignUpScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'LifeTimer', + '1356', style: Theme.of(context) .textTheme .titleMedium diff --git a/lifetimer/lib/features/countdown/presentation/home_countdown_screen.dart b/lifetimer/lib/features/countdown/presentation/home_countdown_screen.dart index 4354f7c..1bf6da7 100644 --- a/lifetimer/lib/features/countdown/presentation/home_countdown_screen.dart +++ b/lifetimer/lib/features/countdown/presentation/home_countdown_screen.dart @@ -30,7 +30,9 @@ class _HomeCountdownScreenState extends ConsumerState { return AppScaffold( body: SafeArea( - child: countdownState.isLoading + child: Padding( + padding: const EdgeInsets.only(top: 30.0, bottom: 30.0), + child: countdownState.isLoading ? const Center(child: LoadingIndicator()) : countdownState.error != null ? Center( @@ -156,7 +158,7 @@ class _CountdownActiveScreen extends StatelessWidget { child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ diff --git a/lifetimer/lib/features/onboarding/presentation/onboarding_how_it_works_screen.dart b/lifetimer/lib/features/onboarding/presentation/onboarding_how_it_works_screen.dart index 5c474e8..4cb039e 100644 --- a/lifetimer/lib/features/onboarding/presentation/onboarding_how_it_works_screen.dart +++ b/lifetimer/lib/features/onboarding/presentation/onboarding_how_it_works_screen.dart @@ -17,10 +17,12 @@ class OnboardingHowItWorksScreen extends ConsumerWidget { return AppScaffold( body: SafeArea( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Progress Bar and Navigation + _OnboardingProgress(currentStep: 2, totalSteps: 3), const SizedBox(height: 24), Text( 'How It Works', @@ -50,13 +52,27 @@ class OnboardingHowItWorksScreen extends ConsumerWidget { description: 'The countdown begins immediately. Track your progress and make every day count.', icon: Icons.timer, ), - const Spacer(), - PrimaryButton( - onPressed: () { - controller.completeStep('how_it_works'); - context.push('/onboarding/motivation'); - }, - text: 'Continue', + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: PrimaryButton( + onPressed: () { + controller.completeStep('how_it_works'); + context.push('/onboarding/motivation'); + }, + text: 'Continue', + ), + ), + ], ), const SizedBox(height: 16), ], @@ -159,3 +175,44 @@ class _StepCard extends StatelessWidget { ); } } + +class _OnboardingProgress extends StatelessWidget { + final int currentStep; + final int totalSteps; + + const _OnboardingProgress({ + required this.currentStep, + required this.totalSteps, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Step $currentStep of $totalSteps', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ], + ), + const SizedBox(height: 12), + LinearProgressIndicator( + value: currentStep / totalSteps, + backgroundColor: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ); + } +} diff --git a/lifetimer/lib/features/onboarding/presentation/onboarding_intro_screen.dart b/lifetimer/lib/features/onboarding/presentation/onboarding_intro_screen.dart index bf5f576..5ec14a5 100644 --- a/lifetimer/lib/features/onboarding/presentation/onboarding_intro_screen.dart +++ b/lifetimer/lib/features/onboarding/presentation/onboarding_intro_screen.dart @@ -17,10 +17,12 @@ class OnboardingIntroScreen extends ConsumerWidget { return AppScaffold( body: SafeArea( child: Padding( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Progress Bar and Navigation + _OnboardingProgress(currentStep: 1, totalSteps: 3), const SizedBox(height: 48), const Icon( Icons.timer_outlined, @@ -29,7 +31,7 @@ class OnboardingIntroScreen extends ConsumerWidget { ), const SizedBox(height: 32), Text( - 'Welcome to LifeTimer', + 'Welcome to 1356', style: Theme.of(context).textTheme.headlineLarge?.copyWith( fontWeight: FontWeight.bold, ), @@ -63,22 +65,31 @@ class OnboardingIntroScreen extends ConsumerWidget { description: 'Watch yourself grow day by day', ), const Spacer(), - PrimaryButton( - onPressed: () { - controller.completeStep('intro'); - context.push('/onboarding/how-it-works'); - }, - text: 'Get Started', - ), - const SizedBox(height: 16), - TextButton( - onPressed: () async { - await controller.skipOnboarding(); - if (context.mounted) { - context.push('/home'); - } - }, - child: const Text('Skip onboarding'), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () async { + await controller.skipOnboarding(); + if (context.mounted) { + context.push('/home'); + } + }, + child: const Text('Skip'), + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: PrimaryButton( + onPressed: () { + controller.completeStep('intro'); + context.push('/onboarding/how-it-works'); + }, + text: 'Get Started', + ), + ), + ], ), const SizedBox(height: 16), ], @@ -144,3 +155,44 @@ class _FeatureCard extends StatelessWidget { ); } } + +class _OnboardingProgress extends StatelessWidget { + final int currentStep; + final int totalSteps; + + const _OnboardingProgress({ + required this.currentStep, + required this.totalSteps, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Step $currentStep of $totalSteps', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ], + ), + const SizedBox(height: 12), + LinearProgressIndicator( + value: currentStep / totalSteps, + backgroundColor: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ); + } +} diff --git a/lifetimer/lib/features/onboarding/presentation/onboarding_motivation_screen.dart b/lifetimer/lib/features/onboarding/presentation/onboarding_motivation_screen.dart index c45c659..c950c34 100644 --- a/lifetimer/lib/features/onboarding/presentation/onboarding_motivation_screen.dart +++ b/lifetimer/lib/features/onboarding/presentation/onboarding_motivation_screen.dart @@ -16,11 +16,14 @@ class OnboardingMotivationScreen extends ConsumerWidget { return AppScaffold( body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(24.0), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Progress Bar and Navigation + _OnboardingProgress(currentStep: 3, totalSteps: 3), const SizedBox(height: 24), const Icon( Icons.psychology_outlined, @@ -65,16 +68,30 @@ class OnboardingMotivationScreen extends ConsumerWidget { title: 'Celebrate Wins', description: 'Every achievement is worth celebrating.', ), - const Spacer(), - PrimaryButton( - onPressed: () async { - controller.completeStep('motivation'); - await controller.completeOnboarding(); - if (context.mounted) { - context.push('/profile/create'); - } - }, - text: 'Get Started', + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: PrimaryButton( + onPressed: () async { + controller.completeStep('motivation'); + await controller.completeOnboarding(); + if (context.mounted) { + context.push('/profile/create'); + } + }, + text: 'Get Started', + ), + ), + ], ), const SizedBox(height: 16), ], @@ -140,3 +157,44 @@ class _MotivationCard extends StatelessWidget { ); } } + +class _OnboardingProgress extends StatelessWidget { + final int currentStep; + final int totalSteps; + + const _OnboardingProgress({ + required this.currentStep, + required this.totalSteps, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Step $currentStep of $totalSteps', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('Back'), + ), + ], + ), + const SizedBox(height: 12), + LinearProgressIndicator( + value: currentStep / totalSteps, + backgroundColor: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.3), + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ); + } +} diff --git a/lifetimer/lib/main.dart b/lifetimer/lib/main.dart index 4766160..0691acb 100644 --- a/lifetimer/lib/main.dart +++ b/lifetimer/lib/main.dart @@ -13,6 +13,7 @@ void main() async { const SystemUiOverlayStyle( statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent, + systemNavigationBarDividerColor: Colors.transparent, ), ); @@ -24,13 +25,13 @@ void main() async { runApp( const ProviderScope( - child: LifeTimerApp(), + child: App1356(), ), ); } -class LifeTimerApp extends ConsumerWidget { - const LifeTimerApp({super.key}); +class App1356 extends ConsumerWidget { + const App1356({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -38,7 +39,7 @@ class LifeTimerApp extends ConsumerWidget { final themeMode = ref.watch(themeModeProvider); return MaterialApp.router( - title: 'LifeTimer', + title: '1356', debugShowCheckedModeBanner: false, theme: AppTheme.light, darkTheme: AppTheme.dark, diff --git a/lifetimer/pubspec.yaml b/lifetimer/pubspec.yaml index cf63d0b..2378e81 100644 --- a/lifetimer/pubspec.yaml +++ b/lifetimer/pubspec.yaml @@ -1,4 +1,4 @@ -name: lifetimer +name: 1356 description: A gamified life countdown app with 1356-day challenge and bucket list tracking. publish_to: 'none' version: 1.0.0+1