sementara

This commit is contained in:
kleponijo 2026-05-23 21:07:41 +07:00
parent d7db4ece12
commit 87507f1f13
11 changed files with 218 additions and 142 deletions

View File

@ -37,4 +37,3 @@ class MyApp extends StatelessWidget {
);
}
}
// sadadda

View File

@ -1,9 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:klimatologiot/screens/auth/views/welcome_screen.dart';
import 'package:klimatologiot/screens/home/views/home_screen.dart';
import 'screens/splash/views/splash_screen.dart';
class MyAppView extends StatelessWidget {
const MyAppView({super.key});
@ -11,32 +8,25 @@ class MyAppView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Klimatologi',
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('id', 'ID'),
Locale('en'),
],
theme: ThemeData(
colorScheme: ColorScheme.light(
surface: Colors.grey.shade100,
onSurface: Colors.black,
primary: Colors.blue,
onPrimary: Colors.white),
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: ((context, state) {
if (state.status == AuthenticationStatus.authenticated) {
return HomeScreen();
} else {
return WelcomeScreen();
}
}),
));
title: 'Klimatologi',
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('id', 'ID'),
Locale('en'),
],
theme: ThemeData(
colorScheme: ColorScheme.light(
surface: Colors.grey.shade100,
onSurface: Colors.black,
primary: Colors.blue,
onPrimary: Colors.white),
),
home: const SplashScreen(),
);
}
}

View File

@ -12,7 +12,6 @@ void main() async {
options: DefaultFirebaseOptions.currentPlatform,
);
await initializeDateFormatting('id_ID', null);
await Future.delayed(const Duration(seconds: 2));
runApp(MyApp(
FirebaseUserRepo(),
FirebaseMonitoringRepo(),

View File

@ -28,7 +28,5 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
emit(SignInFailure());
}
});
on<SignOutRequired>((event, emit) async => await _userRepository.logOut());
}
}

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:user_repository/user_repository.dart';
import 'package:firebase_auth/firebase_auth.dart';
part 'sign_up_event.dart';
part 'sign_up_state.dart';
@ -12,13 +13,27 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
on<SignUpRequired>((event, emit) async {
emit(SignUpProcess());
try {
MyUser myUser =
await _userRepository.signUp(event.user, event.password);
await _userRepository.setUserData(myUser);
await _userRepository.signUp(event.user, event.password);
emit(SignUpSuccess());
} catch (e) {
emit(SignUpFailure(e.toString()));
String message = 'Terjadi kesalahan. Coba lagi.';
if (e is FirebaseAuthException) {
switch (e.code) {
case 'email-already-in-use':
message = 'Email sudah terdaftar.';
break;
case 'weak-password':
message = 'Password terlalu lemah.';
break;
case 'invalid-email':
message = 'Format email tidak valid.';
break;
}
}
emit(SignUpFailure(message));
}
});
}
}
// 2 / 5

View File

@ -39,12 +39,6 @@ class _SignInScreenState extends State<SignInScreen> {
if (state is SignInFailure) {
setState(() => _errorMsg = 'Email atau password salah. Coba lagi.');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_errorMsg!),
backgroundColor: Colors.red,
),
);
} else if (state is SignInSuccess) {
setState(() => _errorMsg = null);
}
@ -159,7 +153,7 @@ class _SignInScreenState extends State<SignInScreen> {
),
const SizedBox(height: 16),
// Tombol Google — hanya muncul ketika tidak loading
// // Tombol Google — hanya muncul ketika tidak loading
SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
child: TextButton.icon(

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:user_repository/user_repository.dart';
import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart'; // ← tambah
import 'package:klimatologiot/screens/home/views/home_screen.dart';
import '../blocs/sign_in_bloc/sign_in_bloc.dart';
import '../blocs/sign_up_bloc/sign_up_bloc.dart';
@ -32,54 +34,65 @@ class _WelcomeScreenState extends State<WelcomeScreen>
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 50.0),
child: TabBar(
controller: tabController,
unselectedLabelColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
labelColor: Theme.of(context).colorScheme.onSurface,
tabs: const [
Padding(
padding: EdgeInsets.all(12.0),
child: Text('Sign In', style: TextStyle(fontSize: 18)),
),
Padding(
padding: EdgeInsets.all(12.0),
child: Text('Sign Up', style: TextStyle(fontSize: 18)),
),
],
),
),
// FIX: Expanded supaya TabBarView mengisi sisa tinggi layar
// dengan ini scroll di dalam tab bisa berjalan normal
Expanded(
child: TabBarView(
controller: tabController,
children: [
BlocProvider<SignInBloc>(
create: (context) =>
// FIX: akses UserRepository langsung, bukan via AuthBloc
SignInBloc(context.read<UserRepository>()),
child: const SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) =>
SignUpBloc(context.read<UserRepository>()),
child: SignUpScreen(
onSignInTap: () => tabController.animateTo(0),
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => HomeScreen()),
(route) => false, // hapus semua route lama (termasuk SplashScreen)
);
}
},
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 50.0),
child: TabBar(
controller: tabController,
unselectedLabelColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
labelColor: Theme.of(context).colorScheme.onSurface,
tabs: const [
Padding(
padding: EdgeInsets.all(12.0),
child: Text('Sign In', style: TextStyle(fontSize: 18)),
),
),
],
Padding(
padding: EdgeInsets.all(12.0),
child: Text('Sign Up', style: TextStyle(fontSize: 18)),
),
],
),
),
),
],
// FIX: Expanded supaya TabBarView mengisi sisa tinggi layar
// dengan ini scroll di dalam tab bisa berjalan normal
Expanded(
child: TabBarView(
controller: tabController,
children: [
BlocProvider<SignInBloc>(
create: (context) =>
// FIX: akses UserRepository langsung, bukan via AuthBloc
SignInBloc(context.read<UserRepository>()),
child: const SignInScreen(),
),
BlocProvider<SignUpBloc>(
create: (context) =>
SignUpBloc(context.read<UserRepository>()),
child: SignUpScreen(
onSignInTap: () => tabController.animateTo(0),
),
),
],
),
),
],
),
),
),
);

View File

@ -1,8 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../auth/views/welcome_screen.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:klimatologiot/screens/auth/views/welcome_screen.dart';
import 'package:klimatologiot/screens/home/views/home_screen.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@ -15,15 +17,36 @@ class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
_navigateAfterSplash();
}
Timer(const Duration(seconds: 3), () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const WelcomeScreen(),
),
);
});
Future<void> _navigateAfterSplash() async {
// Tunggu minimal 3 detik (efek splash)
await Future.delayed(const Duration(seconds: 3));
if (!mounted) return;
final bloc = context.read<AuthenticationBloc>();
// Kalau auth state masih unknown (Firebase belum resolve), tunggu dulu
AuthenticationState state = bloc.state;
if (state.status == AuthenticationStatus.unknown) {
state = await bloc.stream
.firstWhere((s) => s.status != AuthenticationStatus.unknown)
.timeout(
const Duration(seconds: 5),
onTimeout: () => const AuthenticationState.unauthenticated(),
);
}
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => state.status == AuthenticationStatus.authenticated
? HomeScreen()
: WelcomeScreen(),
),
);
}
@override
@ -35,7 +58,7 @@ class _SplashScreenState extends State<SplashScreen> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo.png',
'images/logo_klimatologi.png',
width: 140,
),
const SizedBox(height: 24),

View File

@ -1,16 +1,14 @@
class MyUserEntity {
String userId;
String email;
String name;
bool hasActiveCart;
final String userId;
final String email;
final String name;
final bool hasActiveCart;
MyUserEntity ({
MyUserEntity({
required this.userId,
required this.email,
required this.name,
required this.hasActiveCart,
});
Map<String, Object?> toDocument() {
@ -19,17 +17,17 @@ class MyUserEntity {
'email': email,
'name': name,
'hasActiveCart': hasActiveCart,
};
}
static MyUserEntity fromDocument(Map<String, dynamic> doc) {
return MyUserEntity(
userId: doc['userId'] as String,
email: doc['email'] as String,
name: doc['name'] as String,
hasActiveCart: doc['hasActiveCart'] as bool,
userId: doc['userId'] as String? ?? '',
email: doc['email'] as String? ?? '',
name: doc['name'] as String? ?? '',
hasActiveCart: doc['hasActiveCart'] as bool? ?? false,
);
}
}
}
// 3

View File

@ -3,14 +3,11 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:rxdart/rxdart.dart';
import 'package:user_repository/user_repository.dart';
// Google Sign-In import di-disable sementara karena error analisis:
// "Target of URI doesn't exist: package:google_sign_in/google_sign_in.dart".
// Implementasi Google Sign-In (kecuali untuk web auth) akan dilewatkan.
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseUserRepo implements UserRepository {
final FirebaseAuth _firebaseAuth;
final _googleSignIn = GoogleSignIn();
final userCollection = FirebaseFirestore.instance.collection('users');
FirebaseUserRepo({FirebaseAuth? firebaseAuth})
@ -29,7 +26,12 @@ class FirebaseUserRepo implements UserRepository {
MyUserEntity.fromDocument(userData.data()!));
} else {
// Data Firestore belum ada (seharusnya tidak terjadi dengan flow yang sudah diperbaiki)
yield MyUser.empty;
yield MyUser(
userId: firebaseUser.uid,
email: firebaseUser.email ?? '',
name: firebaseUser.displayName ?? '',
hasActiveCart: false,
);
}
} catch (e) {
log('Error fetching user data: $e');
@ -53,27 +55,29 @@ class FirebaseUserRepo implements UserRepository {
@override
Future<MyUser> signUp(MyUser myUser, String password) async {
try {
UserCredential user = await _firebaseAuth.createUserWithEmailAndPassword(
email: myUser.email, password: password);
myUser.userId = user.user!.uid;
return myUser;
UserCredential credential =
await _firebaseAuth.createUserWithEmailAndPassword(
email: myUser.email, password: password);
final newUser = MyUser(
userId: credential.user!.uid,
email: myUser.email,
name: myUser.name,
hasActiveCart: false,
);
await setUserData(newUser);
return newUser;
} catch (e) {
log(e.toString());
rethrow;
}
}
// NOTE: Google Sign-In disabled because dependency import fails on this workspace.
// After google_sign_in issue resolved, restore the implementation.
@override
Future<void> logOut() async {
// GoogleSignIn sementara di-skip karena error analisis import.
await _googleSignIn.signOut();
await _firebaseAuth.signOut();
}
@override
Future<void> setUserData(MyUser myUser) async {
try {
@ -88,9 +92,34 @@ class FirebaseUserRepo implements UserRepository {
@override
Future<void> signInWithGoogle() async {
// Google Sign-In sementara dimatikan agar build tidak gagal.
// Setelah dependency google_sign_in benar-benar resolvable, bagian ini bisa dikembalikan.
throw UnimplementedError('Google Sign-In disabled (analysis fix)');
}
try {
final googleUser = await _googleSignIn.signIn();
if (googleUser == null) return; // user cancel
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final userCredential =
await _firebaseAuth.signInWithCredential(credential);
final firebaseUser = userCredential.user!;
final doc = await userCollection.doc(firebaseUser.uid).get();
if (!doc.exists) {
// User baru via Google — buat doc Firestore
final myUser = MyUser(
userId: firebaseUser.uid,
email: firebaseUser.email ?? '',
name: firebaseUser.displayName ?? '',
hasActiveCart: false,
);
await setUserData(myUser);
}
} catch (e) {
log(e.toString());
rethrow;
}
}
}

View File

@ -1,25 +1,43 @@
import '../entities/entities.dart';
class MyUser {
String userId;
String email;
String name;
bool hasActiveCart;
final String userId; // ← final semua
final String email;
final String name;
final bool hasActiveCart;
MyUser({
const MyUser({
required this.userId,
required this.email,
required this.name,
required this.hasActiveCart,
});
static final empty = MyUser(
static const empty = MyUser(
userId: '',
email: '',
name: '',
hasActiveCart: false,
);
bool get isEmpty => this == empty;
bool get isNotEmpty => this != empty;
// ← tambah copyWith agar bisa "ubah" tanpa mutasi
MyUser copyWith({
String? userId,
String? email,
String? name,
bool? hasActiveCart,
}) {
return MyUser(
userId: userId ?? this.userId,
email: email ?? this.email,
name: name ?? this.name,
hasActiveCart: hasActiveCart ?? this.hasActiveCart,
);
}
MyUserEntity toEntity() {
return MyUserEntity(
userId: userId,