NOt publised, some permissions issue

rename-package
vinay kumar 2025-10-17 12:25:27 +05:30
parent 39a65e00e3
commit 8a60842bec
17 changed files with 164 additions and 82 deletions

View File

@ -92,8 +92,8 @@ android {
applicationId 'com.vecmocon.driversaathi'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 5
versionName "1.0.0"
versionCode 11
versionName "2.0.0"
}
signingConfigs {
debug {

View File

@ -1,10 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.vecmocon.driversaathi" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>

View File

@ -2,7 +2,7 @@
"expo": {
"name": "Driver Saathi",
"slug": "BatteryAsAService",
"version": "1.0.0",
"version": "2.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "batteryasaservice",
@ -23,7 +23,7 @@
},
"edgeToEdgeEnabled": true,
"package": "com.vecmocon.driversaathi",
"versionCode": 5
"versionCode": 11
},
"web": {
"bundler": "metro",

View File

@ -8,7 +8,6 @@ import { useSnackbar } from "@/contexts/Snackbar";
import NetInfo from "@react-native-community/netinfo";
import { getPaymentSummary, getUserDetails } from "@/store/userSlice";
import { useDispatch } from "react-redux";
import { logout } from "@/store/authSlice";
import { getLanguage, setLanguage } from "@/services/i18n";
import { useTranslation } from "react-i18next";

View File

@ -25,7 +25,7 @@ import { useSnackbar } from "@/contexts/Snackbar";
import CustomerCareIcon from "../../assets/icons/customer-care.svg";
import api from "@/services/axiosClient";
import PaymentHistoryCard from "@/components/Payments/PaymentHistoryCard";
import { BASE_URL } from "@/constants/config";
import { BASE_URL, STORAGE_KEYS } from "@/constants/config";
import { useDispatch } from "react-redux";
import {
setAdvanceBalance,
@ -39,6 +39,7 @@ import RefreshIcon from "@/assets/icons/refresh.svg";
import CustomerSupport from "@/components/home/CustomerSupportModal";
import { useTranslation } from "react-i18next";
import { Image } from "expo-image";
import AsyncStorage from "@react-native-async-storage/async-storage";
export interface MyPlan {
no_of_emi: number;
@ -149,6 +150,12 @@ export default function PaymentsTabScreen() {
const fetchEmiDetails = async () => {
try {
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
if (!token) {
console.log("No auth token found, skipping API call");
return;
}
setIsLoading(true);
setEmiDetails(null);
setAdvanceBalance(null);
@ -218,6 +225,8 @@ export default function PaymentsTabScreen() {
spinValue.setValue(0);
};
const { isLoggedIn } = useSelector((state: RootState) => state.auth);
const spin = spinValue.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
@ -225,10 +234,12 @@ export default function PaymentsTabScreen() {
useFocusEffect(
useCallback(() => {
if (isLoggedIn) {
setShowFullHistory(false);
fetchEmiDetails();
fetchPaymentHistory(1, false);
}, [])
}
}, [isLoggedIn])
);
useLayoutEffect(() => {
@ -319,6 +330,12 @@ export default function PaymentsTabScreen() {
pageNumber: number = 1,
isLoadMore: boolean = false
) => {
const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
if (!token) {
console.log("No auth token found, skipping API call");
return;
}
try {
if (isLoadMore) {
setIsLoadingMore(true);

View File

@ -42,22 +42,28 @@ interface FormValues {
interface Issue {
id: number;
name: string;
name: {
en: String;
hi: String;
};
}
const validationSchema = Yup.object().shape({
serviceType: Yup.string().required("Service Type is required"),
export default function ServiceFormScreen(): JSX.Element {
const { t } = useTranslation();
const validationSchema = Yup.object().shape({
serviceType: Yup.string().required(t("service.service-type-is-required")),
issues: Yup.array().when("serviceType", {
is: (val: string) => val !== "Regular",
then: (schema) => schema.min(1, "At least one issue is required"),
then: (schema) =>
schema.min(1, t("service.atleast-one-issue-is-required")),
otherwise: (schema) => schema.notRequired(),
}),
date: Yup.date().required("Date and Time is required"),
photos: Yup.array().min(1, "At least one photo is required"),
date: Yup.date().required(t("service.date-and-time-is-required")),
photos: Yup.array().min(1, t("service.atleast-one-photo-is-required")),
comments: Yup.string(),
});
});
export default function ServiceFormScreen(): JSX.Element {
const [isFocus, setIsFocus] = useState<boolean>(false);
const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false);
@ -196,26 +202,43 @@ export default function ServiceFormScreen(): JSX.Element {
});
};
const { t } = useTranslation();
const handleServiceTypeChange = async (
item: { label: string; value: string },
setFieldValue: (field: string, value: any) => void,
setFieldTouched: (field: string, touched: boolean) => void
) => {
setFieldValue("serviceType", item.value);
if (item.value === "Regular") {
// Regular service can be selected immediately
setFieldValue("serviceType", item.value);
setFieldValue("issues", []);
setFieldTouched("issues", false);
setIssues([]);
setIsFocus(false);
} else if (item.value === "On-demand") {
await fetchIssues();
try {
setFieldValue("serviceType", item.value);
setIsLoadingIssues(true);
const response = await api.get(`${BASE_URL}/api/v1/service-issue-list`);
if (response.data.success) {
setIssues(response.data.data);
setFieldValue("serviceType", item.value);
setFieldValue("issues", []);
setFieldTouched("issues", false);
}
setIsFocus(false);
} else {
setFieldValue("serviceType", null);
throw new Error(response.data?.message || "Failed to fetch issues");
}
} catch (error: any) {
console.error("Error fetching issues:", error);
showSnackbar(`${t("common.something-went-wrong")}`, "error");
setIssues([]);
setFieldValue("serviceType", null);
} finally {
setIsLoadingIssues(false);
}
}
};
const getSelectedIssuesText = (selectedIssueIds: number[]) => {

View File

@ -40,8 +40,8 @@ export default function WelcomeScreen() {
const phoneValidationSchema = Yup.object().shape({
phone: Yup.string()
.required("Phone number is required")
.matches(/^\d{10}$/, "Phone number must be exactly 10 digits"),
.required(t("onboarding.phone-number-is-required"))
.matches(/^\d{10}$/, t("onboarding.must-be-10-digits")),
});
useEffect(() => {
if (status === AUTH_STATUSES.SUCCESS && otpId) {

View File

@ -4,8 +4,6 @@ import {
Text,
TouchableOpacity,
StyleSheet,
Alert,
Share,
Linking,
BackHandler,
ScrollView,
@ -192,7 +190,9 @@ const UpiPaymentScreen = () => {
};
function handlePaymentDone() {
router.navigate("/(tabs)/payments");
offPaymentConfirmation();
disconnect();
router.replace("/(tabs)/payments");
}
const { t } = useTranslation();

View File

@ -24,20 +24,21 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
import { displayValue, formatCurrency } from "@/utils/Common";
const validationSchema = Yup.object().shape({
const SelectAmountScreen = () => {
const validationSchema = Yup.object().shape({
paymentType: Yup.string().required("Please select a payment option"),
customAmount: Yup.string().when("paymentType", {
is: "custom",
then: (schema) =>
schema
.required("Amount is required")
.required(t("payment.amount-is-required"))
.test("valid-number", "Please enter a valid amount", (value) => {
const numValue = parseFloat(value);
return !isNaN(numValue) && numValue > 0;
})
.test(
"min-amount",
`Minimum amount is${payments.MIN_AMOUNT}`,
`${t("payment.minimum")}:${payments.MIN_AMOUNT}`,
(value) => {
const numValue = parseFloat(value);
return !isNaN(numValue) && numValue >= payments.MIN_AMOUNT;
@ -45,9 +46,8 @@ const validationSchema = Yup.object().shape({
),
otherwise: (schema) => schema.notRequired(),
}),
});
});
const SelectAmountScreen = () => {
const dueAmount = useSelector(
(state: RootState) => state.payments?.due_amount || 0
);
@ -365,7 +365,8 @@ const SelectAmountScreen = () => {
</Text>
) : (
<Text style={styles.payButtonText}>
Pay {displayValue(getPaymentAmount(), formatCurrency)}
{`${t("payment.pay")}`}{" "}
{displayValue(getPaymentAmount(), formatCurrency)}
</Text>
)}
</TouchableOpacity>

View File

@ -23,6 +23,7 @@ import { setUserData } from "@/store/userSlice";
import { Overlay } from "@/components/common/Overlay";
import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
import { clearStackAndRouteTo } from "@/utils/Common";
export default function ProfileScreen() {
const [isLangaugeModalVisible, setLanguageModalVisible] =
@ -43,7 +44,7 @@ export default function ProfileScreen() {
const handleLogout = () => {
dispatch(logout());
router.replace("/auth/login");
clearStackAndRouteTo("/auth/login");
};
const handlePickImage = async () => {

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import {
View,
Text,
@ -11,10 +11,15 @@ import {
import Checkbox from "expo-checkbox";
import { issueConfig } from "@/constants/config";
import CloseIcon from "@/assets/icons/close.svg";
import { getLanguage } from "@/services/i18n";
import { useFocusEffect } from "expo-router";
interface Issue {
id: number;
name: string;
name: {
en: String;
hi: String;
};
}
interface IssueSelectorModalProps {
@ -37,6 +42,8 @@ export default function IssueSelectorModal({
);
const [search, setSearch] = useState("");
const [lang, setLang] = useState<"en" | "hi">("en");
const toggleValue = (id: number) => {
setSelectedValues((prev) =>
prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id]
@ -44,7 +51,7 @@ export default function IssueSelectorModal({
};
const filteredIssues = issues.filter((issue) =>
issue.name.toLowerCase().includes(search.toLowerCase().trim())
issue.name[lang].toLowerCase().includes(search.toLowerCase().trim())
);
const clearSelection = () => setSelectedValues([]);
@ -73,6 +80,17 @@ export default function IssueSelectorModal({
}
}, [visible, initialSelectedValues]);
useFocusEffect(
useCallback(() => {
const fetchLang = async () => {
const selectedLang = await getLanguage();
setLang((selectedLang as "en") || "hi");
};
fetchLang();
}, []) // dependencies, can include anything that triggers refetch
);
return (
<Modal visible={visible} animationType="slide">
<View style={styles.container}>
@ -126,7 +144,7 @@ export default function IssueSelectorModal({
selectedValues.includes(issue.id) ? "#009E71" : undefined
}
/>
<Text style={styles.itemLabel}>{issue.name}</Text>
<Text style={styles.itemLabel}>{issue.name[lang]}</Text>
</TouchableOpacity>
))
) : issues.length === 0 ? (

View File

@ -12,9 +12,8 @@ import DangerIcon from "../assets/icons/danger.svg";
import type { BmsState } from "./types";
import { useTranslation } from "react-i18next";
export const BASE_URL = "https://dev-driver-saathi-api.vecmocon.com";
export const PAYMENT_SOCKET_BASE_URL =
"wss://dev-driver-saathi-api.vecmocon.com";
export const BASE_URL = "https://driver-saathi-api.vecmocon.com";
export const PAYMENT_SOCKET_BASE_URL = "wss://driver-saathi-api.vecmocon.com";
// export const BASE_URL = "https://46fa2cacfc37.ngrok-free.app";
// const SERVER_URL =
@ -181,7 +180,7 @@ export const issueConfig = [
];
export const payments = {
MIN_AMOUNT: 1,
MIN_AMOUNT: 200,
MAX_AMOUNT: 500000,
LINK_EXPIRED: "Payment link expired",
SOCKET_CONNECTION_TIMEOUT_IN_SECS: 5,

View File

@ -1,7 +1,7 @@
{
"name": "batteryasaservice",
"main": "expo-router/entry",
"version": "1.0.0",
"version": "2.0.0",
"scripts": {
"start": "expo start --dev-client",
"android": "expo run:android",

View File

@ -31,6 +31,7 @@ export const setLanguage = async (language: string) => {
export const getLanguage = async () => {
const lang = await AsyncStorage.getItem(STORAGE_KEYS.LANGUAGE);
if (!lang) return "en";
return lang;
};

View File

@ -9,9 +9,11 @@
"enter-otp": "Please enter OTP sent to your mobile number",
"verify-otp": "Verify OTP",
"otp-incorrect": "OTP incorrect.",
"resend-otp": "Resend OTP in",
"resend-otp": "Resend OTP ",
"verification-limit-exceeded": "Verification limit exceeded, try again later",
"send-otp": "Send OTP"
"send-otp": "Send OTP",
"phone-number-is-required": "Phone number is required",
"must-be-10-digits": "Phone number must be exactly 10 digits"
},
"navigation": {
"home": "Home",
@ -116,7 +118,9 @@
"view-plan": "View Plan",
"cancel-payment": "Cancel Payment",
"continue-payment": "Continue Payment",
"do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?"
"do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?",
"amount-is-required": "Amount is required",
"pay": "Pay"
},
"service": {
"schedule-maintenance": "Schedule Maintenance",
@ -135,7 +139,11 @@
"select-valid-time": "Select valid time",
"words": "words",
"time-must-be-between-10-and-5": "Select a time between 10 AM 5 PM.",
"regular-available-after-6-months": "Regular service available after 6 months of purchase"
"regular-available-after-6-months": "Regular service available after 6 months of purchase",
"service-type-is-required": "Service Type is required",
"atleast-one-issue-is-required": "At least one issue is required",
"date-and-time-is-required": "Date and time is required",
"atleast-one-photo-is-required": "At least one photo is required"
},
"battery": {
"battery-and-warranty": "My Battery and Warranty",

View File

@ -11,7 +11,9 @@
"otp-incorrect": "OTP गलत है।",
"resend-otp": "OTP पुनः भेजें",
"verification-limit-exceeded": "वेरिफिकेशन लिमिट पार हो गई है, बाद में फिर से कोशिश करो",
"send-otp": "OTP भेजें"
"send-otp": "OTP भेजें",
"phone-number-is-required": "फ़ोन नंबर अनिवार्य है",
"must-be-10-digits": "फ़ोन नंबर बिल्कुल 10 अंकों का होना चाहिए"
},
"navigation": {
"home": "होम",
@ -116,7 +118,9 @@
"payment-done": "भुगतान हो गया",
"cancel-payment": "भुगतान रद्द करें",
"continue-payment": "भुगतान जारी रखें",
"do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?"
"do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?",
"amount-is-required": "राशि दर्ज करें",
"pay": "भुगतान करें"
},
"service": {
"schedule-maintenance": "शेड्यूल मेंटेनेंस",
@ -135,7 +139,11 @@
"words": "शब्द",
"select-valid-time": "सही समय चुनें",
"time-must-be-between-10-and-5": "समय सुबह 10:00 बजे से शाम 5:00 बजे के बीच चुनें।",
"regular-available-after-6-months": "रेगुलर सेवा खरीद के 6 महीने बाद उपलब्ध होगी"
"regular-available-after-6-months": "रेगुलर सेवा खरीद के 6 महीने बाद उपलब्ध होगी",
"service-type-is-required": "सेवा का प्रकार चुनें",
"atleast-one-issue-is-required": "कम से कम एक समस्या चुनें",
"date-and-time-is-required": "तारीख और समय चुनें",
"atleast-one-photo-is-required": "कम से कम एक फोटो चुनें"
},
"battery": {
"battery-and-warranty": "मेरी बैटरी और वारंटी",

View File

@ -1,3 +1,6 @@
import { router, type Href } from "expo-router";
import type { NavigationOptions } from "expo-router/build/global-state/routing";
export const displayValue = (value: any, formatter?: (val: any) => string) => {
if (value === null || value === undefined || value === "") {
return "--";
@ -8,3 +11,11 @@ export const displayValue = (value: any, formatter?: (val: any) => string) => {
export const formatCurrency = (amount: number) => {
return `${amount.toLocaleString()}`;
};
export const clearStackAndRouteTo = (
link: Href,
options?: NavigationOptions
): void => {
router.dismissAll();
router.replace(link, options);
};