mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-05 04:22:55 +00:00
fix and improve
This commit is contained in:
@@ -84,27 +84,51 @@ class AuthRepository {
|
||||
}
|
||||
|
||||
Future<void> 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<void> _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()}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class _AuthChoiceScreenState extends ConsumerState<AuthChoiceScreen> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'LifeTimer',
|
||||
'1356',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -111,7 +111,7 @@ class _SignInScreenState extends ConsumerState<SignInScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'LifeTimer',
|
||||
'1356',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
|
||||
@@ -91,7 +91,7 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'LifeTimer',
|
||||
'1356',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
|
||||
@@ -30,7 +30,9 @@ class _HomeCountdownScreenState extends ConsumerState<HomeCountdownScreen> {
|
||||
|
||||
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: [
|
||||
|
||||
@@ -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<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user