mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-04 20:12:56 +00:00
Merge branch 'master' of https://github.com/Dvorinka/1356
This commit is contained in:
@@ -7,7 +7,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.lifetimer"
|
namespace = "com.example.app1356"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// 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.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<application
|
<application
|
||||||
android:label="lifetimer"
|
android:label="1356"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
@@ -30,7 +36,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="lifetimer" />
|
<data android:scheme="app1356" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
package com.example.lifetimer
|
package com.example.app1356
|
||||||
|
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import com.example.lifetimer.R
|
import com.example.app1356.R
|
||||||
import es.antonborri.home_widget.HomeWidgetProvider
|
import es.antonborri.home_widget.HomeWidgetProvider
|
||||||
|
|
||||||
class NextCountdownWidgetProvider : HomeWidgetProvider() {
|
class NextCountdownWidgetProvider : HomeWidgetProvider() {
|
||||||
@@ -16,7 +16,7 @@ class NextCountdownWidgetProvider : HomeWidgetProvider() {
|
|||||||
) {
|
) {
|
||||||
appWidgetIds.forEach { widgetId ->
|
appWidgetIds.forEach { widgetId ->
|
||||||
val title = widgetData.getString("next_title", "Next goal")
|
val title = widgetData.getString("next_title", "Next goal")
|
||||||
val subtitle = widgetData.getString("next_subtitle", "Open Lifetimer to see details")
|
val subtitle = widgetData.getString("next_subtitle", "Open 1356 to see details")
|
||||||
val timeLeft = widgetData.getString("next_time_left", "0 days left")
|
val timeLeft = widgetData.getString("next_time_left", "0 days left")
|
||||||
|
|
||||||
val views = RemoteViews(context.packageName, R.layout.next_countdown_widget).apply {
|
val views = RemoteViews(context.packageName, R.layout.next_countdown_widget).apply {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#1E1E1E" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#33FFFFFF" />
|
||||||
|
</shape>
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#121212"
|
android:background="#1A1A1A"
|
||||||
|
android:backgroundTint="#2A2A2A"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="12dp">
|
android:padding="12dp">
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Next goal"
|
android:text="Next goal"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
|
android:textStyle="bold"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end" />
|
android:ellipsize="end" />
|
||||||
@@ -23,7 +25,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:text="0 days left"
|
android:text="0 days left"
|
||||||
android:textColor="#FFCC66"
|
android:textColor="#4CAF50"
|
||||||
|
android:textStyle="bold"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end" />
|
android:ellipsize="end" />
|
||||||
@@ -33,7 +36,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:text="Open Lifetimer to see details"
|
android:text="Open 1356 to see details"
|
||||||
android:textColor="#B3FFFFFF"
|
android:textColor="#B3FFFFFF"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/widget_background"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- 1356 Branding -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_brand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1356"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:background="#4CAF50"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingVertical="4dp"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<!-- Days Remaining -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_days"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="1356"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="36sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
|
<!-- Label -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="days remaining"
|
||||||
|
android:textColor="#B3FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:progressTint="#4CAF50"
|
||||||
|
android:progressBackgroundTint="#33FFFFFF" />
|
||||||
|
|
||||||
|
<!-- Progress Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0% Complete"
|
||||||
|
android:textColor="#B3FFFFFF"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">1356</string>
|
||||||
|
|
||||||
|
<!-- Google Sign-In Configuration -->
|
||||||
|
<string name="default_web_client_id" translatable="false">YOUR_WEB_CLIENT_ID_HERE</string>
|
||||||
|
</resources>
|
||||||
@@ -84,27 +84,51 @@ class AuthRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> signInWithGoogle() async {
|
Future<void> signInWithGoogle() async {
|
||||||
final GoogleSignIn googleSignIn = GoogleSignIn();
|
try {
|
||||||
|
final GoogleSignIn googleSignIn = GoogleSignIn(
|
||||||
|
scopes: ['email', 'profile'],
|
||||||
|
);
|
||||||
|
|
||||||
final googleUser = await googleSignIn.signIn();
|
// Check if user is already signed in
|
||||||
if (googleUser == null) {
|
final googleUser = await googleSignIn.signInSilently();
|
||||||
throw Exception('Google sign-in was cancelled');
|
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;
|
Future<void> _handleGoogleUser(dynamic googleUser) async {
|
||||||
final idToken = googleAuth.idToken;
|
try {
|
||||||
|
final googleAuth = await googleUser.authentication;
|
||||||
|
final idToken = googleAuth.idToken;
|
||||||
|
final accessToken = googleAuth.accessToken;
|
||||||
|
|
||||||
if (idToken == null) {
|
if (idToken == null && accessToken == null) {
|
||||||
throw Exception('No ID token from Google sign-in');
|
throw Exception('No ID token or access token from Google sign-in');
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await _client.auth.signInWithIdToken(
|
final response = await _client.auth.signInWithIdToken(
|
||||||
provider: supabase.OAuthProvider.google,
|
provider: supabase.OAuthProvider.google,
|
||||||
idToken: idToken,
|
idToken: idToken,
|
||||||
);
|
accessToken: accessToken,
|
||||||
|
);
|
||||||
|
|
||||||
if (response.user != null) {
|
if (response.user != null) {
|
||||||
await _ensureUserProfileExists(response.user!.id, response.user!);
|
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),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text(
|
||||||
'LifeTimer',
|
'1356',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.headlineLarge
|
.headlineLarge
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AuthShowcaseScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
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(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
color: colorScheme.onSurface.withValues(alpha:0.7),
|
color: colorScheme.onSurface.withValues(alpha:0.7),
|
||||||
height: 1.6,
|
height: 1.6,
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class _SignInScreenState extends ConsumerState<SignInScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'LifeTimer',
|
'1356',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium
|
.titleMedium
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'LifeTimer',
|
'1356',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium
|
.titleMedium
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class _HomeCountdownScreenState extends ConsumerState<HomeCountdownScreen> {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: countdownState.isLoading
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 30.0, bottom: 30.0),
|
||||||
|
child: countdownState.isLoading
|
||||||
? const Center(child: LoadingIndicator())
|
? const Center(child: LoadingIndicator())
|
||||||
: countdownState.error != null
|
: countdownState.error != null
|
||||||
? Center(
|
? Center(
|
||||||
@@ -156,7 +158,7 @@ class _CountdownActiveScreen extends StatelessWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
child: Padding(
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
+93
-67
@@ -16,82 +16,67 @@ class OnboardingHowItWorksScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0),
|
||||||
// Progress indicator and back button
|
child: Column(
|
||||||
Padding(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
padding: const EdgeInsets.all(24.0),
|
children: [
|
||||||
child: Row(
|
// Progress Bar and Navigation
|
||||||
children: [
|
_OnboardingProgress(currentStep: 2, totalSteps: 3),
|
||||||
IconButton(
|
const SizedBox(height: 24),
|
||||||
onPressed: () {
|
Text(
|
||||||
context.pop();
|
'How It Works',
|
||||||
},
|
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||||
icon: const Icon(Icons.arrow_back),
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
Expanded(
|
textAlign: TextAlign.center,
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: 2 / 3, // Step 2 of 3
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 48), // Balance the back button
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
// Scrollable content
|
const _StepCard(
|
||||||
Expanded(
|
number: 1,
|
||||||
child: SingleChildScrollView(
|
title: 'Create Your Bucket List',
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
description: 'Add between 1 and 20 goals you want to achieve. Each goal can have a description, location, and image.',
|
||||||
child: Column(
|
icon: Icons.edit_note,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
),
|
||||||
children: [
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 8),
|
const _StepCard(
|
||||||
Text(
|
number: 2,
|
||||||
'How It Works',
|
title: 'Finalize Your List',
|
||||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
description: 'Once you\'re happy with your goals, confirm your bucket list. This action cannot be undone.',
|
||||||
fontWeight: FontWeight.bold,
|
icon: Icons.lock,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
const SizedBox(height: 16),
|
||||||
|
const _StepCard(
|
||||||
|
number: 3,
|
||||||
|
title: 'Start Your 1356-Day Journey',
|
||||||
|
description: 'The countdown begins immediately. Track your progress and make every day count.',
|
||||||
|
icon: Icons.timer,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
child: const Text('Back'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
const _StepCard(
|
const SizedBox(width: 16),
|
||||||
number: 1,
|
Expanded(
|
||||||
title: 'Create Your Bucket List',
|
flex: 2,
|
||||||
description: 'Add between 1 and 20 goals you want to achieve. Each goal can have a description, location, and image.',
|
child: PrimaryButton(
|
||||||
icon: Icons.edit_note,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const _StepCard(
|
|
||||||
number: 2,
|
|
||||||
title: 'Finalize Your List',
|
|
||||||
description: 'Once you\'re happy with your goals, confirm your bucket list. This action cannot be undone.',
|
|
||||||
icon: Icons.lock,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const _StepCard(
|
|
||||||
number: 3,
|
|
||||||
title: 'Start Your 1356-Day Journey',
|
|
||||||
description: 'The countdown begins immediately. Track your progress and make every day count.',
|
|
||||||
icon: Icons.timer,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
PrimaryButton(
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
controller.completeStep('how_it_works');
|
controller.completeStep('how_it_works');
|
||||||
context.push('/onboarding/motivation');
|
context.push('/onboarding/motivation');
|
||||||
},
|
},
|
||||||
text: 'Continue',
|
text: 'Continue',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -190,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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,105 +16,84 @@ class OnboardingIntroScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0),
|
||||||
// Progress indicator and back button
|
child: Column(
|
||||||
Padding(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
padding: const EdgeInsets.all(24.0),
|
children: [
|
||||||
child: Row(
|
// Progress Bar and Navigation
|
||||||
children: [
|
_OnboardingProgress(currentStep: 1, totalSteps: 3),
|
||||||
IconButton(
|
const SizedBox(height: 48),
|
||||||
onPressed: () {
|
const Icon(
|
||||||
// Can't go back from intro, go to auth choice
|
Icons.timer_outlined,
|
||||||
context.push('/auth-choice');
|
size: 100,
|
||||||
},
|
color: null,
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: 1 / 3, // Step 1 of 3
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 48), // Balance the back button
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
// Scrollable content
|
Text(
|
||||||
Expanded(
|
'Welcome to 1356',
|
||||||
child: SingleChildScrollView(
|
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
fontWeight: FontWeight.bold,
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
textAlign: TextAlign.center,
|
||||||
children: [
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Icon(
|
Text(
|
||||||
Icons.timer_outlined,
|
'Your 1356-day journey starts here.\nCreate your bucket list and begin your countdown.',
|
||||||
size: 64,
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
color: null,
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||||
),
|
height: 1.5,
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Text(
|
textAlign: TextAlign.center,
|
||||||
'Welcome to LifeTimer',
|
),
|
||||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
const SizedBox(height: 48),
|
||||||
fontWeight: FontWeight.bold,
|
const _FeatureCard(
|
||||||
),
|
icon: Icons.flag,
|
||||||
textAlign: TextAlign.center,
|
title: 'Set Your Goals',
|
||||||
),
|
description: 'Create a bucket list of 1-20 meaningful goals',
|
||||||
const SizedBox(height: 10),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
'Your 1356-day journey starts here.\nCreate your bucket list and begin your countdown.',
|
const _FeatureCard(
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
icon: Icons.lock_clock,
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
title: 'Fixed Timeline',
|
||||||
height: 1.5,
|
description: '1356 days to achieve everything - no extensions',
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
const SizedBox(height: 16),
|
||||||
),
|
const _FeatureCard(
|
||||||
const SizedBox(height: 24),
|
icon: Icons.trending_up,
|
||||||
const _FeatureCard(
|
title: 'Track Progress',
|
||||||
icon: Icons.flag,
|
description: 'Watch yourself grow day by day',
|
||||||
title: 'Set Your Goals',
|
),
|
||||||
description: 'Create a bucket list of 1-20 meaningful goals',
|
const Spacer(),
|
||||||
),
|
Row(
|
||||||
const SizedBox(height: 10),
|
children: [
|
||||||
const _FeatureCard(
|
Expanded(
|
||||||
icon: Icons.lock_clock,
|
child: TextButton(
|
||||||
title: 'Fixed Timeline',
|
|
||||||
description: '1356 days to achieve everything - no extensions',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const _FeatureCard(
|
|
||||||
icon: Icons.trending_up,
|
|
||||||
title: 'Track Progress',
|
|
||||||
description: 'Watch yourself grow day by day',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
PrimaryButton(
|
|
||||||
onPressed: () {
|
|
||||||
controller.completeStep('intro');
|
|
||||||
context.push('/onboarding/how-it-works');
|
|
||||||
},
|
|
||||||
text: 'Get Started',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await controller.skipOnboarding();
|
await controller.skipOnboarding();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.push('/home');
|
context.push('/home');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('Skip onboarding'),
|
child: const Text('Skip'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
],
|
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),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -176,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,100 +16,86 @@ class OnboardingMotivationScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
children: [
|
child: Padding(
|
||||||
// Progress indicator and back button
|
padding: const EdgeInsets.only(top: 30.0, left: 24.0, right: 24.0, bottom: 30.0),
|
||||||
Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.all(24.0),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
// Progress Bar and Navigation
|
||||||
IconButton(
|
_OnboardingProgress(currentStep: 3, totalSteps: 3),
|
||||||
onPressed: () {
|
const SizedBox(height: 24),
|
||||||
context.pop();
|
const Icon(
|
||||||
},
|
Icons.psychology_outlined,
|
||||||
icon: const Icon(Icons.arrow_back),
|
size: 80,
|
||||||
),
|
color: Colors.amber,
|
||||||
Expanded(
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: 3 / 3, // Step 3 of 3 (complete)
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 48), // Balance the back button
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 24),
|
||||||
// Scrollable content
|
Text(
|
||||||
Expanded(
|
'Your Time is Now',
|
||||||
child: SingleChildScrollView(
|
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
fontWeight: FontWeight.bold,
|
||||||
child: Column(
|
),
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
textAlign: TextAlign.center,
|
||||||
children: [
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 16),
|
||||||
const Icon(
|
Text(
|
||||||
Icons.psychology_outlined,
|
'1356 days is approximately 3 years and 8 months.\n\n'
|
||||||
size: 64,
|
'That\'s enough time to transform your life, learn new skills, '
|
||||||
color: Colors.amber,
|
'build meaningful relationships, and achieve your biggest dreams.\n\n'
|
||||||
|
'Every day counts. Every step matters. Your journey begins now.',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
const _MotivationCard(
|
||||||
|
icon: Icons.trending_up,
|
||||||
|
title: 'Track Progress',
|
||||||
|
description: 'Watch yourself grow as you complete goals and milestones.',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const _MotivationCard(
|
||||||
|
icon: Icons.people,
|
||||||
|
title: 'Join Community',
|
||||||
|
description: 'Connect with others on similar journeys (optional).',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const _MotivationCard(
|
||||||
|
icon: Icons.celebration,
|
||||||
|
title: 'Celebrate Wins',
|
||||||
|
description: 'Every achievement is worth celebrating.',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
child: const Text('Back'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
const SizedBox(width: 16),
|
||||||
'Your Time is Now',
|
Expanded(
|
||||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
flex: 2,
|
||||||
fontWeight: FontWeight.bold,
|
child: PrimaryButton(
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
'1356 days is approximately 3 years and 8 months.\n\n'
|
|
||||||
'That\'s enough time to transform your life, learn new skills, '
|
|
||||||
'build meaningful relationships, and achieve your biggest dreams.\n\n'
|
|
||||||
'Every day counts. Every step matters. Your journey begins now.',
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
const _MotivationCard(
|
|
||||||
icon: Icons.trending_up,
|
|
||||||
title: 'Track Progress',
|
|
||||||
description: 'Watch yourself grow as you complete goals and milestones.',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const _MotivationCard(
|
|
||||||
icon: Icons.people,
|
|
||||||
title: 'Join Community',
|
|
||||||
description: 'Connect with others on similar journeys (optional).',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const _MotivationCard(
|
|
||||||
icon: Icons.celebration,
|
|
||||||
title: 'Celebrate Wins',
|
|
||||||
description: 'Every achievement is worth celebrating.',
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
PrimaryButton(
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
controller.completeStep('motivation');
|
controller.completeStep('motivation');
|
||||||
await controller.completeOnboarding();
|
await controller.completeOnboarding();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.push('/profile-setup');
|
context.push('/profile/create');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: 'Get Started',
|
text: 'Get Started',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -171,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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ void main() async {
|
|||||||
const SystemUiOverlayStyle(
|
const SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
systemNavigationBarColor: Colors.transparent,
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -28,13 +29,13 @@ void main() async {
|
|||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
const ProviderScope(
|
const ProviderScope(
|
||||||
child: LifeTimerApp(),
|
child: App1356(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class LifeTimerApp extends ConsumerWidget {
|
class App1356 extends ConsumerWidget {
|
||||||
const LifeTimerApp({super.key});
|
const App1356({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -42,7 +43,7 @@ class LifeTimerApp extends ConsumerWidget {
|
|||||||
final themeMode = ref.watch(themeModeProvider);
|
final themeMode = ref.watch(themeModeProvider);
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
title: 'LifeTimer',
|
title: '1356',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: AppTheme.light,
|
theme: AppTheme.light,
|
||||||
darkTheme: AppTheme.dark,
|
darkTheme: AppTheme.dark,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: lifetimer
|
name: 1356
|
||||||
description: A gamified life countdown app with 1356-day challenge and bucket list tracking.
|
description: A gamified life countdown app with 1356-day challenge and bucket list tracking.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|||||||
Reference in New Issue
Block a user