diff --git a/assets/language/ar.json b/assets/language/ar.json index 0a10353..5e5c41f 100644 --- a/assets/language/ar.json +++ b/assets/language/ar.json @@ -273,7 +273,61 @@ "bankDetails" : "التفاصيل المصرفية", "addBankDetails" : "إضافة تفاصيل البنك", "updateBankDetails" : "تحديث تفاصيل البنك", - "yourAccountDetails" : "تفاصيل حسابك" + "yourAccountDetails" : "تفاصيل حسابك", + "gettingStarted": "البدء", + "knowledgeAndEducation": "المعرفة والتعليم", + "complianceSecurityAndSupport": "الامتثال والأمان والدعم", + "getStartedTitle1": "كيف أبدأ في استخدام تطبيقكم؟", + "geStartedDesc1": "لبدء الاستخدام، قم بتنزيل تطبيق Tanami من متجر التطبيقات أو متجر Google Play. اتبع التعليمات على الشاشة لإنشاء حساب وإكمال عملية التسجيل. بمجرد إعداد حسابك، يمكنك استكشاف خيارات الاستثمار لدينا.", + "getStartedTitle2": "كم من المال أحتاج للبدء في الاستثمار عبر تطبيقكم؟", + "geStartedDesc2": "يختلف الحد الأدنى لمبلغ الاستثمار حسب الفرصة الاستثمارية المحددة. ستجد الحد الأدنى لمبلغ الاستثمار ضمن صفحة كل فرصة استثمارية.", + "getStartedTitle3": "كم يستغرق فتح حساب والبدء في الاستثمار؟", + "geStartedDesc3": "يمكن عادةً إكمال عملية فتح الحساب عبر الإنترنت في غضون بضع دقائق. بمجرد الموافقة على حسابك، يمكنك البدء في استكشاف والاستثمار في الأصول البديلة عبر تطبيقنا. ومع ذلك، يرجى ملاحظة أن متطلبات التحقق الإضافية أو تمويل الحساب قد تكون ضرورية، مما قد يؤثر على الجدول الزمني.", + "investmentTitle1": "ما هي أنواع الفرص الاستثمارية المتاحة عبر تطبيقكم؟", + "investmentDesc1": "يوفر تطبيقنا مجموعة من خيارات الاستثمار البديلة، بما في ذلك صناديق الأسهم الخاصة، صناديق رأس المال الاستثماري، صناديق الاستثمار العقاري، صناديق البنية التحتية، صناديق الدين الخاص، والمزيد. يمكنك اختيار الاستثمارات بناءً على تحملك للمخاطر وأهدافك الاستثمارية.", + "investmentTitle2": "كيف تختارون الفرص الاستثمارية البديلة المتاحة على تطبيقكم؟", + "investmentDesc2": "يقوم فريقنا من المتخصصين الاستثماريين ذوي الخبرة بإجراء عمليات تدقيق وبحث شاملة لتحديد واختيار مجموعة من الفرص الاستثمارية البديلة. نأخذ في الاعتبار عوامل مثل سجل الأداء، ممارسات إدارة المخاطر، خبرة مديري الصناديق، والتوافق مع فلسفتنا الاستثمارية.", + "investmentTitle3": "هل الاستثمارات البديلة مناسبة للجميع؟", + "investmentDesc3": "قد لا تكون الاستثمارات البديلة مناسبة لجميع المستثمرين. فهي أقل سيولة من الأسهم والسندات التقليدية، وقد تتضمن مخاطر أعلى مقارنة بالاستثمارات التقليدية. من المهم مراعاة تحملك للمخاطر وأهدافك الاستثمارية بعناية قبل الاستثمار. نوصي بالتشاور مع مستشار مالي لتحديد ما إذا كانت الاستثمارات البديلة تتماشى مع استراتيجيتك الاستثمارية العامة.", + "investmentTitle4": "كيف أختار الاستثمارات عبر تطبيقكم؟", + "investmentDesc4": "يوفر تطبيقنا معلومات شاملة حول كل فرصة استثمارية، بما في ذلك تاريخ الأداء، ملف المخاطر، والتفاصيل ذات الصلة. يمكنك مراجعة الاستثمارات المتاحة، إجراء الأبحاث، وتحليل البيانات لاتخاذ قرارات مستنيرة. نقدم أيضًا مقالات قصيرة وفيديوهات حول أنواع الاستثمارات المختلفة في منطقة المعرفة لدينا.", + "investmentTitle5": "كم مرة يتم تحديث أو تجديد خيارات الاستثمار؟", + "investmentDesc5": "نقوم بمراجعة وتحليل الفرص الاستثمارية بانتظام لضمان مجموعة متنوعة وديناميكية لمستخدمينا. قد يختلف تكرار التحديثات حسب ظروف السوق وتوافر الفرص الاستثمارية الجديدة. يمكنك توقع تطور عروضنا بمرور الوقت.", + "investmentTitle6": "ما هي فوائد الاستثمار في الأصول البديلة عبر تطبيقكم؟", + "investmentDesc6": "يوفر الاستثمار في الأصول البديلة عبر تطبيقنا العديد من الفوائد، بما في ذلك الوصول إلى فرص استثمارية فريدة في فئات الأصول ذات العائد المرتفع جنبًا إلى جنب مع مديري الصف الأول عالميًا. يتم اختيار فرص الاستثمار بواسطة خبراء استثماريين متمرسين يستهدفون الاستثمارات ذات العوائد المعدلة حسب المخاطر الأعلى. تلتزم Tanami بالشفافية والتوافق التام مع المستثمرين ولا تتلقى رسوم توظيف للاستثمارات المدرجة على المنصة. من خلال تطبيقنا المتطور، نسعى لتوفير تجربة استثمارية سلسة ومجزية لمستخدمينا.", + "investmentTitle7": "هل تقدمون أي ضمانات للأداء للاستثمارات التي تتم عبر تطبيقكم؟", + "investmentDesc7": "نحن لا نقدم ضمانات أداء للاستثمارات لأنها تخضع لتقلبات السوق والمخاطر الكامنة. الأداء السابق ليس مؤشراً على النتائج المستقبلية. نشجع المستخدمين على مراجعة بيانات الأداء التاريخية، ومراعاة تحملهم للمخاطر، وإجراء العناية الواجبة الشاملة قبل اتخاذ قرارات الاستثمار.", + "investmentTitle8": "هل يمكنني الوصول إلى حساب الاستثمار الخاص بي وإجراء المعاملات عبر التطبيق في أي وقت؟", + "investmentDesc8": "نعم، يوفر تطبيقنا وصولاً على مدار الساعة طوال أيام الأسبوع إلى حساب الاستثمار الخاص بك. يمكنك عرض محفظتك، وتتبع الأداء، وإجراء المعاملات في أي وقت يناسبك. تم تصميم تطبيقنا لتقديم واجهة سلسة وسهلة الاستخدام لإدارة استثماراتك البديلة.", + "investmentTitle9": "كيف يمكنني البقاء على اطلاع بأداء وأخبار استثماراتي؟", + "investmentDesc9": "نعم، يوفر تطبيقنا ميزة تتبع المحفظة التي تتيح لك مراقبة أداء استثماراتك في الوقت الفعلي. يمكنك عرض قيمة محفظتك، وتتبع عوائد الاستثمار الفردية، وتقييم الأداء العام لاستثماراتك البديلة. يمكنك أيضًا تخصيص تفضيلاتك لتلقي تحديثات الأخبار، والرؤى السوقية، والإعلانات المتعلقة بالاستثمار.", + "investmentTitle10": "ما هي أنواع التقارير والبيانات التي يمكنني توقعها من تطبيقكم؟", + "investmentDesc10": "يوفر تطبيقنا تقارير وبيانات دورية تلخص أداء محفظة استثماراتك، والمعاملات، والممتلكات. يمكنك الوصول إلى هذه التقارير داخل التطبيق أو تنزيلها لسجلاتك.", + "investmentTitle11": "ما هي الرسوم المرتبطة باستخدام تطبيقكم والاستثمار في الأصول البديلة؟", + "investmentDesc11": "يختلف هيكل الرسوم لدينا حسب الفرصة الاستثمارية المحددة. نحن شفافون بشأن رسومنا، ويمكنك العثور على معلومات مفصلة عنها في التطبيق وفي الشروط والأحكام لكل فرصة استثمارية. تلتزم Tanami بالتماشي التام مع المستثمرين ولا تتلقى رسوم توظيف للاستثمارات المدرجة على المنصة.", + "investmentTitle12": "هل يمكنني سحب استثماراتي في أي وقت؟", + "investmentDesc12": "قد تختلف سيولة الاستثمارات البديلة حسب الفرصة الاستثمارية المحددة. قد تحتوي بعض الاستثمارات على فترات قفل محددة أو قيود على الاسترداد. يوفر تطبيقنا معلومات حول شروط السيولة المرتبطة بكل استثمار، مما يتيح لك اتخاذ قرارات مستنيرة.", + "investmentTitle13": "هل هناك أي غرامات أو رسوم على السحوبات المبكرة أو التصفية؟", + "investmentDesc13": "قد تحتوي بعض الاستثمارات البديلة على شروط محددة بشأن السحوبات المبكرة أو التصفية. قد تختلف هذه الشروط حسب المنتج الاستثماري وسياسات الصندوق الأساسي. من المهم مراجعة الشروط والأحكام لكل استثمار قبل الالتزام.", + "knowledgeTitle1": "ما هو مستوى المعرفة أو الخبرة الاستثمارية التي أحتاجها لاستخدام تطبيقكم؟", + "knowledgeDesc1": "تم تصميم تطبيقنا ليكون سهل الاستخدام ومتاحًا للمستثمرين ذوي مستويات الخبرة المختلفة. سواء كنت مستثمرًا متمرسًا أو جديدًا على الاستثمارات البديلة، يوفر تطبيقنا موارد تعليمية، وتقارير تدقيق، ودعم معرفي آخر لمساعدتك في اتخاذ قرارات مستنيرة.", + "knowledgeTitle2": "هل تقدمون أي موارد تعليمية حول الاستثمارات البديلة؟", + "knowledgeDesc2": "بالتأكيد! نحن نؤمن بتمكين المستثمرين بالمعرفة. يوفر تطبيقنا موارد تعليمية مثل المقالات، والأدلة، والفيديوهات التي تغطي جوانب مختلفة من الاستثمارات البديلة، واستراتيجيات إدارة المخاطر، والرؤى السوقية. هذه الموارد متاحة في منطقة المعرفة لدينا وهي مصممة لتعزيز فهمك لمشهد الاستثمار.", + "knowledgeTitle3": "هل تقدمون أي نصائح استثمارية أو توصيات مخصصة؟", + "knowledgeDesc3": "في الوقت الحالي، لا نقدم أي توصيات استثمارية لأي من مستثمرينا. من المهم إجراء أبحاثك الخاصة، ومراجعة تفاصيل الفرص والمستندات، واستشارة مستشار مالي قبل اتخاذ قرارات الاستثمار.", + "securityTitle1": "هل هناك أي قيود على من يمكنه استخدام تطبيقكم والاستثمار في الأصول البديلة؟", + "securityDesc1": "قد تكون هناك بعض المتطلبات الأهلية أو القيود القانونية حسب ولايتك القضائية. قبل استخدام تطبيقنا والاستثمار في أي من فرصنا، سنطلب منك إكمال نموذج معرفة عميلك (KYC) بالتفصيل. نوصي أيضًا بمراجعة والامتثال للشروط والأحكام الموضحة في تطبيقنا واللوائح ذات الصلة في بلد إقامتك.", + "securityTitle2": "هل هناك أي قيود على التوافر الجغرافي لتطبيقكم وحلول الاستثمار الخاصة بكم؟", + "securityDesc2": "يتوفر تطبيقنا وحلول الاستثمار في ولايات قضائية مختارة. يرجى الرجوع إلى الشروط والأحكام الخاصة بتطبيقنا أو الاتصال بفريق الدعم لدينا للتحقق مما إذا كانت خدماتنا متاحة في بلدك أو منطقتك.", + "securityTitle3": "هل هناك أي آثار ضريبية مرتبطة بالاستثمار في الأصول البديلة؟", + "securityDesc3": "قد يكون للاستثمارات البديلة آثار ضريبية تختلف حسب الاستثمار المحدد والولاية القضائية الخاصة بك. نوصي باستشارة محترف ضرائب لفهم ومعالجة أي اعتبارات ضريبية تتعلق باستثماراتك البديلة.", + "securityTitle4": "ما مدى أمان معلوماتي الشخصية والمالية؟", + "securityDesc4": "نحن نولي أولوية قصوى لأمان بياناتك. يستخدم تطبيقنا بروتوكولات تشفير قياسية لحماية معلوماتك الشخصية والمالية. نلتزم أيضًا بسياسات خصوصية صارمة ونمتثل للأنظمة ذات الصلة لضمان سرية وسلامة بياناتك.", + "securityTitle5": "كيف يمكنني الحصول على الدعم إذا كان لدي أسئلة أو واجهت مشاكل؟", + "securityDesc5": "لدينا فريق دعم عملاء مخصص لمساعدتك. يمكنك الاتصال بنا من خلال قسم الاتصال داخل التطبيق، أو عبر البريد الإلكتروني، أو الهاتف. نسعى لتقديم دعم سريع ومفيد لمعالجة أي أسئلة أو مخاوف قد تكون لديك.", + "websiteText" : "موقع إلكتروني", + "toRestorePasswordYouWillBeLoggedOut" : "لاستعادة كلمة المرور، سيتم تسجيل خروجك من التطبيق" + } \ No newline at end of file diff --git a/assets/language/en.json b/assets/language/en.json index 25bc0fb..0a77e26 100644 --- a/assets/language/en.json +++ b/assets/language/en.json @@ -273,10 +273,60 @@ "bankDetails" : "Bank details", "addBankDetails" : "Add bank details", "updateBankDetails" : "Update bank details", - "yourAccountDetails" : "Your account Details" - - - - - } + "yourAccountDetails" : "Your account Details", + "gettingStarted" : "Getting Started", + "knowledgeAndEducation" : "Knowledge and Education", + "complianceSecurityAndSupport": "Compliance, Security and Support", + "getStartedTitle1" : "How do I get started with your app?", + "geStartedDesc1" : "To get started, simply download the Tanami app from the App Store or Google Play Store. Follow the on-screen instructions to create an account and complete the registration process. Once your account is set up, you can explore our investment options.", + "getStartedTitle2" : "How much money do I need to start investing through your app?", + "geStartedDesc2" : "The minimum investment amount varies depending on the specific investment opportunity. You will find the minimum investment amount within the page of each investment opportunity.", + "getStartedTitle3" : "How long does it take to open an account and start investing?", + "geStartedDesc3" : "The account opening process can typically be completed online within a few minutes. Once your account is approved, you can start exploring and investing in alternative assets through our app. However, please note that additional verification requirements or account funding may be necessary, which may affect the timeline.", + "investmentTitle1" : "What types of investment opportunities are available through your app?", + "investmentDesc1" : "Our app offers a range of alternative investment options, including private equity funds, venture capital funds, real estate investment funds, infrastructure funds, private debt funds, and more. You can choose investments based on your risk tolerance and investment goals.", + "investmentTitle2" : "How do you select the alternative investment opportunities available on your app?", + "investmentDesc2" : "Our team of experienced investment professionals conducts extensive due diligence and research to identify and select a range of alternative investment opportunities. We consider factors such as performance track record, risk management practices, fund managers' expertise, and alignment with our investment philosophy.", + "investmentTitle3" : "Are alternative investments suitable for everyone?", + "investmentDesc3" : "Alternative investments may not be suitable for all investors. They are less liquid than traditional stocks and bonds, and may involve higher risks compared to traditional investments, and it is important to carefully consider your risk tolerance and investment objectives before investing. We recommend consulting with a financial advisor to determine if alternative investments align with your overall investment strategy.", + "investmentTitle4" : "How do I select investments through your app?", + "investmentDesc4" : "Our app provides comprehensive information about each investment opportunity, including performance history, risk profile, and relevant details. You can review the available investments, conduct research, and analyse the data to make informed decisions. We also offer short articles and videos about different types of investments in our Knowledge Zone.", + "investmentTitle5" : "How often are the investment options updated or refreshed?", + "investmentDesc5" : "We regularly review and analyze investment opportunities to ensure a diverse and dynamic selection for our users. The frequency of updates may vary depending on market conditions and the availability of new investment opportunities. You can expect our offerings to evolve over time.", + "investmentTitle6" : "What are the benefits of investing in alternative assets through your app?", + "investmentDesc6" : "Investing in alternative assets through our app offers several benefits, including access to unique investment opportunities in higher return asset classes alongside world class managers globally. Investment opportunities are vetted by seasoned investment experts that target investments with the highest risk-adjusted returns. Tanami is committed to transparency and total alignment with investors and does not receive placement fees for investments listed on the platform. Through our cutting-edge app, we strive to provide a seamless and rewarding investing experience for our users.", + "investmentTitle7" : "Do you offer any performance guarantees for the investments made through your app?", + "investmentDesc7" : "We do not provide performance guarantees for investments as they are subject to market fluctuations and inherent risks. Past performance is not indicative of future results. We encourage users to review historical performance data, consider their risk tolerance, and conduct thorough due diligence before making investment decisions.", + "investmentTitle8" : "Can I access my investment account and make transactions through the app at any time?", + "investmentDesc8" : "Yes, our app provides 24/7 access to your investment account. You can view your portfolio, track performance, and make transactions at your convenience. Our app is designed to offer a seamless and user-friendly interface for managing your alternative investments.", + "investmentTitle9" : "How can I stay updated on the performance and news related to my investments?", + "investmentDesc9" : "Yes, our app offers a portfolio tracking feature that allows you to monitor the performance of your investments in real-time. You can view your portfolio's value, track individual investment returns, and assess the overall performance of your alternative investments. You can also customize your preferences to receive news updates, market insights, and investment-related announcements.", + "investmentTitle10" : "What types of reporting and statements can I expect from your app?", + "investmentDesc10" : "Our app provides regular reports and statements that summarize your investment portfolio's performance, transactions, and holdings. You can access these reports within the app or download them for your records.", + "investmentTitle11" : "What are the fees associated with using your app and investing in alternative assets?", + "investmentDesc11" : "Our fee structure varies depending on the specific investment opportunity. We are transparent about our fees, and you can find detailed information about them in the app and in the terms and conditions of each investment opportunity. Tanami is committed to total alignment with investors and does not receive placement fees for investments listed on the platform.", + "investmentTitle12" : "Can I withdraw my investments at any time?", + "investmentDesc12" : "The liquidity of alternative investments may vary depending on the specific investment opportunity. Some investments may have specific lock-up periods or redemption restrictions. Our app provides information about the liquidity terms associated with each investment, allowing you to make informed decisions.", + "investmentTitle13" : "Are there any penalties or fees for early withdrawals or liquidation?", + "investmentDesc13" : "Some alternative investments may have specific terms regarding early withdrawals or liquidation. These terms can vary depending on the investment product and the underlying fund's policies. It is important to review the terms and conditions of each investment before making a commitment.", + "knowledgeTitle1" : "What level of investment knowledge or experience do I need to use your app?", + "knowledgeDesc1" : "Our app is designed to be user-friendly and accessible to investors with varying levels of experience. Whether you are a seasoned investor or new to alternative investments, our app provides educational resources, due diligence reports, and other knowledge support to help you make informed decisions.", + "knowledgeTitle2" : "Do you provide any educational resources on alternative investments?", + "knowledgeDesc2" : "Absolutely! We believe in empowering investors with knowledge. Our app offers educational resources such as articles, guides, and videos that cover various aspects of alternative investments, risk management strategies, and market insights. These resources are available in our Knowledge Zone and are designed to enhance your understanding of the investment landscape.", + "knowledgeTitle3" : "Do you provide any investment advice or personalized recommendations?", + "knowledgeDesc3" : "At the moment, we do not provide any investment recommendations to any of our investors. It is important to conduct your own research, review opportunity details and documents, and/or consult with a financial advisor before making investment decisions.", + "securityTitle1" : "Are there any restrictions on who can use your app and invest in alternative assets?", + "securityDesc1" : "There may be certain eligibility requirements or legal restrictions depending on your jurisdiction. Before using our app and investing in any of our opportunities, we will ask you to complete a detailed Know Your Customer (KYC) form. We also recommend reviewing and complying with the terms and conditions outlined in our app and relevant regulations in your country of residence.", + "securityTitle2" : "Are there any restrictions on the geographic availability of your app and investment solutions?", + "securityDesc2" : "Our app and investment solutions are available in select jurisdictions. Please refer to our app's terms and conditions or contact our support team to verify if our services are available in your country or region.", + "securityTitle3" : "Are there any tax implications associated with investing in alternative assets?", + "securityDesc3" : "Alternative investments may have tax implications that vary depending on the specific investment and your jurisdiction. We recommend consulting with a tax professional to understand and address any tax considerations related to your alternative investments.", + "securityTitle4" : "How secure is my personal and financial information?", + "securityDesc4" : "We prioritize the security of your data. Our app employs industry-standard encryption protocols to protect your personal and financial information. We also adhere to strict privacy policies and comply with relevant regulations to ensure the confidentiality and integrity of your data.", + "securityTitle5" : "How can I get support if I have questions or encounter issues?", + "securityDesc5" : "We have a dedicated customer support team available to assist you. You can reach out to us through our in-app contact section, email, or phone. We strive to provide timely and helpful support to address any questions or concerns you may have.", + "websiteText" : "website", + "toRestorePasswordYouWillBeLoggedOut" : "To restore password you will be logged out of the app" + + } \ No newline at end of file diff --git a/lib/Globalconst.dart b/lib/Globalconst.dart index 68c7ff8..9db5b46 100644 --- a/lib/Globalconst.dart +++ b/lib/Globalconst.dart @@ -5,5 +5,6 @@ class Globalconst { static String isdcode = ""; static String createdpin = ""; static String firstName = ""; + static String lastName = ""; static String languageSelected = "en"; } diff --git a/lib/core/routes/route_name.dart b/lib/core/routes/route_name.dart index 9470091..be3c353 100644 --- a/lib/core/routes/route_name.dart +++ b/lib/core/routes/route_name.dart @@ -81,4 +81,10 @@ class RouteName { //Bank Details static const String addBankDetailsScreen = "addBankDetailsScreen"; + + //Vimeo + static const String vimeoScreen = "vimeoScreen"; + + //PDF + static const String pdfReaderScreen = "pdfReaderScreen"; } diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index 580a789..ac2674b 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -30,6 +30,7 @@ import 'package:tanami_app/features/register/presentation/pages/register_step_sc import 'package:tanami_app/features/securePin/presentation/pages/pin_screen.dart'; import 'package:tanami_app/features/welcome/presentation/pages/weclome_screen.dart'; import 'package:tanami_app/shared/components/no_internet.dart'; +import 'package:tanami_app/shared/components/read_pdf.dart'; import '../../features/MainScreens/Wallet/presentation/pages/deposit/deposit_completed_screen.dart'; import '../../features/MainScreens/main_screen.dart'; @@ -39,6 +40,7 @@ import '../../features/login/presentation/pages/login_screen.dart'; import '../../features/register/presentation/pages/register_user_details_screen.dart'; import '../../features/securePin/presentation/pages/confirm_pin_screen.dart'; import '../../features/splash/presentation/pages/splash_screen.dart'; +import '../../shared/components/viemo_screen.dart'; /* CREATED BY - JAYESH JAIN DATE - 24-05-2024 @@ -313,6 +315,25 @@ final goRouter = GoRouter( return const FAQScreen(); }, ), + GoRoute( + name: RouteName.vimeoScreen, + path: "${RouteName.vimeoScreen}/:videoUrl", + builder: (context, state) { + return VimeoScreen( + videoUrl: state.pathParameters["videoUrl"]!, + ); + }, + ), + GoRoute( + name: RouteName.pdfReaderScreen, + path: "${RouteName.pdfReaderScreen}/:title/:pdfUrl", + builder: (context, state) { + return ReadPDF( + title: state.pathParameters["title"]!, + pdfUrl: state.pathParameters["pdfUrl"]!, + ); + }, + ), ], ), ], diff --git a/lib/core/styles/app_text.dart b/lib/core/styles/app_text.dart index 5824580..59e666d 100644 --- a/lib/core/styles/app_text.dart +++ b/lib/core/styles/app_text.dart @@ -109,6 +109,58 @@ class AppText { //FAQ static const String faqTitle = "faqTitle"; + static const String gettingStarted = "gettingStarted"; + static const String knowledgeAndEducation = "knowledgeAndEducation"; + static const String complianceSecurityAndSupport = + "complianceSecurityAndSupport"; + static const String getStartedTitle1 = "getStartedTitle1"; + static const String geStartedDesc1 = "geStartedDesc1"; + static const String getStartedTitle2 = "getStartedTitle2"; + static const String geStartedDesc2 = "geStartedDesc2"; + static const String getStartedTitle3 = "getStartedTitle3"; + static const String geStartedDesc3 = "geStartedDesc3"; + static const String investmentTitle1 = "investmentTitle1"; + static const String investmentDesc1 = "investmentDesc1"; + static const String investmentTitle2 = "investmentTitle2"; + static const String investmentDesc2 = "investmentDesc2"; + static const String investmentTitle3 = "investmentTitle3"; + static const String investmentDesc3 = "investmentDesc3"; + static const String investmentTitle4 = "investmentTitle4"; + static const String investmentDesc4 = "investmentDesc4"; + static const String investmentTitle5 = "investmentTitle5"; + static const String investmentDesc5 = "investmentDesc5"; + static const String investmentTitle6 = "investmentTitle6"; + static const String investmentDesc6 = "investmentDesc6"; + static const String investmentTitle7 = "investmentTitle7"; + static const String investmentDesc7 = "investmentDesc7"; + static const String investmentTitle8 = "investmentTitle8"; + static const String investmentDesc8 = "investmentDesc8"; + static const String investmentTitle9 = "investmentTitle9"; + static const String investmentDesc9 = "investmentDesc9"; + static const String investmentTitle10 = "investmentTitle10"; + static const String investmentDesc10 = "investmentDesc10"; + static const String investmentTitle11 = "investmentTitle11"; + static const String investmentDesc11 = "investmentDesc11"; + static const String investmentTitle12 = "investmentTitle12"; + static const String investmentDesc12 = "investmentDesc12"; + static const String investmentTitle13 = "investmentTitle13"; + static const String investmentDesc13 = "investmentDesc13"; + static const String knowledgeTitle1 = "knowledgeTitle1"; + static const String knowledgeDesc1 = "knowledgeDesc1"; + static const String knowledgeTitle2 = "knowledgeTitle2"; + static const String knowledgeDesc2 = "knowledgeDesc2"; + static const String knowledgeTitle3 = "knowledgeTitle3"; + static const String knowledgeDesc3 = "knowledgeDesc3"; + static const String securityTitle1 = "securityTitle1"; + static const String securityDesc1 = "securityDesc1"; + static const String securityTitle2 = "securityTitle2"; + static const String securityDesc2 = "securityDesc2"; + static const String securityTitle3 = "securityTitle3"; + static const String securityDesc3 = "securityDesc3"; + static const String securityTitle4 = "securityTitle4"; + static const String securityDesc4 = "securityDesc4"; + static const String securityTitle5 = "securityTitle5"; + static const String securityDesc5 = "securityDesc5"; //Security static const String unlockText = "unlockText"; @@ -144,6 +196,9 @@ class AppText { static const String masterPinAddedSucessfullyText = "masterPinAddedSucessfullyText"; + static const String toRestorePasswordYouWillBeLoggedOut = + "toRestorePasswordYouWillBeLoggedOut"; + //Forgot Password static const String almostHere = "almostHere"; static const String completeAcc = "completeAcc"; @@ -270,6 +325,7 @@ class AppText { static const String currentPsswordText = "currentPsswordText"; static const String bankDetails = "bankDetails"; static const String addBankDetails = "addBankDetails"; + static const String websiteText = "websiteText"; //Contact Admin static const String byPhoneText = "byPhoneText"; diff --git a/lib/core/utils/date_time_formatter/date_time_formatter.dart b/lib/core/utils/date_time_formatter/date_time_formatter.dart new file mode 100644 index 0000000..a2b3823 --- /dev/null +++ b/lib/core/utils/date_time_formatter/date_time_formatter.dart @@ -0,0 +1,16 @@ +import 'package:intl/intl.dart'; + +class DateTimeFormatter { + String formatDate(String dateString) { + // Parse the input date string + DateTime dateTime = DateTime.parse(dateString); + + // Define the output format + DateFormat formatter = DateFormat('MMM dd yyyy'); + + // Format the date + String formattedDate = formatter.format(dateTime); + + return formattedDate; + } +} diff --git a/lib/core/utils/device_info/device_info_data.dart b/lib/core/utils/device_info/device_info_data.dart new file mode 100644 index 0000000..a192c88 --- /dev/null +++ b/lib/core/utils/device_info/device_info_data.dart @@ -0,0 +1,10 @@ +import 'package:device_info_plus/device_info_plus.dart'; + +class DeviceInfoData { + final deviceInfoPlugin = DeviceInfoPlugin(); + + Future getDeviceId() async { + var dataV = await deviceInfoPlugin.androidInfo; + return dataV.id.toString(); + } +} diff --git a/lib/core/utils/text_formatter/comma_input_text_formatter.dart b/lib/core/utils/text_formatter/comma_input_text_formatter.dart index 605bd52..842338e 100644 --- a/lib/core/utils/text_formatter/comma_input_text_formatter.dart +++ b/lib/core/utils/text_formatter/comma_input_text_formatter.dart @@ -16,4 +16,15 @@ class CommaTextInputFormatter extends TextInputFormatter { selection: TextSelection.collapsed(offset: newText.length), ); } + + String getInitials(String name) { + List words = name.split(' '); + String initials = ''; + for (var word in words) { + if (word.isNotEmpty) { + initials += word[0].toUpperCase(); + } + } + return initials; + } } diff --git a/lib/core/utils/url_launcher/url_launcher.dart b/lib/core/utils/url_launcher/url_launcher.dart index 89d323d..d61ea11 100644 --- a/lib/core/utils/url_launcher/url_launcher.dart +++ b/lib/core/utils/url_launcher/url_launcher.dart @@ -17,3 +17,12 @@ void launchEmail(String email) async { throw 'Could not launch $url'; } } + +void launchWebsiteUrl(String websiteUrl) async { + final url = websiteUrl; + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } else { + throw 'Could not launch $url'; + } +} diff --git a/lib/features/MainScreens/Invest/domain/model/available_io_model.dart b/lib/features/MainScreens/Invest/domain/model/available_io_model.dart new file mode 100644 index 0000000..981386b --- /dev/null +++ b/lib/features/MainScreens/Invest/domain/model/available_io_model.dart @@ -0,0 +1,460 @@ +class AvailableIOModel { + int? statusCode; + Data? data; + String? message; + bool? success; + + AvailableIOModel({this.statusCode, this.data, this.message, this.success}); + + AvailableIOModel.fromJson(Map json) { + statusCode = json['statusCode']; + data = json['data'] != null ? Data.fromJson(json['data']) : null; + message = json['message']; + success = json['success']; + } + + Map toJson() { + final Map data = {}; + data['statusCode'] = statusCode; + if (this.data != null) { + data['data'] = this.data!.toJson(); + } + data['message'] = message; + data['success'] = success; + return data; + } +} + +class Data { + int? totalItems; + List? rows; + int? totalPages; + int? currentPage; + + Data({this.totalItems, this.rows, this.totalPages, this.currentPage}); + + Data.fromJson(Map json) { + totalItems = json['totalItems']; + if (json['rows'] != null) { + rows = []; + json['rows'].forEach((v) { + rows!.add(Rows.fromJson(v)); + }); + } + totalPages = json['totalPages']; + currentPage = json['currentPage']; + } + + Map toJson() { + final Map data = {}; + data['totalItems'] = totalItems; + if (rows != null) { + data['rows'] = rows!.map((v) => v.toJson()).toList(); + } + data['totalPages'] = totalPages; + data['currentPage'] = currentPage; + return data; + } +} + +class Rows { + int? id; + String? ioId; + int? investmentTypeXid; + int? sponsorXid; + int? ioStatusXid; + String? investmentNameEnglish; + String? investmentNameArabic; + String? descriptionEnglish; + String? descriptionArabic; + String? goalAmount; + String? closingDate; + String? holdingPeriod; + String? expectedReturn; + String? originalValuation; + String? currentValuation; + String? iSIN; + String? investmentDetails; + String? comment; + bool? isInvestedAmount; + String? amountInvested; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + Sponsor? sponsor; + IoStatus? ioStatus; + InvestmentType? investmentType; + List? artifactsImage; + List? minInvestmentAmt; + + Rows( + {this.id, + this.ioId, + this.investmentTypeXid, + this.sponsorXid, + this.ioStatusXid, + this.investmentNameEnglish, + this.investmentNameArabic, + this.descriptionEnglish, + this.descriptionArabic, + this.goalAmount, + this.closingDate, + this.holdingPeriod, + this.expectedReturn, + this.originalValuation, + this.currentValuation, + this.iSIN, + this.investmentDetails, + this.comment, + this.isInvestedAmount, + this.amountInvested, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt, + this.sponsor, + this.ioStatus, + this.investmentType, + this.artifactsImage, + this.minInvestmentAmt}); + + Rows.fromJson(Map json) { + id = json['id']; + ioId = json['io_id']; + investmentTypeXid = json['investmentType_xid']; + sponsorXid = json['sponsor_xid']; + ioStatusXid = json['ioStatus_xid']; + investmentNameEnglish = json['investmentNameEnglish']; + investmentNameArabic = json['investmentNameArabic']; + descriptionEnglish = json['descriptionEnglish']; + descriptionArabic = json['descriptionArabic']; + goalAmount = json['goalAmount']; + closingDate = json['closingDate']; + holdingPeriod = json['holdingPeriod']; + expectedReturn = json['expectedReturn']; + originalValuation = json['originalValuation']; + currentValuation = json['currentValuation']; + iSIN = json['ISIN']; + investmentDetails = json['InvestmentDetails']; + comment = json['comment']; + isInvestedAmount = json['isInvestedAmount']; + amountInvested = json['amountInvested']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + sponsor = + json['sponsor'] != null ? Sponsor.fromJson(json['sponsor']) : null; + ioStatus = + json['ioStatus'] != null ? IoStatus.fromJson(json['ioStatus']) : null; + investmentType = json['investmentType'] != null + ? InvestmentType.fromJson(json['investmentType']) + : null; + if (json['artifactsImage'] != null) { + artifactsImage = []; + json['artifactsImage'].forEach((v) { + artifactsImage!.add(v); + }); + } + if (json['minInvestmentAmt'] != null) { + minInvestmentAmt = []; + json['minInvestmentAmt'].forEach((v) { + minInvestmentAmt!.add(MinInvestmentAmt.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['io_id'] = ioId; + data['investmentType_xid'] = investmentTypeXid; + data['sponsor_xid'] = sponsorXid; + data['ioStatus_xid'] = ioStatusXid; + data['investmentNameEnglish'] = investmentNameEnglish; + data['investmentNameArabic'] = investmentNameArabic; + data['descriptionEnglish'] = descriptionEnglish; + data['descriptionArabic'] = descriptionArabic; + data['goalAmount'] = goalAmount; + data['closingDate'] = closingDate; + data['holdingPeriod'] = holdingPeriod; + data['expectedReturn'] = expectedReturn; + data['originalValuation'] = originalValuation; + data['currentValuation'] = currentValuation; + data['ISIN'] = iSIN; + data['InvestmentDetails'] = investmentDetails; + data['comment'] = comment; + data['isInvestedAmount'] = isInvestedAmount; + data['amountInvested'] = amountInvested; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + if (sponsor != null) { + data['sponsor'] = sponsor!.toJson(); + } + if (ioStatus != null) { + data['ioStatus'] = ioStatus!.toJson(); + } + if (investmentType != null) { + data['investmentType'] = investmentType!.toJson(); + } + if (artifactsImage != null) { + data['artifactsImage'] = artifactsImage!.map((v) => v).toList(); + } + if (minInvestmentAmt != null) { + data['minInvestmentAmt'] = + minInvestmentAmt!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Sponsor { + int? id; + String? sponsorName; + String? sponsorNameArabic; + String? email; + Null profile; + Null address; + Null mobileNo; + Null bankName; + Null accountNumber; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + Sponsor( + {this.id, + this.sponsorName, + this.sponsorNameArabic, + this.email, + this.profile, + this.address, + this.mobileNo, + this.bankName, + this.accountNumber, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + Sponsor.fromJson(Map json) { + id = json['id']; + sponsorName = json['sponsorName']; + sponsorNameArabic = json['sponsorNameArabic']; + email = json['email']; + profile = json['profile']; + address = json['address']; + mobileNo = json['mobileNo']; + bankName = json['bankName']; + accountNumber = json['accountNumber']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['sponsorName'] = sponsorName; + data['sponsorNameArabic'] = sponsorNameArabic; + data['email'] = email; + data['profile'] = profile; + data['address'] = address; + data['mobileNo'] = mobileNo; + data['bankName'] = bankName; + data['accountNumber'] = accountNumber; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class IoStatus { + int? id; + String? statusAdmin; + String? statusInvest; + String? statusPortfolio; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + IoStatus( + {this.id, + this.statusAdmin, + this.statusInvest, + this.statusPortfolio, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + IoStatus.fromJson(Map json) { + id = json['id']; + statusAdmin = json['statusAdmin']; + statusInvest = json['statusInvest']; + statusPortfolio = json['statusPortfolio']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['statusAdmin'] = statusAdmin; + data['statusInvest'] = statusInvest; + data['statusPortfolio'] = statusPortfolio; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class InvestmentType { + int? id; + String? investmentTypeName; + String? investmentTypeNameArabic; + String? note; + String? noteArabic; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + InvestmentType( + {this.id, + this.investmentTypeName, + this.investmentTypeNameArabic, + this.note, + this.noteArabic, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + InvestmentType.fromJson(Map json) { + id = json['id']; + investmentTypeName = json['investmentTypeName']; + investmentTypeNameArabic = json['investmentTypeNameArabic']; + note = json['note']; + noteArabic = json['noteArabic']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['investmentTypeName'] = investmentTypeName; + data['investmentTypeNameArabic'] = investmentTypeNameArabic; + data['note'] = note; + data['noteArabic'] = noteArabic; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class MinInvestmentAmt { + int? id; + int? ioXid; + int? countryXid; + String? minInvestmentAmt; + String? maxInvestmentAmt; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + MinInvestmentAmt( + {this.id, + this.ioXid, + this.countryXid, + this.minInvestmentAmt, + this.maxInvestmentAmt, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + MinInvestmentAmt.fromJson(Map json) { + id = json['id']; + ioXid = json['io_xid']; + countryXid = json['country_xid']; + minInvestmentAmt = json['minInvestmentAmt']; + maxInvestmentAmt = json['maxInvestmentAmt']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['io_xid'] = ioXid; + data['country_xid'] = countryXid; + data['minInvestmentAmt'] = minInvestmentAmt; + data['maxInvestmentAmt'] = maxInvestmentAmt; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} diff --git a/lib/features/MainScreens/Invest/domain/model/closed_io_model.dart b/lib/features/MainScreens/Invest/domain/model/closed_io_model.dart new file mode 100644 index 0000000..fcd791c --- /dev/null +++ b/lib/features/MainScreens/Invest/domain/model/closed_io_model.dart @@ -0,0 +1,460 @@ +class ClosedIOModel { + int? statusCode; + Data? data; + String? message; + bool? success; + + ClosedIOModel({this.statusCode, this.data, this.message, this.success}); + + ClosedIOModel.fromJson(Map json) { + statusCode = json['statusCode']; + data = json['data'] != null ? Data.fromJson(json['data']) : null; + message = json['message']; + success = json['success']; + } + + Map toJson() { + final Map data = {}; + data['statusCode'] = statusCode; + if (this.data != null) { + data['data'] = this.data!.toJson(); + } + data['message'] = message; + data['success'] = success; + return data; + } +} + +class Data { + int? totalItems; + List? rows; + int? totalPages; + int? currentPage; + + Data({this.totalItems, this.rows, this.totalPages, this.currentPage}); + + Data.fromJson(Map json) { + totalItems = json['totalItems']; + if (json['rows'] != null) { + rows = []; + json['rows'].forEach((v) { + rows!.add(Rows.fromJson(v)); + }); + } + totalPages = json['totalPages']; + currentPage = json['currentPage']; + } + + Map toJson() { + final Map data = {}; + data['totalItems'] = totalItems; + if (rows != null) { + data['rows'] = rows!.map((v) => v.toJson()).toList(); + } + data['totalPages'] = totalPages; + data['currentPage'] = currentPage; + return data; + } +} + +class Rows { + int? id; + String? ioId; + int? investmentTypeXid; + int? sponsorXid; + int? ioStatusXid; + String? investmentNameEnglish; + String? investmentNameArabic; + String? descriptionEnglish; + String? descriptionArabic; + String? goalAmount; + String? closingDate; + String? holdingPeriod; + String? expectedReturn; + String? originalValuation; + String? currentValuation; + String? iSIN; + String? investmentDetails; + String? comment; + bool? isInvestedAmount; + String? amountInvested; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + Sponsor? sponsor; + IoStatus? ioStatus; + InvestmentType? investmentType; + List? artifactsImage; + List? minInvestmentAmt; + + Rows( + {this.id, + this.ioId, + this.investmentTypeXid, + this.sponsorXid, + this.ioStatusXid, + this.investmentNameEnglish, + this.investmentNameArabic, + this.descriptionEnglish, + this.descriptionArabic, + this.goalAmount, + this.closingDate, + this.holdingPeriod, + this.expectedReturn, + this.originalValuation, + this.currentValuation, + this.iSIN, + this.investmentDetails, + this.comment, + this.isInvestedAmount, + this.amountInvested, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt, + this.sponsor, + this.ioStatus, + this.investmentType, + this.artifactsImage, + this.minInvestmentAmt}); + + Rows.fromJson(Map json) { + id = json['id']; + ioId = json['io_id']; + investmentTypeXid = json['investmentType_xid']; + sponsorXid = json['sponsor_xid']; + ioStatusXid = json['ioStatus_xid']; + investmentNameEnglish = json['investmentNameEnglish']; + investmentNameArabic = json['investmentNameArabic']; + descriptionEnglish = json['descriptionEnglish']; + descriptionArabic = json['descriptionArabic']; + goalAmount = json['goalAmount']; + closingDate = json['closingDate']; + holdingPeriod = json['holdingPeriod']; + expectedReturn = json['expectedReturn']; + originalValuation = json['originalValuation']; + currentValuation = json['currentValuation']; + iSIN = json['ISIN']; + investmentDetails = json['InvestmentDetails']; + comment = json['comment']; + isInvestedAmount = json['isInvestedAmount']; + amountInvested = json['amountInvested']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + sponsor = + json['sponsor'] != null ? Sponsor.fromJson(json['sponsor']) : null; + ioStatus = + json['ioStatus'] != null ? IoStatus.fromJson(json['ioStatus']) : null; + investmentType = json['investmentType'] != null + ? InvestmentType.fromJson(json['investmentType']) + : null; + if (json['artifactsImage'] != null) { + artifactsImage = []; + json['artifactsImage'].forEach((v) { + artifactsImage!.add(v); + }); + } + if (json['minInvestmentAmt'] != null) { + minInvestmentAmt = []; + json['minInvestmentAmt'].forEach((v) { + minInvestmentAmt!.add(MinInvestmentAmt.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['io_id'] = ioId; + data['investmentType_xid'] = investmentTypeXid; + data['sponsor_xid'] = sponsorXid; + data['ioStatus_xid'] = ioStatusXid; + data['investmentNameEnglish'] = investmentNameEnglish; + data['investmentNameArabic'] = investmentNameArabic; + data['descriptionEnglish'] = descriptionEnglish; + data['descriptionArabic'] = descriptionArabic; + data['goalAmount'] = goalAmount; + data['closingDate'] = closingDate; + data['holdingPeriod'] = holdingPeriod; + data['expectedReturn'] = expectedReturn; + data['originalValuation'] = originalValuation; + data['currentValuation'] = currentValuation; + data['ISIN'] = iSIN; + data['InvestmentDetails'] = investmentDetails; + data['comment'] = comment; + data['isInvestedAmount'] = isInvestedAmount; + data['amountInvested'] = amountInvested; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + if (sponsor != null) { + data['sponsor'] = sponsor!.toJson(); + } + if (ioStatus != null) { + data['ioStatus'] = ioStatus!.toJson(); + } + if (investmentType != null) { + data['investmentType'] = investmentType!.toJson(); + } + if (artifactsImage != null) { + data['artifactsImage'] = artifactsImage!.map((v) => v).toList(); + } + if (minInvestmentAmt != null) { + data['minInvestmentAmt'] = + minInvestmentAmt!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Sponsor { + int? id; + String? sponsorName; + String? sponsorNameArabic; + String? email; + Null profile; + Null address; + Null mobileNo; + Null bankName; + Null accountNumber; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + Sponsor( + {this.id, + this.sponsorName, + this.sponsorNameArabic, + this.email, + this.profile, + this.address, + this.mobileNo, + this.bankName, + this.accountNumber, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + Sponsor.fromJson(Map json) { + id = json['id']; + sponsorName = json['sponsorName']; + sponsorNameArabic = json['sponsorNameArabic']; + email = json['email']; + profile = json['profile']; + address = json['address']; + mobileNo = json['mobileNo']; + bankName = json['bankName']; + accountNumber = json['accountNumber']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['sponsorName'] = sponsorName; + data['sponsorNameArabic'] = sponsorNameArabic; + data['email'] = email; + data['profile'] = profile; + data['address'] = address; + data['mobileNo'] = mobileNo; + data['bankName'] = bankName; + data['accountNumber'] = accountNumber; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class IoStatus { + int? id; + String? statusAdmin; + String? statusInvest; + String? statusPortfolio; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + IoStatus( + {this.id, + this.statusAdmin, + this.statusInvest, + this.statusPortfolio, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + IoStatus.fromJson(Map json) { + id = json['id']; + statusAdmin = json['statusAdmin']; + statusInvest = json['statusInvest']; + statusPortfolio = json['statusPortfolio']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['statusAdmin'] = statusAdmin; + data['statusInvest'] = statusInvest; + data['statusPortfolio'] = statusPortfolio; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class InvestmentType { + int? id; + String? investmentTypeName; + String? investmentTypeNameArabic; + String? note; + String? noteArabic; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + InvestmentType( + {this.id, + this.investmentTypeName, + this.investmentTypeNameArabic, + this.note, + this.noteArabic, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + InvestmentType.fromJson(Map json) { + id = json['id']; + investmentTypeName = json['investmentTypeName']; + investmentTypeNameArabic = json['investmentTypeNameArabic']; + note = json['note']; + noteArabic = json['noteArabic']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['investmentTypeName'] = investmentTypeName; + data['investmentTypeNameArabic'] = investmentTypeNameArabic; + data['note'] = note; + data['noteArabic'] = noteArabic; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} + +class MinInvestmentAmt { + int? id; + int? ioXid; + int? countryXid; + String? minInvestmentAmt; + String? maxInvestmentAmt; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + MinInvestmentAmt( + {this.id, + this.ioXid, + this.countryXid, + this.minInvestmentAmt, + this.maxInvestmentAmt, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + MinInvestmentAmt.fromJson(Map json) { + id = json['id']; + ioXid = json['io_xid']; + countryXid = json['country_xid']; + minInvestmentAmt = json['minInvestmentAmt']; + maxInvestmentAmt = json['maxInvestmentAmt']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['io_xid'] = ioXid; + data['country_xid'] = countryXid; + data['minInvestmentAmt'] = minInvestmentAmt; + data['maxInvestmentAmt'] = maxInvestmentAmt; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} diff --git a/lib/features/MainScreens/Invest/domain/repository/io_api.dart b/lib/features/MainScreens/Invest/domain/repository/io_api.dart new file mode 100644 index 0000000..5f344f0 --- /dev/null +++ b/lib/features/MainScreens/Invest/domain/repository/io_api.dart @@ -0,0 +1,17 @@ +import '../../../../../Api_Helper/base_manager.dart'; +import '../../../../../shared/api/api_endpoints.dart'; +import '../../../../../shared/api/network_api_services.dart'; + +class IOApi { + Future availableIOAPI() async { + String url = ApiEndpoints.availableIOApi; + final response = await NetworkApiService().get(url); + return response; + } + + Future closedIOAPI() async { + String url = ApiEndpoints.closedIOApi; + final response = await NetworkApiService().get(url); + return response; + } +} diff --git a/lib/features/MainScreens/Invest/presentation/bloc/tab_bloc.dart b/lib/features/MainScreens/Invest/presentation/bloc/tab_bloc.dart index a953b48..deff09c 100644 --- a/lib/features/MainScreens/Invest/presentation/bloc/tab_bloc.dart +++ b/lib/features/MainScreens/Invest/presentation/bloc/tab_bloc.dart @@ -1,25 +1,52 @@ -import 'package:bloc/bloc.dart'; +import 'dart:developer'; +import 'package:bloc/bloc.dart'; +import 'package:tanami_app/features/MainScreens/Invest/domain/model/available_io_model.dart'; +import 'package:tanami_app/features/MainScreens/Invest/domain/model/closed_io_model.dart'; + +import '../../../../../Api_Helper/base_manager.dart'; +import '../../domain/repository/io_api.dart'; import 'tab_event.dart'; import 'tab_state.dart'; class TabBloc extends Bloc { - TabBloc() : super(const TabState()) { + TabBloc() : super(TabState(AvailableIOModel(), ClosedIOModel(), false, '')) { on(_onLoadAvailableItems); on(_onLoadClosedItems); } - void _onLoadAvailableItems(LoadAvailableItems event, Emitter emit) { - // Simulate fetching available items - final availableItems = - List.generate(10, (index) => 'Available Item $index'); - emit(state.copyWith(availableItems: availableItems)); + void _onLoadAvailableItems( + LoadAvailableItems event, Emitter emit) async { + emit(state.copyWith(loading: true)); + try { + ResponseData response = await IOApi().availableIOAPI(); + log(response.data.toString()); + if (response.status == ResponseStatus.SUCCESS) { + AvailableIOModel availableIOModel = + AvailableIOModel.fromJson(response.data); + emit(state.copyWith(availableItems: availableIOModel, loading: false)); + } else { + emit(state.copyWith(errorMessage: response.message, loading: false)); + } + } catch (e) { + emit(state.copyWith( + errorMessage: 'Failed to fetch available items: $e', loading: false)); + } } - void _onLoadClosedItems(LoadClosedItems event, Emitter emit) { - // Simulate fetching closed items - final closedItems = - List.generate(10, (index) => 'Closed Item $index'); - emit(state.copyWith(closedItems: closedItems)); + void _onLoadClosedItems(LoadClosedItems event, Emitter emit) async { + emit(state.copyWith(loading: true)); + try { + ResponseData response = await IOApi().closedIOAPI(); + if (response.status == ResponseStatus.SUCCESS) { + ClosedIOModel closedIOModel = ClosedIOModel.fromJson(response.data); + emit(state.copyWith(closedItems: closedIOModel, loading: false)); + } else { + emit(state.copyWith(errorMessage: response.message, loading: false)); + } + } catch (e) { + emit(state.copyWith( + errorMessage: 'Failed to fetch available items: $e', loading: false)); + } } } diff --git a/lib/features/MainScreens/Invest/presentation/bloc/tab_state.dart b/lib/features/MainScreens/Invest/presentation/bloc/tab_state.dart index b4e0d8e..2654415 100644 --- a/lib/features/MainScreens/Invest/presentation/bloc/tab_state.dart +++ b/lib/features/MainScreens/Invest/presentation/bloc/tab_state.dart @@ -1,24 +1,36 @@ import 'package:equatable/equatable.dart'; -class TabState extends Equatable { - final List availableItems; - final List closedItems; +import '../../domain/model/available_io_model.dart'; +import '../../domain/model/closed_io_model.dart'; - const TabState({ - this.availableItems = const [], - this.closedItems = const [], - }); +class TabState extends Equatable { + final AvailableIOModel availableItems; + final ClosedIOModel closedItems; + final bool loading; + final String errorMessage; + + const TabState( + this.availableItems, + this.closedItems, + this.loading, + this.errorMessage, + ); TabState copyWith({ - List? availableItems, - List? closedItems, + AvailableIOModel? availableItems, + ClosedIOModel? closedItems, + bool? loading, + String? errorMessage, }) { return TabState( - availableItems: availableItems ?? this.availableItems, - closedItems: closedItems ?? this.closedItems, + availableItems ?? this.availableItems, + closedItems ?? this.closedItems, + loading ?? this.loading, + errorMessage ?? this.errorMessage, ); } @override - List get props => [availableItems, closedItems]; + List get props => + [availableItems, closedItems, loading, errorMessage]; } diff --git a/lib/features/MainScreens/Invest/presentation/pages/invest_layout.dart b/lib/features/MainScreens/Invest/presentation/pages/invest_layout.dart index 5fc6320..57fdb35 100644 --- a/lib/features/MainScreens/Invest/presentation/pages/invest_layout.dart +++ b/lib/features/MainScreens/Invest/presentation/pages/invest_layout.dart @@ -72,52 +72,63 @@ class AvailableItemsScreen extends StatelessWidget { return BlocBuilder( builder: (context, state) { - if (state.availableItems.isEmpty) { + if (state.loading) { return const Center(child: CircularProgressIndicator()); - } - return ListView.builder( - itemCount: state.availableItems.length, - itemBuilder: (context, index) { - return index == 0 - ? Container( - margin: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 18, - ), - child: kycCard(context)) - : GestureDetector( - onTap: () { - goRouter.pushNamed(RouteName.investDetailScreen, - pathParameters: {"type": "available"}); - }, - child: Container( + } else if (state.errorMessage.isNotEmpty) { + return Center(child: Text(state.errorMessage)); + } else if (state.availableItems.data!.rows!.isEmpty) { + return const Center(child: Text('No available items.')); + } else { + return ListView.builder( + itemCount: state.availableItems.data!.rows!.length + 1, + itemBuilder: (context, index) { + return index == 0 + ? Container( margin: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), - decoration: BoxDecoration( - color: AppColor.plainWhite, - borderRadius: - const BorderRadius.all(Radius.circular(20.0)), - boxShadow: [ - BoxShadow( - color: AppColor.plainBlack.withOpacity(0.15), - spreadRadius: 2, - blurRadius: 10, - offset: const Offset(0, 3), - ), - ], + child: kycCard(context)) + : GestureDetector( + onTap: () { + goRouter.pushNamed(RouteName.investDetailScreen, + pathParameters: {"type": "available"}); + }, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: + const BorderRadius.all(Radius.circular(20.0)), + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + children: [ + InvestCarouselView( + imageList: state.availableItems.data! + .rows![index - 1].artifactsImage!, + ), + InvestDetailsSection( + availableIOModel: + state.availableItems.data!.rows![index - 1], + ), + ], + ), ), - child: Column( - children: [ - InvestCarouselView(), - const InvestDetailsSection(), - ], - ), - ), - ); - }, - ); + ); + }, + ); + } }, ); } @@ -132,44 +143,53 @@ class ClosedItemsScreen extends StatelessWidget { return BlocBuilder( builder: (context, state) { - if (state.closedItems.isEmpty) { + if (state.loading) { return const Center(child: CircularProgressIndicator()); + } else if (state.errorMessage.isNotEmpty) { + return Center(child: Text(state.errorMessage)); + } else if (state.closedItems.data!.rows!.isEmpty) { + return const Center(child: Text('No available items.')); + } else { + return ListView.builder( + itemCount: state.closedItems.data!.rows!.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + goRouter.pushNamed(RouteName.investDetailScreen, + pathParameters: {"type": "closed"}); + }, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ), + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: const BorderRadius.all(Radius.circular(20.0)), + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: Column( + children: [ + InvestCarouselView( + imageList: const [], + ), + InvestClosedDetailsSection( + closedIoModel: state.closedItems.data!.rows![index], + ), + ], + ), + ), + ); + }, + ); } - return ListView.builder( - itemCount: state.closedItems.length, - itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - goRouter.pushNamed(RouteName.investDetailScreen, - pathParameters: {"type": "closed"}); - }, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 18, - ), - decoration: BoxDecoration( - color: AppColor.plainWhite, - borderRadius: const BorderRadius.all(Radius.circular(20.0)), - boxShadow: [ - BoxShadow( - color: AppColor.plainBlack.withOpacity(0.15), - spreadRadius: 2, - blurRadius: 10, - offset: const Offset(0, 3), - ), - ], - ), - child: Column( - children: [ - InvestCarouselView(), - const InvestClosedDetailsSection(), - ], - ), - ), - ); - }, - ); }, ); } diff --git a/lib/features/MainScreens/Invest/presentation/widgets/invest_closed_details_section.dart b/lib/features/MainScreens/Invest/presentation/widgets/invest_closed_details_section.dart index e64f119..92bb0a5 100644 --- a/lib/features/MainScreens/Invest/presentation/widgets/invest_closed_details_section.dart +++ b/lib/features/MainScreens/Invest/presentation/widgets/invest_closed_details_section.dart @@ -5,11 +5,14 @@ import 'package:tanami_app/core/styles/app_color.dart'; import '../../../../../core/styles/app_images.dart'; import '../../../../../core/styles/app_text.dart'; +import '../../../../../core/utils/date_time_formatter/date_time_formatter.dart'; import '../../../../../core/utils/language/localizations_delegate.dart'; import '../../../../../shared/components/text_widget.dart'; +import '../../domain/model/closed_io_model.dart'; class InvestClosedDetailsSection extends StatelessWidget { - const InvestClosedDetailsSection({super.key}); + final Rows closedIoModel; + const InvestClosedDetailsSection({super.key, required this.closedIoModel}); @override Widget build(BuildContext context) { @@ -32,7 +35,7 @@ class InvestClosedDetailsSection extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextWidget().text17W700( - 'Multi Family Residental', + closedIoModel.investmentNameEnglish!, clr: AppColor.plainBlack, ), Container( @@ -46,7 +49,7 @@ class InvestClosedDetailsSection extends StatelessWidget { ), child: Center( child: TextWidget().text12W700( - "Real estate", + closedIoModel.investmentType!.investmentTypeName!, clr: AppColor.selectedItemColor, )), ) @@ -77,7 +80,8 @@ class InvestClosedDetailsSection extends StatelessWidget { clr: AppColor.plainBlack, ), TextWidget().text12W500( - 'Jul 10 2025', + DateTimeFormatter() + .formatDate(closedIoModel.closingDate!), clr: AppColor.plainBlack, ), ], @@ -86,23 +90,8 @@ class InvestClosedDetailsSection extends StatelessWidget { Gap( 10.h, ), - // TextWidget() - // .text22W900("SAR 1,478,000", clr: AppColor.investTextColor), - // const Gap(8.0), - // LinearProgressIndicator( - // value: 1, - // borderRadius: BorderRadius.circular(2), - // minHeight: 8.0, - // backgroundColor: AppColor.txtBorderColor, - // valueColor: const AlwaysStoppedAnimation( - // AppColor.investTextColor), - // ), - // const Gap(8.0), - // TextWidget().text11W700("100% ${AppText.fundedText}", - // clr: AppColor.portoflioCardTextColor), - // const Gap(8.0), TextWidget().text14W400( - 'Forem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vulputate libero et velit interdum, ac aliquet odio mattis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur tempus urna at turpis condimentum lobortis.', + closedIoModel.descriptionEnglish!, clr: Colors.grey, txtAlign: TextAlign.start, maxLine: 2, @@ -135,7 +124,7 @@ class InvestClosedDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - 'Silverlake', + closedIoModel.sponsor!.sponsorName!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ) @@ -157,7 +146,7 @@ class InvestClosedDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - '22.5%', + closedIoModel.expectedReturn!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), @@ -179,7 +168,7 @@ class InvestClosedDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - '24 Months', + closedIoModel.holdingPeriod!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), @@ -198,7 +187,8 @@ class InvestClosedDetailsSection extends StatelessWidget { txtAlign: TextAlign.start, ), TextWidget().text14W700( - 'August 1, 2027', + DateTimeFormatter() + .formatDate(closedIoModel.closingDate!), clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), diff --git a/lib/features/MainScreens/Invest/presentation/widgets/invest_details_section.dart b/lib/features/MainScreens/Invest/presentation/widgets/invest_details_section.dart index 9a7cf6a..959d7d3 100644 --- a/lib/features/MainScreens/Invest/presentation/widgets/invest_details_section.dart +++ b/lib/features/MainScreens/Invest/presentation/widgets/invest_details_section.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/utils/date_time_formatter/date_time_formatter.dart'; +import 'package:tanami_app/features/MainScreens/Invest/domain/model/available_io_model.dart'; import '../../../../../core/styles/app_images.dart'; import '../../../../../core/styles/app_text.dart'; @@ -9,7 +11,8 @@ import '../../../../../core/utils/language/localizations_delegate.dart'; import '../../../../../shared/components/text_widget.dart'; class InvestDetailsSection extends StatelessWidget { - const InvestDetailsSection({super.key}); + final Rows availableIOModel; + const InvestDetailsSection({super.key, required this.availableIOModel}); @override Widget build(BuildContext context) { @@ -32,7 +35,7 @@ class InvestDetailsSection extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextWidget().text17W700( - 'Multi Family Residental', + availableIOModel.investmentNameEnglish!, clr: AppColor.plainBlack, ), Container( @@ -46,7 +49,7 @@ class InvestDetailsSection extends StatelessWidget { ), child: Center( child: TextWidget().text11W700( - "Real estate", + availableIOModel.investmentType!.investmentTypeName!, clr: AppColor.selectedItemColor, )), ) @@ -77,7 +80,8 @@ class InvestDetailsSection extends StatelessWidget { clr: AppColor.plainBlack, ), TextWidget().text12W500( - 'Jul 10 2025', + DateTimeFormatter() + .formatDate(availableIOModel.closingDate!), clr: AppColor.plainBlack, ), ], @@ -86,11 +90,12 @@ class InvestDetailsSection extends StatelessWidget { Gap( 10.h, ), - TextWidget() - .text22W900("SAR 1,478,000", clr: AppColor.investTextColor), + TextWidget().text22W900("USD ${availableIOModel.goalAmount!}", + clr: AppColor.investTextColor), const Gap(8.0), LinearProgressIndicator( - value: 0.6, + value: (double.parse(availableIOModel.amountInvested!) / + double.parse(availableIOModel.goalAmount!)), borderRadius: BorderRadius.circular(2), minHeight: 8.0, backgroundColor: AppColor.txtBorderColor, @@ -99,11 +104,11 @@ class InvestDetailsSection extends StatelessWidget { ), const Gap(8.0), TextWidget().text11W700( - "60% ${localizations.translate(AppText.fundedText)}", + "${(double.parse(availableIOModel.amountInvested!) / double.parse(availableIOModel.goalAmount!)) * 100}% ${localizations.translate(AppText.fundedText)}", clr: AppColor.portoflioCardTextColor), const Gap(8.0), TextWidget().text14W400( - 'Forem ipsum dolor sit amet, consectetur adipiscing elit. Nunc vulputate libero et velit interdum, ac aliquet odio mattis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur tempus urna at turpis condimentum lobortis.', + availableIOModel.descriptionEnglish!, clr: Colors.grey, txtAlign: TextAlign.start, maxLine: 2, @@ -136,7 +141,7 @@ class InvestDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - 'Silverlake', + availableIOModel.sponsor!.sponsorName!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ) @@ -158,7 +163,7 @@ class InvestDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - '20.0%', + availableIOModel.expectedReturn!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), @@ -180,7 +185,7 @@ class InvestDetailsSection extends StatelessWidget { ), ), TextWidget().text14W700( - '24 Months', + availableIOModel.holdingPeriod!, clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), @@ -199,7 +204,7 @@ class InvestDetailsSection extends StatelessWidget { txtAlign: TextAlign.start, ), TextWidget().text14W700( - 'SAR 1,000', + 'BHD ${availableIOModel.minInvestmentAmt![0].minInvestmentAmt}', clr: AppColor.plainBlack, txtAlign: TextAlign.end, ), diff --git a/lib/features/MainScreens/Invest/presentation/widgets/invest_image_carousel.dart b/lib/features/MainScreens/Invest/presentation/widgets/invest_image_carousel.dart index 5d42bda..4fb3c96 100644 --- a/lib/features/MainScreens/Invest/presentation/widgets/invest_image_carousel.dart +++ b/lib/features/MainScreens/Invest/presentation/widgets/invest_image_carousel.dart @@ -19,8 +19,9 @@ final List imgList = [ class InvestCarouselView extends StatelessWidget { final CarouselController _controller = CarouselController(); + final List imageList; - InvestCarouselView({super.key}); + InvestCarouselView({super.key, required this.imageList}); @override Widget build(BuildContext context) { @@ -37,10 +38,10 @@ class InvestCarouselView extends StatelessWidget { ClipRRect( borderRadius: BorderRadius.circular(20), child: CarouselSlider( - items: imgList + items: imageList .map( (item) => Image.asset( - item['img_path'], + item, fit: BoxFit.cover, width: double.infinity, alignment: Alignment.topCenter, @@ -67,7 +68,7 @@ class InvestCarouselView extends StatelessWidget { } return Row( mainAxisAlignment: MainAxisAlignment.center, - children: imgList.asMap().entries.map((entry) { + children: imageList.asMap().entries.map((entry) { return GestureDetector( onTap: () => _controller.animateToPage(entry.key), child: Container( diff --git a/lib/features/MainScreens/Invest/presentation/widgets/invest_included_documents_section.dart b/lib/features/MainScreens/Invest/presentation/widgets/invest_included_documents_section.dart index 200280e..bbe2dbc 100644 --- a/lib/features/MainScreens/Invest/presentation/widgets/invest_included_documents_section.dart +++ b/lib/features/MainScreens/Invest/presentation/widgets/invest_included_documents_section.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; +import 'package:tanami_app/core/routes/route_name.dart'; +import 'package:tanami_app/core/routes/routes.dart'; import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_images.dart'; import 'package:tanami_app/shared/components/text_widget.dart'; @@ -51,48 +53,60 @@ class InvestIncludedDocumentsSection extends StatelessWidget { 6, (index) { return Center( - child: Container( - decoration: const BoxDecoration( - color: AppColor.documentCardBgColor, - borderRadius: BorderRadius.all(Radius.circular(10.0)), - ), - padding: const EdgeInsets.all(15.0), - child: Column( - children: [ - Row( - children: [ - Image.asset( - AppImages.documentIcon, - height: 16.sp, - ), - Gap( - 7.w, - ), - TextWidget().text12W700("Filename.pdf", - clr: AppColor.plainBlack), - ], - ), - Gap( - 20.h, - ), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: InkWell( + onTap: () { + //https://www.orimi.com/pdf-test.pdf + goRouter.pushNamed(RouteName.pdfReaderScreen, + pathParameters: { + "pdfUrl": + "https://dn790007.ca.archive.org/0/items/atomic-habits-pdfdrive/Atomic%20habits%20%28%20PDFDrive%20%29.pdf", + "title": "Test" + }); + }, + child: Container( + decoration: const BoxDecoration( + color: AppColor.documentCardBgColor, + borderRadius: BorderRadius.all(Radius.circular(10.0)), + ), + padding: const EdgeInsets.all(15.0), + child: Column( + children: [ + Row( children: [ - TextWidget().text12W700("512 Mb", - clr: AppColor.portoflioCardTextColor), + Image.asset( + AppImages.documentIcon, + height: 16.sp, + ), Gap( 7.w, ), - Image.asset( - AppImages.donwloadIcon, - height: 20.sp, - ), + TextWidget().text12W700("Filename.pdf", + clr: AppColor.plainBlack), ], ), - ), - ], + Gap( + 20.h, + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + TextWidget().text12W700("512 Mb", + clr: AppColor.portoflioCardTextColor), + Gap( + 7.w, + ), + Image.asset( + AppImages.donwloadIcon, + height: 20.sp, + ), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/features/MainScreens/Invest/presentation/widgets/invest_video_section.dart b/lib/features/MainScreens/Invest/presentation/widgets/invest_video_section.dart index 9ac1364..0c0358f 100644 --- a/lib/features/MainScreens/Invest/presentation/widgets/invest_video_section.dart +++ b/lib/features/MainScreens/Invest/presentation/widgets/invest_video_section.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:tanami_app/core/routes/route_name.dart'; +import 'package:tanami_app/core/routes/routes.dart'; import 'package:tanami_app/core/styles/app_images.dart'; import '../../../../../core/styles/app_text.dart'; @@ -46,32 +48,38 @@ class InvestVideoSection extends StatelessWidget { shrinkWrap: true, itemCount: videos.length, itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(bottom: 18.0), - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(18.0), - ), - child: Stack( - children: [ - Image.asset( - videos[index], - fit: BoxFit.cover, - height: 160.h, - width: 1.sw, - ), - Positioned( - bottom: 0.0, - child: Image.asset( - AppImages.academyCardOverlay, + return InkWell( + onTap: () { + goRouter.pushNamed(RouteName.vimeoScreen, + pathParameters: {"videoUrl": "989972367"}); + }, + child: Padding( + padding: const EdgeInsets.only(bottom: 18.0), + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(18.0), + ), + child: Stack( + children: [ + Image.asset( + videos[index], + fit: BoxFit.cover, + height: 160.h, + width: 1.sw, ), - ), - Positioned.fill( - child: Image.asset( - AppImages.videoPlayIcon, + Positioned( + bottom: 0.0, + child: Image.asset( + AppImages.academyCardOverlay, + ), ), - ) - ], + Positioned.fill( + child: Image.asset( + AppImages.videoPlayIcon, + ), + ) + ], + ), ), ), ); diff --git a/lib/features/MainScreens/Settings/domain/repository/settings_api.dart b/lib/features/MainScreens/Settings/domain/repository/settings_api.dart new file mode 100644 index 0000000..c329c54 --- /dev/null +++ b/lib/features/MainScreens/Settings/domain/repository/settings_api.dart @@ -0,0 +1,11 @@ +import '../../../../../Api_Helper/base_manager.dart'; +import '../../../../../shared/api/api_endpoints.dart'; +import '../../../../../shared/api/network_api_services.dart'; + +class SettingsApi { + Future updateNotification() async { + String url = ApiEndpoints.updateNotificationApi; + final response = await NetworkApiService().post(url, {}); + return response; + } +} diff --git a/lib/features/MainScreens/Settings/presentation/widgets/user_account_section.dart b/lib/features/MainScreens/Settings/presentation/widgets/user_account_section.dart index 977ef0a..410feab 100644 --- a/lib/features/MainScreens/Settings/presentation/widgets/user_account_section.dart +++ b/lib/features/MainScreens/Settings/presentation/widgets/user_account_section.dart @@ -12,6 +12,17 @@ class UserAccountSection extends StatelessWidget { @override Widget build(BuildContext context) { + String getInitials(String name) { + List words = name.split(' '); + String initials = ''; + for (var word in words) { + if (word.isNotEmpty) { + initials += word[0].toUpperCase(); + } + } + return initials; + } + var localizations = AppLocalizations.of(context); return Padding( padding: const EdgeInsets.symmetric( @@ -65,7 +76,8 @@ class UserAccountSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ TextWidget().text15W500( - 'AN', + getInitials( + "${Globalconst.firstName} ${Globalconst.lastName}"), clr: const Color(0xFF066123), ) ], @@ -80,7 +92,7 @@ class UserAccountSection extends StatelessWidget { TextWidget().text14W500( Globalconst.firstName.isEmpty ? "User" - : Globalconst.firstName, + : "${Globalconst.firstName} ${Globalconst.lastName}", clr: const Color(0xFF191B1E), ), const Gap(4), diff --git a/lib/features/biometric/bloc/biometric_bloc.dart b/lib/features/biometric/bloc/biometric_bloc.dart index dc5c046..bd03792 100644 --- a/lib/features/biometric/bloc/biometric_bloc.dart +++ b/lib/features/biometric/bloc/biometric_bloc.dart @@ -7,6 +7,7 @@ import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; import 'package:tanami_app/features/biometric/domain/repository/biometric_api.dart'; import '../../../Api_Helper/base_manager.dart'; +import '../../../core/utils/device_info/device_info_data.dart'; import 'biometric_event.dart'; import 'biometric_state.dart'; @@ -57,16 +58,17 @@ class BiometricBloc extends Bloc { if (authenticated) { Map biometricLoginData = { - "token": await secureStorageService.read("temp_token"), + "code": await secureStorageService.read("temp_token"), + "deviceId": await DeviceInfoData().getDeviceId(), }; ResponseData response = await BiometricAPIServices().biometricLoginApi(biometricLoginData); if (response.status == ResponseStatus.SUCCESS) { await secureStorageService.write( - 'accesstoken', response.data["data"]["accessToken"]); + 'accesstoken', response.data["data"]["access"]["token"]); await secureStorageService.write( - 'refreshtoken', response.data["data"]["refreshToken"]); + 'refreshtoken', response.data["data"]["refresh"]["token"]); emit(BiometricAuthenticated()); } else { if (Globalconst.languageSelected == "en") { diff --git a/lib/features/changePassword/bloc/change_password_bloc.dart b/lib/features/changePassword/bloc/change_password_bloc.dart index 1a404d1..4e4a9ad 100644 --- a/lib/features/changePassword/bloc/change_password_bloc.dart +++ b/lib/features/changePassword/bloc/change_password_bloc.dart @@ -1,6 +1,8 @@ import 'package:bloc/bloc.dart'; import 'package:flutter/material.dart'; +import 'package:tanami_app/features/changePassword/domain/repository/change_password_api.dart'; +import '../../../Api_Helper/base_manager.dart'; import 'change_password_event.dart'; import 'change_password_state.dart'; @@ -28,15 +30,18 @@ class ChangePasswordBloc } emit(ChangePasswordLoading()); try { - // Simulate API call - await Future.delayed(const Duration(seconds: 2)); - // Replace the next line with actual API call - final isSuccess = await _mockLoginApi(event.password); - if (isSuccess) { + Map dataForm = { + "oldPassword": event.currentPassword, + "newPassword": event.password, + "confirmNewPassword": event.repeatPassword, + }; + ResponseData response = + await ChangePasswordApi().changePassword(dataForm); + + if (response.status == ResponseStatus.SUCCESS) { emit(ChangePasswordSuccess()); } else { - emit(const ChangePasswordFailure( - "Failed. Please check your credentials.")); + emit(ChangePasswordFailure(response.message)); } } catch (e) { emit(ChangePasswordFailure(e.toString())); diff --git a/lib/features/changePassword/domain/repository/change_password_api.dart b/lib/features/changePassword/domain/repository/change_password_api.dart new file mode 100644 index 0000000..754b3cd --- /dev/null +++ b/lib/features/changePassword/domain/repository/change_password_api.dart @@ -0,0 +1,11 @@ +import '../../../../../Api_Helper/base_manager.dart'; +import '../../../../../shared/api/api_endpoints.dart'; +import '../../../../../shared/api/network_api_services.dart'; + +class ChangePasswordApi { + Future changePassword(Map data) async { + String url = ApiEndpoints.updatePasswordApi; + final response = await NetworkApiService().post(url, data); + return response; + } +} diff --git a/lib/features/changePassword/presentation/widgets/change_password_form.dart b/lib/features/changePassword/presentation/widgets/change_password_form.dart index 39e6947..8d5fc73 100644 --- a/lib/features/changePassword/presentation/widgets/change_password_form.dart +++ b/lib/features/changePassword/presentation/widgets/change_password_form.dart @@ -3,12 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:gap/gap.dart'; import 'package:tanami_app/core/styles/app_text.dart'; -import '../../../../core/routes/route_name.dart'; -import '../../../../core/routes/routes.dart'; import '../../../../core/styles/app_color.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; import '../../../../shared/components/bloc/password_field/password_visibility_bloc.dart'; import '../../../../shared/components/button_widget.dart'; +import '../../../../shared/components/forgot_password_log_out_dialog.dart'; import '../../../../shared/components/form_label_textfield.dart'; import '../../../../shared/components/text_widget.dart'; import '../../bloc/change_password_bloc.dart'; @@ -41,7 +40,7 @@ class RestorePasswordForm extends StatelessWidget { child: FormLabelTextField( hintText: localizations.translate(AppText.enterPassword), title: localizations.translate(AppText.currentPsswordText), - type: AppText.password.toLowerCase(), + type: "login-password", textEditingController: restorePasswordBloc.currentPasswordTextField, ), @@ -65,6 +64,8 @@ class RestorePasswordForm extends StatelessWidget { type: AppText.password.toLowerCase(), textEditingController: restorePasswordBloc.repeatPasswordTextField, + originalPasswordController: + restorePasswordBloc.passwordTextField, ), ), const Gap(12), @@ -72,8 +73,7 @@ class RestorePasswordForm extends StatelessWidget { alignment: Alignment.topRight, child: ButtonWidget().textBtn( function: () { - goRouter.pushNamed( - RouteName.forgotPasswordPhoneVerificationScreen); + forgotPasswordlogoutdialog(context); }, text: TextWidget().text15W400( localizations.translate(AppText.forgotPassword), diff --git a/lib/features/contactAdmin/bloc/contact_admin_bloc.dart b/lib/features/contactAdmin/bloc/contact_admin_bloc.dart new file mode 100644 index 0000000..554279f --- /dev/null +++ b/lib/features/contactAdmin/bloc/contact_admin_bloc.dart @@ -0,0 +1,34 @@ +// video_bloc.dart +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:tanami_app/features/contactAdmin/bloc/contact_admin_event.dart'; +import 'package:tanami_app/features/contactAdmin/bloc/contact_admin_state.dart'; +import 'package:tanami_app/features/contactAdmin/domain/model/contact_admin_model.dart'; +import 'package:tanami_app/features/contactAdmin/domain/repository/contact_admin_api.dart'; + +import '../../../Api_Helper/base_manager.dart'; +import '../../../Globalconst.dart'; + +class ContactAdminBloc extends Bloc { + ContactAdminBloc() : super(ContactAdminInitial()) { + on(mapEventToState); + } + + Future mapEventToState( + ContactAdminEvent event, Emitter emit) async { + emit(ContactAdminLoading()); + try { + ResponseData response = await ContactAdminApi().contactAdminApi(); + if (response.status == ResponseStatus.SUCCESS) { + ContactAdminModel contactAdminModel = + ContactAdminModel.fromJson(response.data); + emit(ContactAdminLoaded(contactAdminModel)); + } + } catch (e) { + emit(ContactAdminError(Globalconst.languageSelected == "en" + ? 'Oops Something went wrong' + : "تبا شيء ما حدث بشكل خاطئ")); + } + } +} diff --git a/lib/features/contactAdmin/bloc/contact_admin_event.dart b/lib/features/contactAdmin/bloc/contact_admin_event.dart new file mode 100644 index 0000000..71bbdda --- /dev/null +++ b/lib/features/contactAdmin/bloc/contact_admin_event.dart @@ -0,0 +1,16 @@ +// video_event.dart +import 'package:equatable/equatable.dart'; + +abstract class ContactAdminEvent extends Equatable { + const ContactAdminEvent(); + + @override + List get props => []; +} + +class FetchContactData extends ContactAdminEvent { + const FetchContactData(); + + @override + List get props => []; +} diff --git a/lib/features/contactAdmin/bloc/contact_admin_state.dart b/lib/features/contactAdmin/bloc/contact_admin_state.dart new file mode 100644 index 0000000..847bdd6 --- /dev/null +++ b/lib/features/contactAdmin/bloc/contact_admin_state.dart @@ -0,0 +1,32 @@ +// video_state.dart +import 'package:equatable/equatable.dart'; +import 'package:tanami_app/features/contactAdmin/domain/model/contact_admin_model.dart'; + +abstract class ContactAdminState extends Equatable { + const ContactAdminState(); + + @override + List get props => []; +} + +class ContactAdminInitial extends ContactAdminState {} + +class ContactAdminLoading extends ContactAdminState {} + +class ContactAdminLoaded extends ContactAdminState { + final ContactAdminModel contactAdminModel; + + const ContactAdminLoaded(this.contactAdminModel); + + @override + List get props => [contactAdminModel]; +} + +class ContactAdminError extends ContactAdminState { + final String message; + + const ContactAdminError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/features/contactAdmin/domain/model/contact_admin_model.dart b/lib/features/contactAdmin/domain/model/contact_admin_model.dart new file mode 100644 index 0000000..078a1c2 --- /dev/null +++ b/lib/features/contactAdmin/domain/model/contact_admin_model.dart @@ -0,0 +1,84 @@ +class ContactAdminModel { + int? statusCode; + List? data; + String? message; + bool? success; + + ContactAdminModel({this.statusCode, this.data, this.message, this.success}); + + ContactAdminModel.fromJson(Map json) { + statusCode = json['statusCode']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data!.add(Data.fromJson(v)); + }); + } + message = json['message']; + success = json['success']; + } + + Map toJson() { + final Map data = {}; + data['statusCode'] = statusCode; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + data['message'] = message; + data['success'] = success; + return data; + } +} + +class Data { + int? id; + String? phoneNumber; + String? emailAddress; + String? websiteUrl; + bool? isActive; + Null createdBy; + Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; + + Data( + {this.id, + this.phoneNumber, + this.emailAddress, + this.websiteUrl, + this.isActive, + this.createdBy, + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); + + Data.fromJson(Map json) { + id = json['id']; + phoneNumber = json['phoneNumber']; + emailAddress = json['emailAddress']; + websiteUrl = json['websiteUrl']; + isActive = json['isActive']; + createdBy = json['createdBy']; + modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['phoneNumber'] = phoneNumber; + data['emailAddress'] = emailAddress; + data['websiteUrl'] = websiteUrl; + data['isActive'] = isActive; + data['createdBy'] = createdBy; + data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; + return data; + } +} diff --git a/lib/features/contactAdmin/domain/repository/contact_admin_api.dart b/lib/features/contactAdmin/domain/repository/contact_admin_api.dart new file mode 100644 index 0000000..098974d --- /dev/null +++ b/lib/features/contactAdmin/domain/repository/contact_admin_api.dart @@ -0,0 +1,11 @@ +import '../../../../../Api_Helper/base_manager.dart'; +import '../../../../../shared/api/api_endpoints.dart'; +import '../../../../../shared/api/network_api_services.dart'; + +class ContactAdminApi { + Future contactAdminApi() async { + String url = ApiEndpoints.contactAdminApi; + final response = await NetworkApiService().get(url); + return response; + } +} diff --git a/lib/features/contactAdmin/presentation/pages/contact_admin_screen.dart b/lib/features/contactAdmin/presentation/pages/contact_admin_screen.dart index 6d41c22..383ebdd 100644 --- a/lib/features/contactAdmin/presentation/pages/contact_admin_screen.dart +++ b/lib/features/contactAdmin/presentation/pages/contact_admin_screen.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tanami_app/features/contactAdmin/bloc/contact_admin_event.dart'; import 'package:tanami_app/features/contactAdmin/presentation/widgets/bottom_section.dart'; import '../../../../core/styles/app_color.dart'; import '../../../../core/styles/app_text.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; import '../../../../shared/components/appbar_widget.dart'; +import '../../bloc/contact_admin_bloc.dart'; +import '../../bloc/contact_admin_state.dart'; import '../widgets/top_section.dart'; class ContactAdminScreen extends StatelessWidget { @@ -14,17 +18,30 @@ class ContactAdminScreen extends StatelessWidget { Widget build(BuildContext context) { var localizations = AppLocalizations.of(context); - return Scaffold( - backgroundColor: AppColor.plainWhite, - appBar: AppBarWidget( - height: 75, - titleTxt: localizations.translate(AppText.contactAdminText), - ), - body: ListView( - children: [ - topSection(context), - bottomSection(context), - ], + return BlocProvider( + create: (context) => ContactAdminBloc()..add(const FetchContactData()), + child: Scaffold( + backgroundColor: AppColor.plainWhite, + appBar: AppBarWidget( + height: 75, + titleTxt: localizations.translate(AppText.contactAdminText), + ), + body: BlocBuilder( + builder: (context, state) { + if (state is ContactAdminLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is ContactAdminLoaded) { + return ListView( + children: [ + topSection(context), + bottomSection(context, state.contactAdminModel), + ], + ); + } else { + return const Center(child: Text('Something went wrong!')); + } + }, + ), ), ); } diff --git a/lib/features/contactAdmin/presentation/widgets/bottom_section.dart b/lib/features/contactAdmin/presentation/widgets/bottom_section.dart index afb55a5..d1f756f 100644 --- a/lib/features/contactAdmin/presentation/widgets/bottom_section.dart +++ b/lib/features/contactAdmin/presentation/widgets/bottom_section.dart @@ -5,11 +5,13 @@ import 'package:tanami_app/core/styles/app_color.dart'; import 'package:tanami_app/core/styles/app_images.dart'; import 'package:tanami_app/core/styles/app_text.dart'; import 'package:tanami_app/core/utils/url_launcher/url_launcher.dart'; +import 'package:tanami_app/features/contactAdmin/domain/model/contact_admin_model.dart'; import '../../../../core/utils/language/localizations_delegate.dart'; import '../../../../shared/components/text_widget.dart'; -Widget bottomSection(BuildContext context) { +Widget bottomSection( + BuildContext context, ContactAdminModel contactAdminModel) { var localizations = AppLocalizations.of(context); return Padding( padding: const EdgeInsets.all(22.0), @@ -19,18 +21,27 @@ Widget bottomSection(BuildContext context) { contactOption( icon: AppImages.byPhoneIcon, title: localizations.translate(AppText.byPhoneText), - subtitle: '+973 12345678', + subtitle: contactAdminModel.data![0].phoneNumber!, onTap: () { - launchPhone('+973 12345678'); + launchPhone(contactAdminModel.data![0].phoneNumber!); }, ), const Gap(16.0), contactOption( icon: AppImages.byMailIcon, title: localizations.translate(AppText.byEmailText), - subtitle: 'info@tanamicapital.com', + subtitle: contactAdminModel.data![0].emailAddress!, onTap: () { - launchEmail('info@tanamicapital.com'); + launchEmail(contactAdminModel.data![0].emailAddress!); + }, + ), + const Gap(16.0), + contactOption( + icon: AppImages.languageIcon, + title: localizations.translate(AppText.websiteText), + subtitle: contactAdminModel.data![0].websiteUrl!, + onTap: () { + launchWebsiteUrl("https://tanamicapital.com/"); }, ), ], diff --git a/lib/features/countrySelection/domain/model/get_country_model.dart b/lib/features/countrySelection/domain/model/get_country_model.dart index d62df32..2bec62f 100644 --- a/lib/features/countrySelection/domain/model/get_country_model.dart +++ b/lib/features/countrySelection/domain/model/get_country_model.dart @@ -1,58 +1,79 @@ class GetCountryModel { + int? statusCode; List? data; + String? message; + bool? success; - GetCountryModel({this.data}); + GetCountryModel({this.statusCode, this.data, this.message, this.success}); GetCountryModel.fromJson(Map json) { + statusCode = json['statusCode']; if (json['data'] != null) { data = []; json['data'].forEach((v) { data!.add(Data.fromJson(v)); }); } + message = json['message']; + success = json['success']; } Map toJson() { final Map data = {}; + data['statusCode'] = statusCode; if (this.data != null) { data['data'] = this.data!.map((v) => v.toJson()).toList(); } + data['message'] = message; + data['success'] = success; return data; } } class Data { - String? id; + int? id; String? countryName; String? countryCode; - String? isdCode; + String? iSDcode; String? flagIcon; - Null currencyXid; + int? maxDigits; + int? currencyXid; bool? isActive; Null createdBy; Null modifiedBy; + String? createdAt; + String? updatedAt; + Null deletedAt; Data( {this.id, this.countryName, this.countryCode, - this.isdCode, + this.iSDcode, this.flagIcon, + this.maxDigits, this.currencyXid, this.isActive, this.createdBy, - this.modifiedBy}); + this.modifiedBy, + this.createdAt, + this.updatedAt, + this.deletedAt}); Data.fromJson(Map json) { id = json['id']; countryName = json['countryName']; countryCode = json['countryCode']; - isdCode = json['isdCode']; + iSDcode = json['ISDcode']; flagIcon = json['flagIcon']; + maxDigits = json['maxDigits']; currencyXid = json['currency_xid']; isActive = json['isActive']; createdBy = json['createdBy']; modifiedBy = json['modifiedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + deletedAt = json['deletedAt']; } Map toJson() { @@ -60,12 +81,16 @@ class Data { data['id'] = id; data['countryName'] = countryName; data['countryCode'] = countryCode; - data['isdCode'] = isdCode; + data['ISDcode'] = iSDcode; data['flagIcon'] = flagIcon; + data['maxDigits'] = maxDigits; data['currency_xid'] = currencyXid; data['isActive'] = isActive; data['createdBy'] = createdBy; data['modifiedBy'] = modifiedBy; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['deletedAt'] = deletedAt; return data; } } diff --git a/lib/features/faq/presentation/pages/faq_screen.dart b/lib/features/faq/presentation/pages/faq_screen.dart index 01351ff..8a3dbec 100644 --- a/lib/features/faq/presentation/pages/faq_screen.dart +++ b/lib/features/faq/presentation/pages/faq_screen.dart @@ -17,11 +17,123 @@ class FAQScreen extends StatefulWidget { } class _AccountappState extends State { - int selectedTile = 0; + int gettingStartedselectedTile = 0; + int investmentSelectedTile = 0; + int knowledgeSelectedTile = 0; + int complianceSelectedTile = 0; @override Widget build(BuildContext context) { var localizations = AppLocalizations.of(context); + + List> gettingStartedList = [ + { + "title": localizations.translate(AppText.getStartedTitle1), + "answer": localizations.translate(AppText.geStartedDesc1), + }, + { + "title": localizations.translate(AppText.getStartedTitle2), + "answer": localizations.translate(AppText.geStartedDesc2), + }, + { + "title": localizations.translate(AppText.getStartedTitle3), + "answer": localizations.translate(AppText.geStartedDesc3), + } + ]; + + List> investmentList = [ + { + "title": localizations.translate(AppText.investmentTitle1), + "answer": localizations.translate(AppText.investmentDesc1), + }, + { + "title": localizations.translate(AppText.investmentTitle2), + "answer": localizations.translate(AppText.investmentDesc2), + }, + { + "title": localizations.translate(AppText.investmentTitle3), + "answer": localizations.translate(AppText.investmentDesc3), + }, + { + "title": localizations.translate(AppText.investmentTitle4), + "answer": localizations.translate(AppText.investmentDesc4), + }, + { + "title": localizations.translate(AppText.investmentTitle5), + "answer": localizations.translate(AppText.investmentDesc5), + }, + { + "title": localizations.translate(AppText.investmentTitle6), + "answer": localizations.translate(AppText.investmentDesc6), + }, + { + "title": localizations.translate(AppText.investmentTitle7), + "answer": localizations.translate(AppText.investmentDesc7), + }, + { + "title": localizations.translate(AppText.investmentTitle8), + "answer": localizations.translate(AppText.investmentDesc8), + }, + { + "title": localizations.translate(AppText.investmentTitle9), + "answer": localizations.translate(AppText.investmentDesc9), + }, + { + "title": localizations.translate(AppText.investmentTitle10), + "answer": localizations.translate(AppText.investmentDesc10), + }, + { + "title": localizations.translate(AppText.investmentTitle11), + "answer": localizations.translate(AppText.investmentDesc11), + }, + { + "title": localizations.translate(AppText.investmentTitle12), + "answer": localizations.translate(AppText.investmentDesc12), + }, + { + "title": localizations.translate(AppText.investmentTitle13), + "answer": localizations.translate(AppText.investmentDesc13), + } + ]; + + List> knowledgeAndEducationList = [ + { + "title": localizations.translate(AppText.knowledgeTitle1), + "answer": localizations.translate(AppText.knowledgeDesc1), + }, + { + "title": localizations.translate(AppText.knowledgeTitle2), + "answer": localizations.translate(AppText.knowledgeDesc2), + }, + { + "title": localizations.translate(AppText.knowledgeTitle3), + "answer": localizations.translate(AppText.knowledgeDesc3), + } + ]; + + List> complianceSecurityAndSupportList = [ + { + "title": localizations.translate(AppText.securityTitle1), + "answer": localizations.translate(AppText.securityDesc1), + }, + { + "title": localizations.translate(AppText.securityTitle2), + "answer": localizations.translate(AppText.securityDesc2), + }, + { + "title": localizations.translate(AppText.securityTitle3), + "answer": localizations.translate(AppText.securityDesc3), + }, + { + "title": localizations.translate(AppText.securityTitle4), + "answer": localizations.translate(AppText.securityDesc4), + }, + { + "title": localizations.translate(AppText.securityTitle5), + "answer": localizations.translate(AppText.securityDesc5), + } + ]; + return Scaffold( backgroundColor: AppColor.plainWhite, appBar: AppBarWidget( @@ -37,7 +149,7 @@ class _AccountappState extends State { Padding( padding: EdgeInsets.only(left: 16.w, top: 23.h), child: Text( - localizations.translate(AppText.faqTitle), + localizations.translate(AppText.gettingStarted), style: GoogleFonts.dmSans( fontSize: 18.sp, color: AppColor.charcoalColor, @@ -47,17 +159,16 @@ class _AccountappState extends State { Gap(17.h), Container( margin: const EdgeInsets.only( - bottom: 50, + bottom: 20, top: 15, ), - height: 1.sh, child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: ExpansionTileGroup( spaceBetweenItem: 23, toggleType: ToggleType.expandOnlyCurrent, children: List.generate( - faList.length, + gettingStartedList.length, (index) => ExpansionTileItem( isHasBottomBorder: true, isHasLeftBorder: true, @@ -77,21 +188,21 @@ class _AccountappState extends State { onExpansionChanged: (bool expanding) { if (expanding) { setState(() { - selectedTile = index; + gettingStartedselectedTile = index; }); } else { setState(() { - selectedTile = -1; + gettingStartedselectedTile = -1; }); } }, backgroundColor: AppColor.portfolioCardBgColor, childrenPadding: EdgeInsets.only( left: 0.w, right: 0.w, bottom: 8.h, top: 10.h), - initiallyExpanded: index == selectedTile, + initiallyExpanded: index == gettingStartedselectedTile, isHasTrailing: false, title: Text( - faList[index]['title']!, + gettingStartedList[index]['title']!, maxLines: 3, style: GoogleFonts.dmSans( fontSize: 16.sp, @@ -111,7 +222,278 @@ class _AccountappState extends State { padding: EdgeInsets.symmetric( horizontal: 12.w, vertical: 10.h), child: Text( - faList[index]['answer']!, + gettingStartedList[index]['answer']!, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.investPaymentTextColor, + ), + ), + ), + ) + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 16.w, top: 23.h), + child: Text( + localizations.translate(AppText.investment), + style: GoogleFonts.dmSans( + fontSize: 18.sp, + color: AppColor.charcoalColor, + fontWeight: FontWeight.w600), + ), + ), + Gap(17.h), + Container( + margin: const EdgeInsets.only( + bottom: 20, + top: 15, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: ExpansionTileGroup( + spaceBetweenItem: 23, + toggleType: ToggleType.expandOnlyCurrent, + children: List.generate( + investmentList.length, + (index) => ExpansionTileItem( + isHasBottomBorder: true, + isHasLeftBorder: true, + isHasRightBorder: true, + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: + const Offset(0, 3), // changes position of shadow + ), + ], + isHasTopBorder: true, + collapsedBackgroundColor: AppColor.portfolioCardBgColor, + borderRadius: BorderRadius.circular(10), + onExpansionChanged: (bool expanding) { + if (expanding) { + setState(() { + investmentSelectedTile = index; + }); + } else { + setState(() { + investmentSelectedTile = -1; + }); + } + }, + backgroundColor: AppColor.portfolioCardBgColor, + childrenPadding: EdgeInsets.only( + left: 0.w, right: 0.w, bottom: 8.h, top: 10.h), + initiallyExpanded: index == investmentSelectedTile, + isHasTrailing: false, + title: Text( + investmentList[index]['title']!, + maxLines: 3, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.otpTextColor, + fontWeight: FontWeight.w600), + ), + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + width: 1.sw, + // height: 109.h, + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: BorderRadius.circular(10.r), + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 10.h), + child: Text( + investmentList[index]['answer']!, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.investPaymentTextColor, + ), + ), + ), + ) + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 16.w, top: 23.h), + child: Text( + localizations.translate(AppText.knowledgeAndEducation), + style: GoogleFonts.dmSans( + fontSize: 18.sp, + color: AppColor.charcoalColor, + fontWeight: FontWeight.w600), + ), + ), + Gap(17.h), + Container( + margin: const EdgeInsets.only( + bottom: 20, + top: 15, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: ExpansionTileGroup( + spaceBetweenItem: 23, + toggleType: ToggleType.expandOnlyCurrent, + children: List.generate( + knowledgeAndEducationList.length, + (index) => ExpansionTileItem( + isHasBottomBorder: true, + isHasLeftBorder: true, + isHasRightBorder: true, + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: + const Offset(0, 3), // changes position of shadow + ), + ], + isHasTopBorder: true, + collapsedBackgroundColor: AppColor.portfolioCardBgColor, + borderRadius: BorderRadius.circular(10), + onExpansionChanged: (bool expanding) { + if (expanding) { + setState(() { + knowledgeSelectedTile = index; + }); + } else { + setState(() { + knowledgeSelectedTile = -1; + }); + } + }, + backgroundColor: AppColor.portfolioCardBgColor, + childrenPadding: EdgeInsets.only( + left: 0.w, right: 0.w, bottom: 8.h, top: 10.h), + initiallyExpanded: index == knowledgeSelectedTile, + isHasTrailing: false, + title: Text( + knowledgeAndEducationList[index]['title']!, + maxLines: 3, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.otpTextColor, + fontWeight: FontWeight.w600), + ), + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + width: 1.sw, + // height: 109.h, + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: BorderRadius.circular(10.r), + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 10.h), + child: Text( + knowledgeAndEducationList[index]['answer']!, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.investPaymentTextColor, + ), + ), + ), + ) + ], + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 16.w, top: 23.h), + child: Text( + localizations.translate(AppText.complianceSecurityAndSupport), + style: GoogleFonts.dmSans( + fontSize: 18.sp, + color: AppColor.charcoalColor, + fontWeight: FontWeight.w600), + ), + ), + Gap(17.h), + Container( + margin: const EdgeInsets.only( + bottom: 20, + top: 15, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: ExpansionTileGroup( + spaceBetweenItem: 23, + toggleType: ToggleType.expandOnlyCurrent, + children: List.generate( + complianceSecurityAndSupportList.length, + (index) => ExpansionTileItem( + isHasBottomBorder: true, + isHasLeftBorder: true, + isHasRightBorder: true, + boxShadow: [ + BoxShadow( + color: AppColor.plainBlack.withOpacity(0.15), + spreadRadius: 2, + blurRadius: 10, + offset: + const Offset(0, 3), // changes position of shadow + ), + ], + isHasTopBorder: true, + collapsedBackgroundColor: AppColor.portfolioCardBgColor, + borderRadius: BorderRadius.circular(10), + onExpansionChanged: (bool expanding) { + if (expanding) { + setState(() { + complianceSelectedTile = index; + }); + } else { + setState(() { + complianceSelectedTile = -1; + }); + } + }, + backgroundColor: AppColor.portfolioCardBgColor, + childrenPadding: EdgeInsets.only( + left: 0.w, right: 0.w, bottom: 8.h, top: 10.h), + initiallyExpanded: index == complianceSelectedTile, + isHasTrailing: false, + title: Text( + complianceSecurityAndSupportList[index]['title']!, + maxLines: 3, + style: GoogleFonts.dmSans( + fontSize: 16.sp, + color: AppColor.otpTextColor, + fontWeight: FontWeight.w600), + ), + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + width: 1.sw, + // height: 109.h, + decoration: BoxDecoration( + color: AppColor.plainWhite, + borderRadius: BorderRadius.circular(10.r), + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 10.h), + child: Text( + complianceSecurityAndSupportList[index] + ['answer']!, style: GoogleFonts.dmSans( fontSize: 16.sp, color: AppColor.investPaymentTextColor, @@ -131,31 +513,3 @@ class _AccountappState extends State { ); } } - -List> faList = [ - { - "title": "What is fractional property investment?", - "answer": - "Fractional property investment allows multiple investors to own a fraction of a property. This means you can invest in real estate without needing to purchase an entire property. Instead, you buy a share of the property, and your investment is proportionate to the amount you invest.", - }, - { - "title": "How does the app work?", - "answer": - "Our app simplifies the process of fractional property investment. Users can browse available properties, view detailed information and investment opportunities, and purchase shares in properties directly through the app. The app also provides tools to manage investments, track performance, and receive updates on property management." - }, - { - "title": "Is fractional property investment safe?", - "answer": - "While all investments carry risk, fractional property investment can be a safer option due to diversification. By investing in multiple properties, you can spread risk and reduce the impact of any single property’s performance on your overall investment." - }, - { - "title": "What kind of properties can I invest in?", - "answer": - "The app offers a variety of properties, including residential, commercial, and mixed-use properties. Each listing provides detailed information about the property, including location, projected returns, and investment terms." - }, - { - "title": "Is there a minimum investment amount?", - "answer": - "Yes, each property listing will specify the minimum investment amount required. This amount varies depending on the property and the terms of the investment." - } -]; diff --git a/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart index fb2d5ea..158f722 100644 --- a/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart +++ b/lib/features/forgotPassword/presentation/bloc/restore_password_bloc.dart @@ -31,8 +31,8 @@ class RestorePasswordBloc try { log(Globalconst.token); Map dataForm = { - "token": Globalconst.token, - "passwordHash": event.password + "code": Globalconst.token, + "password_hash": event.password }; ResponseData response = await ForgotPasswordApi().resetPasswordApi(dataForm); diff --git a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart index 55531bf..1ce20e8 100644 --- a/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart +++ b/lib/features/forgotPassword/presentation/widgets/restore_password_phone_verification_form.dart @@ -39,7 +39,7 @@ class RestorePasswordPhoneVerificationForm extends StatelessWidget { selectedCountry = state.selectedIndex; if (countryState is CountryLoaded) { restorePasswordBloc.isdcode = - "${countryState.countryModel.data![selectedCountry].isdCode}"; + "${countryState.countryModel.data![selectedCountry].iSDcode}"; restorePasswordBloc.countrySelectionTextField.text = countryState .countryModel.data![selectedCountry].countryName .toString(); @@ -53,7 +53,7 @@ class RestorePasswordPhoneVerificationForm extends StatelessWidget { restorePasswordBloc.isdcode, expectedLength!); } restorePasswordBloc.countryId = - countryState.countryModel.data![selectedCountry].id!; + countryState.countryModel.data![selectedCountry].id!.toString(); flag = "${ApiEndpoints.base}${countryState.countryModel.data![selectedCountry].flagIcon}"; } diff --git a/lib/features/languageChange/domain/repositories/language_api.dart b/lib/features/languageChange/domain/repositories/language_api.dart new file mode 100644 index 0000000..2beeabd --- /dev/null +++ b/lib/features/languageChange/domain/repositories/language_api.dart @@ -0,0 +1,11 @@ +import '../../../../../Api_Helper/base_manager.dart'; +import '../../../../../shared/api/api_endpoints.dart'; +import '../../../../../shared/api/network_api_services.dart'; + +class LanguageApi { + Future updateLanguage(Map data) async { + String url = ApiEndpoints.updateLanguageApi; + final response = await NetworkApiService().post(url, data); + return response; + } +} diff --git a/lib/features/languageChange/presentation/bloc/choose_language_bloc.dart b/lib/features/languageChange/presentation/bloc/choose_language_bloc.dart index b5f007c..7930549 100644 --- a/lib/features/languageChange/presentation/bloc/choose_language_bloc.dart +++ b/lib/features/languageChange/presentation/bloc/choose_language_bloc.dart @@ -1,5 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../Api_Helper/base_manager.dart'; +import '../../domain/repositories/language_api.dart'; import 'choose_language_event.dart'; import 'choose_language_state.dart'; @@ -15,6 +17,17 @@ class ChooseLanguageBloc emit(ChooseLanguageSelectionChanged(event.selectedIndex)); } + Future langaugeChangeApiCall(String languageCode) async { + Map languageData = {"languageCode": languageCode}; + + ResponseData response = await LanguageApi().updateLanguage(languageData); + if (response.status == ResponseStatus.SUCCESS) { + return "success"; + } else { + return "failed"; + } + } + void _onResetRadioSelection( ResetRadioSelection event, Emitter emit) { emit(ChooseLanguageInitial()); diff --git a/lib/features/languageChange/presentation/widgets/bottom_section.dart b/lib/features/languageChange/presentation/widgets/bottom_section.dart index b1de861..5b1e934 100644 --- a/lib/features/languageChange/presentation/widgets/bottom_section.dart +++ b/lib/features/languageChange/presentation/widgets/bottom_section.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -32,18 +34,32 @@ Widget bottomSection(BuildContext context) { child: ButtonWidget().elevatedBtn( txtClr: AppColor.plainWhite, function: () async { - var newLocale = radioBloc.selectedCountry == 1 - ? const Locale('ar') - : const Locale('en'); - context.read().add(ChangeLanguage(newLocale)); - if (radioBloc.selectedCountry == 1) { - await secureStorageService.write('languageSelected', "ar"); - Globalconst.languageSelected = "ar"; - } else { - await secureStorageService.write('languageSelected', "en"); - Globalconst.languageSelected = "en"; - } - goRouter.pop(); + radioBloc + .langaugeChangeApiCall( + radioBloc.selectedCountry == 1 ? "AR" : "EN") + .then( + (value) async { + if (value == "success") { + var newLocale = radioBloc.selectedCountry == 1 + ? const Locale('ar') + : const Locale('en'); + context + .read() + .add(ChangeLanguage(newLocale)); + if (radioBloc.selectedCountry == 1) { + await secureStorageService.write('languageSelected', "ar"); + Globalconst.languageSelected = "ar"; + } else { + await secureStorageService.write('languageSelected', "en"); + Globalconst.languageSelected = "en"; + } + goRouter.pop(); + } else { + log(value.toString()); + //goRouter.pop(); + } + }, + ); }, text: localizations.translate(AppText.submitText), clr: AppColor.primaryColor2, diff --git a/lib/features/login/presentation/bloc/login_bloc.dart b/lib/features/login/presentation/bloc/login_bloc.dart index 7a18a06..7b812f4 100644 --- a/lib/features/login/presentation/bloc/login_bloc.dart +++ b/lib/features/login/presentation/bloc/login_bloc.dart @@ -37,27 +37,27 @@ class LoginBloc extends Bloc { emit(LoginLoading()); try { Map logindata = { - "countryId": event.countryId, - "phoneNumber": event.phoneNumber, - "passwordHash": event.password + "ISDcode": event.isdCode, + "mobileNumber": event.phoneNumber, + "password": event.password }; ResponseData response = await LoginAPI().LoginRequest(logindata); if (response.status == ResponseStatus.SUCCESS) { await secureStorageService.write( 'first_name', response.data['data']['firstName']); Globalconst.firstName = response.data['data']['firstName']; + Globalconst.lastName = response.data['data']['lastName']; await secureStorageService.write( - 'temp_token', response.data['data']['token']); - - emit(LoginSuccess()); - } else if (response.status == ResponseStatus.PRIVATE) { - if (response.message.toString() == "Master Pin is not created") { + 'temp_token', response.data['data']['code']); + if (response.data["data"]["isMasterPin"] == false) { await secureStorageService.write( - 'temp_token', response.data['data']['user']); + 'temp_token', response.data['data']['code']); emit(LoginMasterPinPending()); } else { - emit(LoginFailure(response.message.toString())); + emit(LoginSuccess()); } + } else if (response.status == ResponseStatus.PRIVATE) { + emit(LoginFailure(response.message.toString())); } else { emit(LoginFailure(response.message.toString())); } diff --git a/lib/features/login/presentation/bloc/login_event.dart b/lib/features/login/presentation/bloc/login_event.dart index 6524059..aa5a76d 100644 --- a/lib/features/login/presentation/bloc/login_event.dart +++ b/lib/features/login/presentation/bloc/login_event.dart @@ -12,12 +12,13 @@ class LoginSubmitted extends LoginEvent { final String password; final String countryResidence; final String countryId; + final String isdCode; - const LoginSubmitted( - this.phoneNumber, this.password, this.countryResidence, this.countryId); + const LoginSubmitted(this.phoneNumber, this.password, this.countryResidence, + this.countryId, this.isdCode); @override - List get props => [phoneNumber, password, countryResidence]; + List get props => [phoneNumber, password, countryResidence, isdCode]; } class LoginFormChanged extends LoginEvent { diff --git a/lib/features/login/presentation/widgets/bottom_section.dart b/lib/features/login/presentation/widgets/bottom_section.dart index ad324e2..c2368cc 100644 --- a/lib/features/login/presentation/widgets/bottom_section.dart +++ b/lib/features/login/presentation/widgets/bottom_section.dart @@ -103,7 +103,8 @@ class BottomSection extends StatelessWidget { loginbloc.phoneNumberTextField.text, loginbloc.passwordTextField.text, "", - loginbloc.countryId), + loginbloc.countryId, + loginbloc.isdcode), ) : null; }, diff --git a/lib/features/login/presentation/widgets/login_form.dart b/lib/features/login/presentation/widgets/login_form.dart index e7b41fe..be3baf6 100644 --- a/lib/features/login/presentation/widgets/login_form.dart +++ b/lib/features/login/presentation/widgets/login_form.dart @@ -46,7 +46,7 @@ class LoginForm extends StatelessWidget { .toString(); loginBloc.phoneNumberTextField.text = ""; loginBloc.isdcode = - "${countryState.countryModel.data![selectedCountry].isdCode}"; + "${countryState.countryModel.data![selectedCountry].iSDcode}"; if (PhoneNumberHintGenerator().countryPhoneLengths.containsKey( loginBloc.isdcode, )) { @@ -65,7 +65,7 @@ class LoginForm extends StatelessWidget { .countryModel.data![selectedCountry].countryName .toString(); Globalconst.isdcode = - "${countryState.countryModel.data![selectedCountry].isdCode}"; + "${countryState.countryModel.data![selectedCountry].iSDcode}"; } } }, builder: (context, state) { diff --git a/lib/features/otpVerification/presentation/bloc/otp_bloc.dart b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart index 6ad73d7..58c78d3 100644 --- a/lib/features/otpVerification/presentation/bloc/otp_bloc.dart +++ b/lib/features/otpVerification/presentation/bloc/otp_bloc.dart @@ -34,7 +34,7 @@ class OtpBloc extends Bloc { emit(OtpSubmitting()); try { Map otpdata = { - "token": await secureStorageService.read("temp_token"), + "code": await secureStorageService.read("temp_token"), "otp": otpController.text }; ResponseData response = await OTPAPI().verifyOTP(otpdata); diff --git a/lib/features/register/presentation/bloc/register_bloc.dart b/lib/features/register/presentation/bloc/register_bloc.dart index 8a626ec..baefc63 100644 --- a/lib/features/register/presentation/bloc/register_bloc.dart +++ b/lib/features/register/presentation/bloc/register_bloc.dart @@ -32,13 +32,13 @@ class RegisterBloc extends Bloc { emit(RegisterLoading()); try { Map requestdata = { - "countryId": event.id, - "phoneNumber": event.phoneNumber + "ISDcode": event.isdcode, + "mobileNumber": event.phoneNumber }; ResponseData response = await OTPAPI().requestOTP(requestdata); if (response.status == ResponseStatus.SUCCESS) { - var data = response.data["data"]; - String token = data["token"]; + var data = response.data; + String token = data["data"]; await secureStorageService.write('temp_token', token); emit(RegisterSuccess(token)); //emit(OTPLoaded()); } else { @@ -53,7 +53,7 @@ class RegisterBloc extends Bloc { emit(RegisterLoading()); try { Map requestdata = { - "token": await secureStorageService.read('temp_token'), + "code": await secureStorageService.read('temp_token'), }; ResponseData response = await OTPAPI().resendOTPRequest(requestdata); if (response.status == ResponseStatus.SUCCESS) { diff --git a/lib/features/register/presentation/bloc/register_user_bloc.dart b/lib/features/register/presentation/bloc/register_user_bloc.dart index 467872b..0044e8d 100644 --- a/lib/features/register/presentation/bloc/register_user_bloc.dart +++ b/lib/features/register/presentation/bloc/register_user_bloc.dart @@ -36,11 +36,12 @@ class RegisterUserBloc extends Bloc { emit(RegisterUserLoading()); try { Map registerdata = { - "token": event.token, + "code": event.token, "firstName": event.firstName, "lastName": event.lastName, "emailAddress": event.email, - "passwordHash": event.password + "password": event.password, + "confirmPassword": event.password, }; ResponseData response = await RegisterAPIService().RegisterRequest(registerdata); diff --git a/lib/features/register/presentation/widgets/register_form.dart b/lib/features/register/presentation/widgets/register_form.dart index f6410c4..fe5f4dd 100644 --- a/lib/features/register/presentation/widgets/register_form.dart +++ b/lib/features/register/presentation/widgets/register_form.dart @@ -39,7 +39,7 @@ class RegisterForm extends StatelessWidget { .toString(); registerBloc.phoneNumberTextField.text = ""; registerBloc.isdcode = - "${countryState.countryModel.data![selectedCountry].isdCode}"; + "${countryState.countryModel.data![selectedCountry].iSDcode}"; if (PhoneNumberHintGenerator().countryPhoneLengths.containsKey( registerBloc.isdcode, )) { @@ -58,7 +58,7 @@ class RegisterForm extends StatelessWidget { .countryModel.data![selectedCountry].countryName .toString(); Globalconst.isdcode = - "${countryState.countryModel.data![selectedCountry].isdCode}"; + "${countryState.countryModel.data![selectedCountry].iSDcode}"; } } }, builder: (context, state) { diff --git a/lib/features/securePin/Repository/PinAPIServices.dart b/lib/features/securePin/Repository/PinAPIServices.dart index 0767efb..980ffa6 100644 --- a/lib/features/securePin/Repository/PinAPIServices.dart +++ b/lib/features/securePin/Repository/PinAPIServices.dart @@ -15,4 +15,10 @@ class PinAPIServices { final response = await NetworkApiService().post(url, data); return response; } + + Future updatePin(Map data) async { + String url = ApiEndpoints.updatePinApi; + final response = await NetworkApiService().post(url, data); + return response; + } } diff --git a/lib/features/securePin/presentation/bloc/pin_bloc.dart b/lib/features/securePin/presentation/bloc/pin_bloc.dart index d91c05b..a1e1fa1 100644 --- a/lib/features/securePin/presentation/bloc/pin_bloc.dart +++ b/lib/features/securePin/presentation/bloc/pin_bloc.dart @@ -25,7 +25,8 @@ class PinBloc extends Bloc { if (newPin.length <= 6) { if ((event.fromscreen == "login" || - event.fromscreen == "LoginedInUser") && + event.fromscreen == "LoginedInUser" || + event.fromscreen == "reset-pin") && newPin.length < 6) { emit(state.copyWith( pin: newPin, @@ -35,7 +36,8 @@ class PinBloc extends Bloc { )); } else { if ((event.fromscreen != "login" && - event.fromscreen != "LoginedInUser")) { + event.fromscreen != "LoginedInUser" && + event.fromscreen != "reset-pin")) { emit(state.copyWith( pin: newPin, pinComplete: newPin.length == 6, @@ -51,6 +53,8 @@ class PinBloc extends Bloc { } else if (event.fromscreen == "login" || event.fromscreen == "LoginedInUser") { add(VerifyLoginPinPressed(newPin, event.fromscreen)); + } else if (event.fromscreen == "reset-pin") { + add(UpdateLoginPinPressed(newPin, event.fromscreen)); } } } @@ -75,12 +79,17 @@ class PinBloc extends Bloc { emit(const PinLoading()); final storedPin = await secureStorageService.read('pin_code'); Map pindata = { - "token": await secureStorageService.read("temp_token"), - "masterPin": event.pin + "code": await secureStorageService.read("temp_token"), + "masterPin": event.pin, + "confirmMasterPin": event.pin, }; if (storedPin == event.pin) { ResponseData response = await PinAPIServices().ConfirmPin(pindata); if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write( + 'accesstoken', response.data["data"]["access"]["token"]); + await secureStorageService.write( + 'refreshtoken', response.data["data"]["refresh"]["token"]); await secureStorageService.write('userMPIN', storedPin.toString()); } @@ -101,19 +110,48 @@ class PinBloc extends Bloc { } }); + on((event, emit) async { + emit(const PinLoading()); + Map pindata = { + "masterPin": event.pin, + "confirmMasterPin": event.pin + }; + + ResponseData response = await PinAPIServices().updatePin(pindata); + if (response.status == ResponseStatus.SUCCESS) { + emit(state.copyWith( + pinComplete: true, + pin: state.pin, + isVerified: true, + error: '', + verifiedOnce: false)); + } else { + goRouter.pop(); + emit(state.copyWith( + pinComplete: true, + pin: state.pin, + isVerified: false, + error: Globalconst.languageSelected == "en" + ? response.message + : "رمز التعريف الشخصي غير صحيح", + verifiedOnce: true, + )); + } + }); + on((event, emit) async { emit(const PinLoading()); Map pindata = { - "token": await secureStorageService.read("temp_token"), + "code": await secureStorageService.read("temp_token"), "masterPin": event.pin }; ResponseData response = await PinAPIServices().Verifypin(pindata); if (response.status == ResponseStatus.SUCCESS) { await secureStorageService.write( - 'accesstoken', response.data["user"]["accessToken"]); + 'accesstoken', response.data["data"]["access"]["token"]); await secureStorageService.write( - 'refreshtoken', response.data["user"]["refreshToken"]); + 'refreshtoken', response.data["data"]["refresh"]["token"]); emit(state.copyWith( pinComplete: true, pin: state.pin, diff --git a/lib/features/securePin/presentation/bloc/pin_event.dart b/lib/features/securePin/presentation/bloc/pin_event.dart index 1f904dd..ae7a4fd 100644 --- a/lib/features/securePin/presentation/bloc/pin_event.dart +++ b/lib/features/securePin/presentation/bloc/pin_event.dart @@ -38,3 +38,12 @@ class VerifyLoginPinPressed extends PinEvent { @override List get props => [pin, fromscreen]; } + +class UpdateLoginPinPressed extends PinEvent { + final String pin; + final String fromscreen; + const UpdateLoginPinPressed(this.pin, this.fromscreen); + + @override + List get props => [pin, fromscreen]; +} diff --git a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart index 1b480ab..eec4bb1 100644 --- a/lib/features/securePin/presentation/widgets/pin_keypad_section.dart +++ b/lib/features/securePin/presentation/widgets/pin_keypad_section.dart @@ -50,6 +50,7 @@ class PinKey extends StatelessWidget { successToastMessage( context, localizations.translate(AppText.pinUpdatedSucess)); goRouter.pop(); + goRouter.pop(); } else { goRouter.pop(); context.read().add(SavePinPressed()); diff --git a/lib/features/splash/presentation/pages/splash_screen.dart b/lib/features/splash/presentation/pages/splash_screen.dart index 22b7f1e..2710057 100644 --- a/lib/features/splash/presentation/pages/splash_screen.dart +++ b/lib/features/splash/presentation/pages/splash_screen.dart @@ -19,6 +19,7 @@ class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { SecureStorageService secureStorageService = SecureStorageService(); + return Scaffold( backgroundColor: AppColor.plainWhite, body: MultiBlocProvider( diff --git a/lib/shared/api/api_endpoints.dart b/lib/shared/api/api_endpoints.dart index c7cf4d8..1514276 100644 --- a/lib/shared/api/api_endpoints.dart +++ b/lib/shared/api/api_endpoints.dart @@ -1,31 +1,48 @@ class ApiEndpoints { - static const base = "https://tanami.betadelivery.com/"; - static const baseurl = - "https://tanami.betadelivery.com/api/development/v1/"; //App Base url + static const base = + "https://admin.tanami.betadelivery.com/"; //"https://tanami.betadelivery.com/"; + static const baseurl = "https://admin.tanami.betadelivery.com/api/v1/"; + // "https://tanami.betadelivery.com/api/development/v1/"; //App Base url //Country - static const getcountryurl = "${baseurl}country/getAllCountry"; + static const getcountryurl = "${baseurl}country/"; //Register - static const requestotpapi = "${baseurl}auth/public/register"; - static const registerrequestapi = "${baseurl}auth/public/email-register"; + static const requestotpapi = "${baseurl}auth/user/register-mobileNumber"; + static const registerrequestapi = "${baseurl}auth/user/registration"; //OTP - static const requestresendotp = "${baseurl}auth/public/resend-otp"; - static const verifyotp = "${baseurl}auth/public/verify-otp"; + static const requestresendotp = "${baseurl}auth/user/resend-otp"; + static const verifyotp = "${baseurl}auth/user/verified-otp"; //Biometric - static const biometricUpdateapi = "${baseurl}auth/public/biometric-update"; - static const biometricLoginapi = "${baseurl}auth/public/biometric-login"; + static const biometricUpdateapi = "${baseurl}auth/user/set-biometric"; + static const biometricLoginapi = "${baseurl}auth/user/login-biometric"; //PIN - static const confirmpinapi = "${baseurl}auth/public/masterPin"; - static const verifypinapi = "${baseurl}auth/public/verify-materPin"; + static const confirmpinapi = "${baseurl}auth/user/set-masterPin"; + static const verifypinapi = "${baseurl}auth/user/login-masterPin"; + static const updatePinApi = "${baseurl}auth/user/update-pin"; //Login - static const loginapi = "${baseurl}auth/public/login"; + static const loginapi = "${baseurl}auth/user/login-password"; - //Forgot Password - static const forgotPasswordApi = "${baseurl}auth/public/forgot-password"; - static const resetPasswordApi = "${baseurl}auth/public/reset-password"; + //Password + static const forgotPasswordApi = "${baseurl}auth/user/forgot-password"; + static const resetPasswordApi = "${baseurl}auth/user/reset-password"; + static const updatePasswordApi = "${baseurl}auth/user/update-password"; + + //Notification + static const updateNotificationApi = + "${baseurl}auth/user/update-notification"; + + //Language + static const updateLanguageApi = "${baseurl}auth/user/update-language"; + + //Contact Admin + static const contactAdminApi = "${baseurl}contactDetails/user/"; + + //IO + static const availableIOApi = "${baseurl}io/available"; + static const closedIOApi = "${baseurl}io/closed"; } diff --git a/lib/shared/api/network_api_services.dart b/lib/shared/api/network_api_services.dart index d01e1fe..c3c2460 100644 --- a/lib/shared/api/network_api_services.dart +++ b/lib/shared/api/network_api_services.dart @@ -1,11 +1,16 @@ // common_api.dart +import 'dart:developer'; + import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import '../../Api_Helper/base_manager.dart'; +import '../../core/utils/secure/secure_storage_service.dart'; class NetworkApiService { + final SecureStorageService secureStorageService = SecureStorageService(); + final Dio _dio = Dio(BaseOptions( validateStatus: (status) { return status != null && @@ -16,12 +21,18 @@ class NetworkApiService { // Common function for GET requests Future get(String url, {Map? queryParameters}) async { + String token = await secureStorageService.read("accesstoken") ?? ""; if (kDebugMode) { print("api url is >>> $url"); } Response response; try { - response = await _dio.get(url); + response = await _dio.get(url, + options: (token == "") + ? Options() + : Options(headers: { + "x-auth-token": token, + })); if (response.statusCode == 201 || response.statusCode == 200) { return ResponseData("success", ResponseStatus.SUCCESS, data: response.data); @@ -52,41 +63,30 @@ class NetworkApiService { // Common function for POST requests Future post(String url, dynamic data) async { + String token = await secureStorageService.read("accesstoken") ?? ""; + log(token); if (kDebugMode) { print("data >>> $data"); print("api url is >>> $url"); } try { - var response = await _dio.post( - url, - data: data, - ); + var response = await _dio.post(url, + data: data, + options: (token == "") + ? Options() + : Options(headers: { + "x-auth-token": token, + })); if (response.statusCode == 201 || response.statusCode == 200) { return ResponseData("success", ResponseStatus.SUCCESS, data: response.data); } else if (response.statusCode == 400) { - if (response.data['message'] == "Master Pin is not created") { - return ResponseData( - response.data['message'], ResponseStatus.PRIVATE, - data: response.data); - } else if (response.data['error']['message'] == - "MASTER PIN NOT MATCH") { - return ResponseData( - response.data['error']['message'], ResponseStatus.PRIVATE, - data: response.data); - } else if (response.data['error']['message'] == - "Account already exists. Please Login instead.") { - return ResponseData( - response.data['error']['message'], ResponseStatus.PRIVATE, - data: response.data); - } else { - return ResponseData( - response.data['error']['message'], ResponseStatus.PRIVATE, - data: response.data); - } + return ResponseData( + response.data['message'], ResponseStatus.PRIVATE, + data: response.data); } else if (response.statusCode == 401) { return ResponseData( - response.data['error']['message'], ResponseStatus.PRIVATE, + response.data['message'], ResponseStatus.PRIVATE, data: response.data); } else if (response.statusCode == 500) { return ResponseData( @@ -95,8 +95,7 @@ class NetworkApiService { } else { try { return ResponseData( - response.data['error']['message'].toString(), - ResponseStatus.FAILED); + response.data['message'].toString(), ResponseStatus.FAILED); } catch (_) { return ResponseData( data: response.data, diff --git a/lib/shared/components/bloc/toggle/toggle_bloc.dart b/lib/shared/components/bloc/toggle/toggle_bloc.dart index 97e4623..5c22f5e 100644 --- a/lib/shared/components/bloc/toggle/toggle_bloc.dart +++ b/lib/shared/components/bloc/toggle/toggle_bloc.dart @@ -1,6 +1,12 @@ +import 'dart:io'; + import 'package:bloc/bloc.dart'; import 'package:tanami_app/core/utils/secure/secure_storage_service.dart'; +import '../../../../Api_Helper/base_manager.dart'; +import '../../../../core/utils/device_info/device_info_data.dart'; +import '../../../../features/MainScreens/Settings/domain/repository/settings_api.dart'; +import '../../../../features/register/Repository/RegisterApi.dart'; import 'toggle_event.dart'; import 'toggle_state.dart'; @@ -16,11 +22,75 @@ class ToggleBloc extends Bloc { void _onToggleSwitch(ToggleSwitch event, Emitter emit) async { if (state is ToggleOn) { - emit(ToggleOff()); - await secureStorageService.write(type, 'off'); + if (type == "biometric") { + emit(ToggleOff()); + + Map biometricdata = { + "code": await secureStorageService.read("temp_token"), + "is_2FA_on": false, + "deviceId": await DeviceInfoData().getDeviceId(), + "biometric_type": Platform.isIOS ? "face" : "fingerprint" + }; + + ResponseData response = + await RegisterAPIService().BiometricUpdate(biometricdata); + if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write(type, 'off'); + await secureStorageService.write("biometric", 'of'); + } else { + emit(ToggleOn()); + await secureStorageService.write(type, 'on'); + } + } else if (type == "notification") { + emit(ToggleOff()); + + ResponseData response = await SettingsApi().updateNotification(); + if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write(type, 'off'); + await secureStorageService.write("notification", 'of'); + } else { + emit(ToggleOn()); + await secureStorageService.write(type, 'on'); + } + } else { + emit(ToggleOff()); + await secureStorageService.write(type, 'off'); + } } else { - emit(ToggleOn()); - await secureStorageService.write(type, 'on'); + if (type == "biometric") { + emit(ToggleOn()); + + Map biometricdata = { + "code": await secureStorageService.read("temp_token"), + "is_2FA_on": true, + "deviceId": await DeviceInfoData().getDeviceId(), + "biometric_type": Platform.isIOS ? "face" : "fingerprint" + }; + + ResponseData response = + await RegisterAPIService().BiometricUpdate(biometricdata); + if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write("biometric", 'on'); + await secureStorageService.write(type, 'on'); + } else { + emit(ToggleOff()); + await secureStorageService.write(type, 'off'); + } + } else if (type == "notification") { + emit(ToggleOn()); + + ResponseData response = await SettingsApi().updateNotification(); + if (response.status == ResponseStatus.SUCCESS) { + await secureStorageService.write(type, 'on'); + await secureStorageService.write("notification", 'on'); + } else { + emit(ToggleOff()); + await secureStorageService.write(type, 'off'); + } + } else { + emit(ToggleOn()); + await secureStorageService.write(type, 'on'); + } } } diff --git a/lib/shared/components/forgot_password_log_out_dialog.dart b/lib/shared/components/forgot_password_log_out_dialog.dart new file mode 100644 index 0000000..400beb9 --- /dev/null +++ b/lib/shared/components/forgot_password_log_out_dialog.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gap/gap.dart'; + +import '../../core/routes/route_name.dart'; +import '../../core/routes/routes.dart'; +import '../../core/styles/app_color.dart'; +import '../../core/styles/app_text.dart'; +import '../../core/utils/language/localizations_delegate.dart'; +import '../../core/utils/secure/secure_storage_service.dart'; +import 'bloc/bottom_nav_bar/bottom_navigation_bloc.dart'; +import 'bloc/bottom_nav_bar/bottom_navigation_event.dart'; +import 'text_widget.dart'; + +forgotPasswordlogoutdialog(context) { + var localizations = AppLocalizations.of(context); + SecureStorageService secureStorageService = SecureStorageService(); + return showDialog( + context: context, + builder: (context) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AlertDialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + backgroundColor: AppColor.plainWhite, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + side: BorderSide( + color: AppColor.plainWhite, + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextWidget().text17W700( + localizations.translate(AppText.notificationText), + clr: AppColor.plainBlack), + const Gap(25), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25, + ), + child: TextWidget().text15W500( + localizations + .translate(AppText.toRestorePasswordYouWillBeLoggedOut), + clr: AppColor.hintTextColor, + ), + ), + const Gap(30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + GestureDetector( + onTap: () { + goRouter.pop(); + }, + child: Container( + height: 48.h, + width: 140.w, + decoration: BoxDecoration( + border: Border.all( + color: AppColor.primaryColor2, + ), + borderRadius: BorderRadius.circular(10.h), + color: AppColor.plainWhite), + child: Center( + child: TextWidget().text18W700( + localizations.translate(AppText.declineText), + clr: AppColor.primaryColor2, + ), + ), + ), + ), + Gap(28.w), + GestureDetector( + onTap: () async { + context.read().add(TabChanged(2)); + await secureStorageService.write('isLoginedIn', "false"); + await secureStorageService.write('accesstoken', ''); + await secureStorageService.write('refreshtoken', ''); + await secureStorageService.write('temp_token', ''); + + goRouter.goNamed(RouteName.loginScreen, pathParameters: { + "fromScreen": "registerStep", + }); + }, + child: Container( + height: 48.h, + width: 140.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.h), + color: AppColor.primaryColor), + child: Center( + child: TextWidget().text18W700( + localizations.translate(AppText.allowText), + clr: AppColor.plainWhite, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); +} diff --git a/lib/shared/components/log_out_dialog.dart b/lib/shared/components/log_out_dialog.dart index 1ef1bc8..1502f9e 100644 --- a/lib/shared/components/log_out_dialog.dart +++ b/lib/shared/components/log_out_dialog.dart @@ -91,6 +91,7 @@ buildprofilelogoutdialog(context) { await secureStorageService.write('isLoginedIn', "false"); await secureStorageService.write('accesstoken', ''); await secureStorageService.write('refreshtoken', ''); + await secureStorageService.write('temp_token', ''); goRouter.goNamed(RouteName.loginScreen, pathParameters: { "fromScreen": "registerStep", diff --git a/lib/shared/components/permission_dialog.dart b/lib/shared/components/permission_dialog.dart index 3cce34d..57380c4 100644 --- a/lib/shared/components/permission_dialog.dart +++ b/lib/shared/components/permission_dialog.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:gap/gap.dart'; import 'package:tanami_app/Api_Helper/base_manager.dart'; import 'package:tanami_app/core/styles/app_color.dart'; +import 'package:tanami_app/core/utils/device_info/device_info_data.dart'; import 'package:tanami_app/shared/components/loader.dart'; import '../../core/routes/route_name.dart'; @@ -85,12 +88,30 @@ permissionDialog( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ GestureDetector( - onTap: () { - goRouter.pop(); + onTap: () async { + Loader.loader(context); + Map biometricdata = { + "code": await secureStorageService.read("temp_token"), + "is_2FA_on": false, + "deviceId": await DeviceInfoData().getDeviceId(), + "biometric_type": + Platform.isIOS ? "face" : "fingerprint" + }; - goRouter.goNamed(RouteName.pinScreen, pathParameters: { - "fromScreen": "register", - }); + ResponseData response = await RegisterAPIService() + .BiometricUpdate(biometricdata); + if (response.status == ResponseStatus.SUCCESS) { + goRouter.pop(); + + await secureStorageService.write("biometric", 'of'); + + goRouter.pop(); + + goRouter + .goNamed(RouteName.pinScreen, pathParameters: { + "fromScreen": "register", + }); + } }, child: SizedBox( height: 48.h, @@ -112,9 +133,11 @@ permissionDialog( onTap: () async { Loader.loader(context); Map biometricdata = { - "token": - await secureStorageService.read("temp_token"), - "is_2FA_on": true + "code": await secureStorageService.read("temp_token"), + "is_2FA_on": true, + "deviceId": await DeviceInfoData().getDeviceId(), + "biometric_type": + Platform.isIOS ? "face" : "fingerprint" }; ResponseData response = await RegisterAPIService() @@ -125,7 +148,7 @@ permissionDialog( await secureStorageService.write("biometric", 'on'); successToastMessage(context, "Biometric/Face Id Enabled Sucessfully !"); - // successToastMessage(context, "successful !"); + goRouter.pop(); goRouter diff --git a/lib/shared/components/read_pdf.dart b/lib/shared/components/read_pdf.dart new file mode 100644 index 0000000..b9c79b1 --- /dev/null +++ b/lib/shared/components/read_pdf.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; + +import '../../core/routes/routes.dart'; +import '../../core/styles/app_color.dart'; +import 'text_widget.dart'; + +class ReadPDF extends StatelessWidget { + const ReadPDF({ + super.key, + required this.title, + required this.pdfUrl, + }); + final String title; + final String pdfUrl; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColor.plainWhite, + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: AppColor.plainWhite, + elevation: 0, + centerTitle: true, + title: TextWidget().text20W700(title, clr: AppColor.charcoalColor), + leading: Padding( + padding: EdgeInsets.only( + left: 16.w, + ), + child: GestureDetector( + onTap: () { + goRouter.pop(); + }, + child: Padding( + padding: EdgeInsets.only(left: 8.w), + child: Icon( + Icons.arrow_back_rounded, + color: AppColor.appBarIconColor, + size: 25.r, + ), + ), + ), + ), + ), + body: SfPdfViewer.network( + pdfUrl, + ), + ); + } +} diff --git a/lib/shared/components/viemo_screen.dart b/lib/shared/components/viemo_screen.dart new file mode 100644 index 0000000..3ee2360 --- /dev/null +++ b/lib/shared/components/viemo_screen.dart @@ -0,0 +1,89 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:tanami_app/core/routes/routes.dart'; +import 'package:video_player/video_player.dart'; + +class VimeoScreen extends StatefulWidget { + final String videoUrl; + const VimeoScreen({ + super.key, + required this.videoUrl, + }); + + @override + State createState() => _VimeoScreenState(); +} + +class _VimeoScreenState extends State { + late VideoPlayerController videoPlayerController; + late ChewieController chewieController; + var videoUrl1 = + "https://player.vimeo.com/progressive_redirect/playback/848263896/rendition/1080p/file.mp4?loc=external&signature=440740807e0632840f08b973014d831dbb8d17516532a30d24bf10b44b9282cd"; + @override + void initState() { + _initializePlayer(); + super.initState(); + } + + _initializePlayer() async { + try { + videoPlayerController = VideoPlayerController.networkUrl( + Uri.parse(videoUrl1), + ); + + await videoPlayerController.initialize(); + initChewie(); + setState(() {}); + } catch (e) { + print("Error initializing video player: $e"); + } + } + + initChewie() { + chewieController = ChewieController( + videoPlayerController: videoPlayerController, + autoPlay: true, + looping: false); + } + + @override + void dispose() { + videoPlayerController.dispose(); + chewieController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return videoPlayerController.value.isInitialized + ? Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: Colors.transparent, + leading: InkWell( + onTap: () => goRouter.pop(), + child: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + ), + ), + // CommonAppbar(titleTxt: ""), + body: Center( + child: AspectRatio( + aspectRatio: videoPlayerController.value.aspectRatio, + child: Chewie( + controller: chewieController, + ), + ), + ), + ) + : const Center( + child: + //ShimmerCommon(), + CircularProgressIndicator( + color: Color(0xFF0093FF), + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 07d7d5a..f1d8244 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,7 +14,10 @@ import package_info_plus import path_provider_foundation import shared_preferences_foundation import sqflite +import syncfusion_pdfviewer_macos import url_launcher_macos +import video_player_avfoundation +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) @@ -26,5 +29,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 2e6e9cf..2258d7c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + chewie: + dependency: "direct main" + description: + name: chewie + sha256: e53da939709efb9aad0f3d72a69a8d05f889168b7a138af60ce78bab5c94b135 + url: "https://pub.dev" + source: hosted + version: "1.8.1" cli_util: dependency: transitive description: @@ -1226,6 +1234,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: "34920fc391f9562d68657b9b25be6e873a67e069a52bcf41ae583b7eb46dcb26" + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_flutter_pdf: + dependency: transitive + description: + name: syncfusion_flutter_pdf + sha256: d04d5cf7c989245b2f7c9eebe0fd6dad94188cf1ef803cdfc7e2b1cf5fe5759d + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_flutter_pdfviewer: + dependency: "direct main" + description: + name: syncfusion_flutter_pdfviewer + sha256: "86daa9bba31a9cb63f703aef7b6860737abf451810707985211a2260bae37d2f" + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_flutter_signaturepad: + dependency: transitive + description: + name: syncfusion_flutter_signaturepad + sha256: e4b1f8043ecba0e70d852f8bb3b50760d5779b25a2632eaf4dfaa3cc53a06515 + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_pdfviewer_macos: + dependency: transitive + description: + name: syncfusion_pdfviewer_macos + sha256: "2f0bc14c5052a82757736edf016d09724b50c99d418490de82c91541b3ae379e" + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_pdfviewer_platform_interface: + dependency: transitive + description: + name: syncfusion_pdfviewer_platform_interface + sha256: d004e7fdf0479b2ffaa7c2ce85646e9bbcd795eb6286c0f6ef5df14b4bb33707 + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_pdfviewer_web: + dependency: transitive + description: + name: syncfusion_pdfviewer_web + sha256: "17dc227bc3bae33babaa14eb4988ad712f4ac92733720a3049f3975c40e52feb" + url: "https://pub.dev" + source: hosted + version: "26.2.4" + syncfusion_pdfviewer_windows: + dependency: transitive + description: + name: syncfusion_pdfviewer_windows + sha256: "9eb6f394cd2545c0dd9ffc253680ea5d323afe31fad42fb4c00898b647220f42" + url: "https://pub.dev" + source: hosted + version: "26.2.4" synchronized: dependency: transitive description: @@ -1402,6 +1474,46 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + url: "https://pub.dev" + source: hosted + version: "2.9.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: fdc0331ce9f808cc2714014cb8126bd6369943affefd54f8fdab0ea0bb617b7f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c + url: "https://pub.dev" + source: hosted + version: "2.6.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" vm_service: dependency: transitive description: @@ -1410,6 +1522,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.1" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "14758533319a462ffb5aa3b7ddb198e59b29ac3b02da14173a1715d65d4e6e68" + url: "https://pub.dev" + source: hosted + version: "1.2.5" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16" + url: "https://pub.dev" + source: hosted + version: "1.2.1" watcher: dependency: transitive description: @@ -1475,5 +1603,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.4 <4.0.0" - flutter: ">=3.19.2" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index d5424b1..cb9318a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,12 +91,19 @@ dependencies: # #Device Preview # device_preview: + # Video Player + chewie: ^1.5.0 + video_player: ^2.5.2 + #Performance statsfl: ^2.3.0 #Localization flutter_localization: ^0.2.0 + #Pdf + syncfusion_flutter_pdfviewer: + dev_dependencies: flutter_test: sdk: flutter diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c7d278d..4f21a96 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -30,6 +31,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SecureApplicationPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SecureApplicationPlugin")); + SyncfusionPdfviewerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SyncfusionPdfviewerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f78c9b9..66abbf9 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST local_auth_windows permission_handler_windows secure_application + syncfusion_pdfviewer_windows url_launcher_windows )