add open camera files and add packages
This commit is contained in:
parent
660e30f937
commit
7f3b71a40b
|
|
@ -7,7 +7,7 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.smart_vision_assist"
|
namespace = "com.example.smart_vision_assist"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = 36
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<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.CAMERA"/>
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pluginManagement {
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.1.0" apply false
|
id "com.android.application" version "8.2.1" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
|
|
||||||
|
class CameraScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_CameraScreenState createState() => _CameraScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CameraScreenState extends State<CameraScreen> {
|
||||||
|
CameraController? controller;
|
||||||
|
List<CameraDescription>? cameras;
|
||||||
|
|
||||||
|
bool isRecording = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initCamera() async {
|
||||||
|
cameras = await availableCameras();
|
||||||
|
controller = CameraController(
|
||||||
|
cameras![0],
|
||||||
|
ResolutionPreset.high,
|
||||||
|
);
|
||||||
|
|
||||||
|
await controller!.initialize();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔴 START RECORD
|
||||||
|
Future<void> startRecording() async {
|
||||||
|
if (!controller!.value.isRecordingVideo) {
|
||||||
|
await controller!.startVideoRecording();
|
||||||
|
setState(() {
|
||||||
|
isRecording = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⏹ STOP RECORD
|
||||||
|
Future<void> stopRecording() async {
|
||||||
|
if (controller!.value.isRecordingVideo) {
|
||||||
|
await controller!.stopVideoRecording();
|
||||||
|
setState(() {
|
||||||
|
isRecording = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (controller == null || !controller!.value.isInitialized) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// 📷 PREVIEW KAMERA (FULL SCREEN)
|
||||||
|
Positioned.fill(
|
||||||
|
child: CameraPreview(controller!),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 🔙 TOMBOL KEMBALI (KANAN BAWAH)
|
||||||
|
Positioned(
|
||||||
|
bottom: 40,
|
||||||
|
right: 30,
|
||||||
|
child: Semantics(
|
||||||
|
label: "Kembali",
|
||||||
|
button: true,
|
||||||
|
child: FloatingActionButton(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 🔘 TOMBOL RECORD (TENGAH BAWAH)
|
||||||
|
Positioned(
|
||||||
|
bottom: 30,
|
||||||
|
left: MediaQuery.of(context).size.width / 2 - 35,
|
||||||
|
child: Semantics(
|
||||||
|
label: isRecording ? "Stop rekam" : "Mulai rekam",
|
||||||
|
button: true,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (isRecording) {
|
||||||
|
stopRecording();
|
||||||
|
} else {
|
||||||
|
startRecording();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 70,
|
||||||
|
height: 70,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: isRecording ? Colors.red : Colors.white,
|
||||||
|
border: Border.all(color: Colors.grey, width: 4),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
isRecording ? Icons.stop : Icons.circle,
|
||||||
|
color: isRecording ? Colors.white : Colors.red,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import '../utils/colors.dart';
|
||||||
import 'home_screen.dart';
|
import 'home_screen.dart';
|
||||||
import 'signup_screen.dart';
|
import 'signup_screen.dart';
|
||||||
import 'forgot_password_screen.dart';
|
import 'forgot_password_screen.dart';
|
||||||
|
import 'camera_screen.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatelessWidget {
|
class LoginScreen extends StatelessWidget {
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
|
@ -95,8 +96,13 @@ class LoginScreen extends StatelessWidget {
|
||||||
|
|
||||||
// 🔘 LOGIN BUTTON
|
// 🔘 LOGIN BUTTON
|
||||||
CustomButton(
|
CustomButton(
|
||||||
text: "Login",
|
text: "Buka Kamera",
|
||||||
onPressed: () => handleLogin(context),
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => CameraScreen()),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 15),
|
SizedBox(height: 15),
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,35 @@ import '../widgets/custom_button.dart';
|
||||||
import '../services/tts_service.dart';
|
import '../services/tts_service.dart';
|
||||||
import '../utils/colors.dart';
|
import '../utils/colors.dart';
|
||||||
|
|
||||||
class SignUpScreen extends StatelessWidget {
|
class SignUpScreen extends StatefulWidget {
|
||||||
|
const SignUpScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SignUpScreen> createState() => _SignUpScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
final TextEditingController passwordController = TextEditingController();
|
final TextEditingController passwordController = TextEditingController();
|
||||||
final TextEditingController confirmPasswordController = TextEditingController();
|
final TextEditingController confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
final TTSService tts = TTSService();
|
final TTSService tts = TTSService();
|
||||||
|
String? selectedGender;
|
||||||
|
|
||||||
SignUpScreen({super.key});
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
tts.speak("Halaman daftar akun");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
emailController.dispose();
|
||||||
|
passwordController.dispose();
|
||||||
|
confirmPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void handleSignUp(BuildContext context) {
|
void handleSignUp(BuildContext context) {
|
||||||
String email = emailController.text;
|
String email = emailController.text;
|
||||||
|
|
@ -28,31 +49,63 @@ class SignUpScreen extends StatelessWidget {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedGender == null) {
|
||||||
|
tts.speak("Jenis kelamin wajib dipilih");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
tts.speak("Pendaftaran berhasil");
|
tts.speak("Pendaftaran berhasil");
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildGenderOption(String gender) {
|
||||||
|
return RadioListTile<String>(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
value: gender,
|
||||||
|
groupValue: selectedGender,
|
||||||
|
activeColor: AppColors.primary,
|
||||||
|
fillColor: MaterialStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return AppColors.primary;
|
||||||
|
}
|
||||||
|
return AppColors.text;
|
||||||
|
}),
|
||||||
|
title: Text(
|
||||||
|
gender,
|
||||||
|
style: TextStyle(color: AppColors.text),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedGender = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
tts.speak("Jenis kelamin $value dipilih");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
tts.speak("Halaman daftar akun");
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: AppColors.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("Sign Up"),
|
title: const Text("Sign Up"),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
label: "Email",
|
label: "Email",
|
||||||
controller: emailController,
|
controller: emailController,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
label: "Password",
|
label: "Password",
|
||||||
|
|
@ -60,7 +113,7 @@ class SignUpScreen extends StatelessWidget {
|
||||||
obscure: true,
|
obscure: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
label: "Konfirmasi Password",
|
label: "Konfirmasi Password",
|
||||||
|
|
@ -68,7 +121,36 @@ class SignUpScreen extends StatelessWidget {
|
||||||
obscure: true,
|
obscure: true,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: 30),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
Semantics(
|
||||||
|
label: "Pilih jenis kelamin",
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: AppColors.text),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Jenis Kelamin",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.text,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildGenderOption("Laki-laki"),
|
||||||
|
const Divider(color: Colors.white24, height: 1),
|
||||||
|
buildGenderOption("Perempuan"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
CustomButton(
|
CustomButton(
|
||||||
text: "Daftar",
|
text: "Daftar",
|
||||||
|
|
|
||||||
82
pubspec.lock
82
pubspec.lock
|
|
@ -33,6 +33,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
camera:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: camera
|
||||||
|
sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.6"
|
||||||
|
camera_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_android
|
||||||
|
sha256: f19ba60b61fdc7b79128191f3509455978eb945b98748fcf93e38921df502176
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.10+5"
|
||||||
|
camera_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_avfoundation
|
||||||
|
sha256: e4aca5bccaf897b70cac87e5fdd789393310985202442837922fd40325e2733b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.21+1"
|
||||||
|
camera_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_platform_interface
|
||||||
|
sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.0"
|
||||||
|
camera_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_web
|
||||||
|
sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -73,6 +113,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.19.0"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+2"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -110,6 +158,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "6382ce712ff69b0f719640ce957559dde459e55ecd433c767e06d139ddf16cab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.29"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -216,6 +272,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.2"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
posix:
|
posix:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -253,6 +317,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -293,6 +365,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.3.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -311,4 +391,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.1 <4.0.0"
|
dart: ">=3.6.1 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ environment:
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
# the latest version available on pub.dev. To see which dependencies have newer
|
||||||
# versions available, run `flutter pub outdated`.
|
# versions available, run `flutter pub outdated`.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
camera: ^0.10.5+5
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_tts: ^3.5.2
|
flutter_tts: ^3.5.2
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue