Repository: Odoo-mobile/crm Branch: master Commit: b25516b8f745 Files: 345 Total size: 1.3 MB Directory structure: gitextract_1ybjx6vk/ ├── .classpath ├── README.md ├── app/ │ ├── .gitignore │ ├── app-release.apk │ ├── build.gradle │ ├── crm-app-release.apk │ ├── libs/ │ │ └── swipe_library.jar │ ├── manifest-merger-release-report.txt │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ ├── com/ │ │ │ └── odoo/ │ │ │ ├── App.java │ │ │ ├── OdooActivity.java │ │ │ ├── SettingsActivity.java │ │ │ ├── addons/ │ │ │ │ ├── calendar/ │ │ │ │ │ ├── CalendarDashboard.java │ │ │ │ │ ├── EventDetail.java │ │ │ │ │ ├── models/ │ │ │ │ │ │ └── CalendarEvent.java │ │ │ │ │ ├── providers/ │ │ │ │ │ │ └── CalendarSyncProvider.java │ │ │ │ │ ├── services/ │ │ │ │ │ │ └── CalendarSyncService.java │ │ │ │ │ └── utils/ │ │ │ │ │ ├── CalendarUtils.java │ │ │ │ │ ├── EventColorDialog.java │ │ │ │ │ ├── ReminderDialog.java │ │ │ │ │ └── TodayIcon.java │ │ │ │ ├── crm/ │ │ │ │ │ ├── CRMDetail.java │ │ │ │ │ ├── CRMLeads.java │ │ │ │ │ ├── CRMOpportunities.java │ │ │ │ │ ├── CRMOpportunitiesPager.java │ │ │ │ │ ├── ConvertToOpportunityWizard.java │ │ │ │ │ ├── ConvertToQuotation.java │ │ │ │ │ ├── models/ │ │ │ │ │ │ ├── CRMCaseCateg.java │ │ │ │ │ │ ├── CRMCaseStage.java │ │ │ │ │ │ └── CRMLead.java │ │ │ │ │ ├── providers/ │ │ │ │ │ │ └── CRMLeadProvider.java │ │ │ │ │ └── services/ │ │ │ │ │ └── CRMLeadSyncService.java │ │ │ │ ├── customers/ │ │ │ │ │ ├── CustomerDetails.java │ │ │ │ │ ├── Customers.java │ │ │ │ │ ├── providers/ │ │ │ │ │ │ └── CustomersSyncProvider.java │ │ │ │ │ ├── services/ │ │ │ │ │ │ └── CustomerSyncService.java │ │ │ │ │ └── utils/ │ │ │ │ │ └── ShareUtil.java │ │ │ │ ├── phonecall/ │ │ │ │ │ ├── PhoneCallDetail.java │ │ │ │ │ ├── PhoneCalls.java │ │ │ │ │ ├── features/ │ │ │ │ │ │ ├── CallerWindow.java │ │ │ │ │ │ ├── CustomerFinder.java │ │ │ │ │ │ ├── IOnCustomerFindListener.java │ │ │ │ │ │ └── receivers/ │ │ │ │ │ │ └── PhoneStateReceiver.java │ │ │ │ │ ├── models/ │ │ │ │ │ │ ├── CRMPhoneCalls.java │ │ │ │ │ │ └── CRMPhoneCallsCategory.java │ │ │ │ │ ├── providers/ │ │ │ │ │ │ └── PhoneCallProvider.java │ │ │ │ │ └── services/ │ │ │ │ │ └── PhoneCallSyncService.java │ │ │ │ └── sale/ │ │ │ │ ├── AddProductLineWizard.java │ │ │ │ ├── Sales.java │ │ │ │ ├── SalesDetail.java │ │ │ │ ├── models/ │ │ │ │ │ ├── AccountPaymentTerm.java │ │ │ │ │ ├── ProductProduct.java │ │ │ │ │ ├── SaleOrder.java │ │ │ │ │ └── SalesOrderLine.java │ │ │ │ ├── providers/ │ │ │ │ │ └── SaleOrderProvider.java │ │ │ │ └── services/ │ │ │ │ └── SaleOrderSyncService.java │ │ │ ├── base/ │ │ │ │ └── addons/ │ │ │ │ ├── BaseModels.java │ │ │ │ ├── ir/ │ │ │ │ │ ├── IrAttachment.java │ │ │ │ │ ├── IrModel.java │ │ │ │ │ ├── feature/ │ │ │ │ │ │ └── OFileManager.java │ │ │ │ │ └── providers/ │ │ │ │ │ └── IrModelProvider.java │ │ │ │ ├── mail/ │ │ │ │ │ ├── MailMessage.java │ │ │ │ │ ├── MailMessageSubType.java │ │ │ │ │ └── widget/ │ │ │ │ │ ├── MailChatterCompose.java │ │ │ │ │ ├── MailChatterView.java │ │ │ │ │ ├── MailDetailDialog.java │ │ │ │ │ └── MessageObserver.java │ │ │ │ └── res/ │ │ │ │ ├── ResCompany.java │ │ │ │ ├── ResCountry.java │ │ │ │ ├── ResCurrency.java │ │ │ │ ├── ResPartner.java │ │ │ │ └── ResUsers.java │ │ │ ├── config/ │ │ │ │ ├── Addons.java │ │ │ │ ├── BaseConfig.java │ │ │ │ └── IntroSliderItems.java │ │ │ ├── core/ │ │ │ │ ├── account/ │ │ │ │ │ ├── About.java │ │ │ │ │ ├── AppIntro.java │ │ │ │ │ ├── BaseSettings.java │ │ │ │ │ ├── ManageAccounts.java │ │ │ │ │ ├── OdooAccountQuickManage.java │ │ │ │ │ ├── OdooLogin.java │ │ │ │ │ ├── OdooUserAskPassword.java │ │ │ │ │ └── Profile.java │ │ │ │ ├── auth/ │ │ │ │ │ ├── OdooAccountManager.java │ │ │ │ │ ├── OdooAuthService.java │ │ │ │ │ └── OdooAuthenticator.java │ │ │ │ ├── orm/ │ │ │ │ │ ├── ODataRow.java │ │ │ │ │ ├── OM2MRecord.java │ │ │ │ │ ├── OM2ORecord.java │ │ │ │ │ ├── OModel.java │ │ │ │ │ ├── OModelRegistry.java │ │ │ │ │ ├── OO2MRecord.java │ │ │ │ │ ├── OSQLHelper.java │ │ │ │ │ ├── OSQLite.java │ │ │ │ │ ├── OValues.java │ │ │ │ │ ├── ServerDataHelper.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ └── Odoo.java │ │ │ │ │ ├── fields/ │ │ │ │ │ │ ├── OColumn.java │ │ │ │ │ │ └── types/ │ │ │ │ │ │ ├── OBlob.java │ │ │ │ │ │ ├── OBoolean.java │ │ │ │ │ │ ├── ODate.java │ │ │ │ │ │ ├── ODateTime.java │ │ │ │ │ │ ├── OFloat.java │ │ │ │ │ │ ├── OHtml.java │ │ │ │ │ │ ├── OInteger.java │ │ │ │ │ │ ├── OSelection.java │ │ │ │ │ │ ├── OText.java │ │ │ │ │ │ ├── OTimestamp.java │ │ │ │ │ │ ├── OTypeHelper.java │ │ │ │ │ │ └── OVarchar.java │ │ │ │ │ └── provider/ │ │ │ │ │ └── BaseModelProvider.java │ │ │ │ ├── service/ │ │ │ │ │ ├── ISyncFinishListener.java │ │ │ │ │ ├── ISyncServiceListener.java │ │ │ │ │ ├── OSyncAdapter.java │ │ │ │ │ ├── OSyncDataUtils.java │ │ │ │ │ ├── OSyncService.java │ │ │ │ │ └── receivers/ │ │ │ │ │ └── ISyncFinishReceiver.java │ │ │ │ ├── support/ │ │ │ │ │ ├── OUser.java │ │ │ │ │ ├── OdooFields.java │ │ │ │ │ ├── OdooInstancesSelectorDialog.java │ │ │ │ │ ├── OdooLoginHelper.java │ │ │ │ │ ├── OdooServerTester.java │ │ │ │ │ ├── OdooUserLoginSelectorDialog.java │ │ │ │ │ ├── addons/ │ │ │ │ │ │ ├── AddonsHelper.java │ │ │ │ │ │ ├── OAddon.java │ │ │ │ │ │ └── fragment/ │ │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ │ ├── IBaseFragment.java │ │ │ │ │ │ ├── IOnSearchViewChangeListener.java │ │ │ │ │ │ └── ISyncStatusObserverListener.java │ │ │ │ │ ├── drawer/ │ │ │ │ │ │ └── ODrawerItem.java │ │ │ │ │ ├── list/ │ │ │ │ │ │ ├── IOnItemClickListener.java │ │ │ │ │ │ ├── OCursorListAdapter.java │ │ │ │ │ │ └── OListAdapter.java │ │ │ │ │ └── sync/ │ │ │ │ │ └── SyncUtils.java │ │ │ │ └── utils/ │ │ │ │ ├── BitmapUtils.java │ │ │ │ ├── IntentUtils.java │ │ │ │ ├── JSONUtils.java │ │ │ │ ├── OActionBarUtils.java │ │ │ │ ├── OAlert.java │ │ │ │ ├── OAlertDialog.java │ │ │ │ ├── OControls.java │ │ │ │ ├── OCursorUtils.java │ │ │ │ ├── ODateUtils.java │ │ │ │ ├── OFragmentUtils.java │ │ │ │ ├── OListUtils.java │ │ │ │ ├── OPreferenceManager.java │ │ │ │ ├── OResource.java │ │ │ │ ├── OStorageUtils.java │ │ │ │ ├── OStringColorUtil.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── controls/ │ │ │ │ │ └── ExpandableHeightGridView.java │ │ │ │ ├── dialog/ │ │ │ │ │ └── OChoiceDialog.java │ │ │ │ ├── drawer/ │ │ │ │ │ ├── DrawerUtils.java │ │ │ │ │ └── ODrawerScrollView.java │ │ │ │ ├── logger/ │ │ │ │ │ └── OLog.java │ │ │ │ ├── notification/ │ │ │ │ │ └── ONotificationBuilder.java │ │ │ │ ├── reminder/ │ │ │ │ │ ├── ReminderActionReceiver.java │ │ │ │ │ ├── ReminderReceiver.java │ │ │ │ │ └── ReminderUtils.java │ │ │ │ └── sys/ │ │ │ │ ├── IOnActivityResultListener.java │ │ │ │ ├── IOnBackPressListener.java │ │ │ │ └── OCacheUtils.java │ │ │ ├── datas/ │ │ │ │ └── OConstants.java │ │ │ ├── news/ │ │ │ │ ├── News.java │ │ │ │ ├── NewsDetail.java │ │ │ │ ├── OdooNewsReceiver.java │ │ │ │ └── models/ │ │ │ │ └── OdooNews.java │ │ │ └── server/ │ │ │ └── notifications/ │ │ │ └── OdooServerNotificationReceiver.java │ │ └── odoo/ │ │ └── controls/ │ │ ├── BezelImageView.java │ │ ├── DateTimePicker.java │ │ ├── ExpandableListControl.java │ │ ├── ExpandableListOperationListener.java │ │ ├── IOControlData.java │ │ ├── IOnChangeCallback.java │ │ ├── IOnDomainFilterCallbacks.java │ │ ├── IOnQuickRecordCreateListener.java │ │ ├── OBlobField.java │ │ ├── OBooleanField.java │ │ ├── OControlHelper.java │ │ ├── ODateTimeField.java │ │ ├── OEditTextField.java │ │ ├── OField.java │ │ ├── OForm.java │ │ ├── OSelectionField.java │ │ ├── SearchableItemActivity.java │ │ └── fab/ │ │ ├── DirectionScrollListener.java │ │ └── FloatingActionButton.java │ └── res/ │ ├── drawable/ │ │ ├── circle_mask.xml │ │ ├── circle_mask_gray.xml │ │ ├── circle_mask_primary.xml │ │ ├── circle_mask_secondary.xml │ │ ├── drawer_background_cover.xml │ │ ├── icon_bg_oval_blue.xml │ │ ├── icon_bg_oval_green.xml │ │ ├── icon_bg_oval_orange.xml │ │ ├── icon_bg_oval_red.xml │ │ ├── icon_bg_oval_violet.xml │ │ ├── login_signup_button.xml │ │ ├── login_signup_button_clicked.xml │ │ ├── login_signup_button_normal.xml │ │ ├── login_signup_control_bg.xml │ │ └── tag_background.xml │ ├── layout/ │ │ ├── activity_app_intro.xml │ │ ├── base_about.xml │ │ ├── base_account_ask_pass.xml │ │ ├── base_account_item.xml │ │ ├── base_account_quick_manage.xml │ │ ├── base_attachment_item.xml │ │ ├── base_control_searchable_layout.xml │ │ ├── base_control_template.xml │ │ ├── base_drawer.xml │ │ ├── base_drawer_account_item.xml │ │ ├── base_drawer_group_layout.xml │ │ ├── base_drawer_menu_item.xml │ │ ├── base_instance_item.xml │ │ ├── base_intro_slider_view.xml │ │ ├── base_login.xml │ │ ├── base_mail_chatter.xml │ │ ├── base_mail_chatter_item.xml │ │ ├── base_mail_chatter_message_compose.xml │ │ ├── base_mail_chatter_message_detail.xml │ │ ├── base_manage_accounts.xml │ │ ├── base_no_items_view.xml │ │ ├── base_no_items_view.xml~ │ │ ├── base_profile.xml │ │ ├── base_setting_activity.xml │ │ ├── base_simple_list_item_1.xml │ │ ├── base_simple_list_item_1_selected.xml │ │ ├── base_toolbar.xml │ │ ├── calendar_dashboard.xml │ │ ├── calendar_dashboard_item_separator.xml │ │ ├── calendar_dashboard_item_view.xml │ │ ├── calendar_dashboard_items.xml │ │ ├── calendar_event_detail_form.xml │ │ ├── common_listview.xml │ │ ├── crm_caller_window_layout.xml │ │ ├── crm_convert_to_opportunity.xml │ │ ├── crm_convert_to_opportunity_item.xml │ │ ├── crm_convert_to_quotation.xml │ │ ├── crm_detail.xml │ │ ├── crm_item.xml │ │ ├── crm_opportunity_pagger.xml │ │ ├── crm_phonecall_detail.xml │ │ ├── customer_detail.xml │ │ ├── customer_filter_container.xml │ │ ├── customer_row_item.xml │ │ ├── event_color_chooser_item.xml │ │ ├── event_color_grid.xml │ │ ├── listview_data_loading_progress.xml │ │ ├── news_detail.xml │ │ ├── news_list.xml │ │ ├── odoo_activity.xml │ │ ├── odoo_news.xml │ │ ├── phonecall_item.xml │ │ ├── reminder_custom_dialog_view.xml │ │ ├── reminder_item_view.xml │ │ ├── sale_add_item.xml │ │ ├── sale_detail.xml │ │ ├── sale_order_item.xml │ │ ├── sale_order_line_item.xml │ │ └── sale_product_line_item.xml │ ├── menu/ │ │ ├── menu_about.xml │ │ ├── menu_base_login.xml │ │ ├── menu_calendar_dashboard.xml │ │ ├── menu_calendar_detail.xml │ │ ├── menu_customer_detail.xml │ │ ├── menu_dashboard_events.xml │ │ ├── menu_dashboard_opportunity.xml │ │ ├── menu_dashboard_phonecalls.xml │ │ ├── menu_lead_detail.xml │ │ ├── menu_lead_list_sheet.xml │ │ ├── menu_leads.xml │ │ ├── menu_opp_list_sheet.xml │ │ ├── menu_partners.xml │ │ ├── menu_phonecall_detail.xml │ │ ├── menu_phonecalls.xml │ │ ├── menu_quotation_cancel_sheet.xml │ │ ├── menu_quotation_sheet.xml │ │ ├── menu_sale_add_item.xml │ │ ├── menu_sale_detail.xml │ │ ├── menu_sales_order.xml │ │ ├── menu_sheet_customer.xml │ │ └── menu_so_sheet.xml │ ├── values/ │ │ ├── attrs.xml │ │ ├── base-strings.xml │ │ ├── bool.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── fonts.xml │ │ ├── strings.xml │ │ ├── strings.xml~ │ │ ├── styles.xml │ │ └── theme-values.xml │ ├── values-v19/ │ │ └── bool.xml │ ├── values-v21/ │ │ ├── colors.xml │ │ ├── styles.xml │ │ └── theme-values.xml │ ├── values-w820dp/ │ │ └── dimens.xml │ └── xml/ │ ├── authenticator.xml │ ├── base_preference.xml │ ├── calendar_sync_adapter.xml │ ├── crm_sync_adapter.xml │ ├── customer_sync_adapter.xml │ ├── phonecall_sync_adapter.xml │ └── sale_sync_adapter.xml ├── bottom-sheet-lib/ │ ├── bottom-sheet-lib.aar │ ├── bottom-sheet-lib.iml │ └── build.gradle ├── build.gradle ├── calendar-lib/ │ ├── build.gradle │ ├── calendar-lib.aar │ └── calendar-lib.iml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── intro-slider-lib/ │ ├── app.iml │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── odoo/ │ │ ├── widget/ │ │ │ └── slider/ │ │ │ └── navigator/ │ │ │ └── PagerNavigatorAdapter.java │ │ └── widgets/ │ │ └── slider/ │ │ ├── SliderHelper.java │ │ ├── SliderItem.java │ │ ├── SliderPagerAdapter.java │ │ └── SliderView.java │ └── res/ │ ├── drawable/ │ │ └── intro_slider_dot_bg.xml │ ├── layout/ │ │ ├── default_ui.xml │ │ └── slider_default_view.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-v11/ │ │ └── styles.xml │ └── values-v14/ │ └── styles.xml ├── local.properties ├── master-crm-studio-dpr.iml ├── odoo-rpc-v2/ │ ├── build.gradle │ └── odoo-rpc-v2.aar ├── parallax-effect-lib/ │ ├── build.gradle │ └── parallax-effect-lib.aar ├── settings.gradle └── snackbar-lib/ ├── build.gradle ├── snackbar-lib.aar └── snackbar-lib.iml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .classpath ================================================ ================================================ FILE: README.md ================================================ Odoo Mobile Framework v2.0 ========================== Android Studio Project Odoo Instace Odoo is a powerful open source framework. With help of this framework we can rapidly develop almost any application. World is contracting with the growth of mobile phone technology. As the number of users is increasing day by day, facilities are also increasing. Now a days mobiles are not used just for making calls but they have innumerable uses and can be used as a Camera , Music player, Tablet PC, T.V. , Web browser etc. And with the new technologies, new software and operating systems are required. One of the most widely used mobile OS these days is ANDROID. Android is a software bunch comprising not only operating system but also middleware and key applications. Odoo Mobile framework is an open source mobile application development framework with Odoo integration. With the help of mobile framework we can rapidly develop almost all Odoo supported application as faster as we can develop in Odoo Framework. This framework contains its own ORM to handle mobile’s local database. So you do not have to worry about data comming from Odoo Server. It has pre-developed services and providers to make your application data synchronized with Odoo. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { signingConfigs { } compileSdkVersion 22 buildToolsVersion "21.1.2" defaultConfig { applicationId "com.odoo.crm" minSdkVersion 14 targetSdkVersion 22 versionCode 7 versionName "1.0.7" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile files('libs/swipe_library.jar') compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:cardview-v7:22.0.0' compile 'com.google.android.gms:play-services:7.5.0' compile project(':intro-slider-lib') compile project(':parallax-effect-lib') compile project(':odoo-rpc-v2') compile project(':calendar-lib') compile project(':bottom-sheet-lib') compile project(':snackbar-lib') } ================================================ FILE: app/manifest-merger-release-report.txt ================================================ -- Merging decision tree log --- manifest ADDED from AndroidManifest.xml:2:1 xmlns:android ADDED from AndroidManifest.xml:2:11 package ADDED from AndroidManifest.xml:3:5 INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 android:versionName INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 android:versionCode INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 uses-permission#android.permission.INTERNET ADDED from AndroidManifest.xml:5:5 MERGED from com.google.android.gms:play-services-ads:7.5.0:20:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:21:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:21:5 MERGED from com.google.android.gms:play-services-appinvite:7.5.0:19:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5 MERGED from com.google.android.gms:play-services-wallet:7.5.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:21:5 MERGED from master-crm:odoo-rpc-v2:unspecified:11:5 android:name ADDED from AndroidManifest.xml:5:22 uses-permission#android.permission.VIBRATE ADDED from AndroidManifest.xml:6:5 MERGED from master-crm:odoo-rpc-v2:unspecified:14:5 android:name ADDED from AndroidManifest.xml:6:22 uses-permission#android.permission.ACCESS_WIFI_STATE ADDED from AndroidManifest.xml:7:5 MERGED from master-crm:odoo-rpc-v2:unspecified:15:5 android:name ADDED from AndroidManifest.xml:7:22 uses-permission#android.permission.ACCESS_NETWORK_STATE ADDED from AndroidManifest.xml:8:5 MERGED from com.google.android.gms:play-services-ads:7.5.0:21:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:22:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5 MERGED from com.google.android.gms:play-services-nearby:7.5.0:19:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:20:5 MERGED from master-crm:odoo-rpc-v2:unspecified:16:5 android:name ADDED from AndroidManifest.xml:8:22 uses-permission#android.permission.GET_TASKS ADDED from AndroidManifest.xml:9:5 android:name ADDED from AndroidManifest.xml:9:22 uses-permission#android.permission.AUTHENTICATE_ACCOUNTS ADDED from AndroidManifest.xml:10:5 android:name ADDED from AndroidManifest.xml:10:22 uses-permission#android.permission.GET_ACCOUNTS ADDED from AndroidManifest.xml:11:5 MERGED from com.google.android.gms:play-services-wallet:7.5.0:21:5 android:name ADDED from AndroidManifest.xml:11:22 uses-permission#android.permission.USE_CREDENTIALS ADDED from AndroidManifest.xml:12:5 MERGED from com.google.android.gms:play-services-wallet:7.5.0:22:5 android:name ADDED from AndroidManifest.xml:12:22 uses-permission#android.permission.MANAGE_ACCOUNTS ADDED from AndroidManifest.xml:13:5 android:name ADDED from AndroidManifest.xml:13:22 uses-permission#android.permission.READ_SYNC_SETTINGS ADDED from AndroidManifest.xml:14:5 android:name ADDED from AndroidManifest.xml:14:22 uses-permission#android.permission.READ_SYNC_STATS ADDED from AndroidManifest.xml:15:5 android:name ADDED from AndroidManifest.xml:15:22 uses-permission#android.permission.WRITE_SYNC_SETTINGS ADDED from AndroidManifest.xml:16:5 android:name ADDED from AndroidManifest.xml:16:22 uses-permission#android.permission.WAKE_LOCK ADDED from AndroidManifest.xml:17:5 MERGED from master-crm:odoo-rpc-v2:unspecified:17:5 android:name ADDED from AndroidManifest.xml:17:22 uses-permission#com.google.android.c2dm.permission.RECEIVE ADDED from AndroidManifest.xml:18:5 MERGED from master-crm:odoo-rpc-v2:unspecified:13:5 android:name ADDED from AndroidManifest.xml:18:22 uses-permission#android.permission.READ_PHONE_STATE ADDED from AndroidManifest.xml:19:5 MERGED from master-crm:odoo-rpc-v2:unspecified:12:5 android:name ADDED from AndroidManifest.xml:19:22 uses-permission#android.permission.SYSTEM_ALERT_WINDOW ADDED from AndroidManifest.xml:20:5 android:name ADDED from AndroidManifest.xml:20:22 uses-permission#android.permission.CALL_PHONE ADDED from AndroidManifest.xml:21:5 android:name ADDED from AndroidManifest.xml:21:22 uses-permission#android.permission.READ_EXTERNAL_STORAGE ADDED from AndroidManifest.xml:22:5 android:name ADDED from AndroidManifest.xml:22:22 uses-permission#android.permission.WRITE_EXTERNAL_STORAGE ADDED from AndroidManifest.xml:23:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:22:5 android:name ADDED from AndroidManifest.xml:23:22 uses-permission#android.permission.MANAGE_DOCUMENTS ADDED from AndroidManifest.xml:24:5 android:name ADDED from AndroidManifest.xml:24:22 permission#com.odoo.crm.permission.C2D_MESSAGE ADDED from AndroidManifest.xml:26:5 android:protectionLevel ADDED from AndroidManifest.xml:28:9 android:name ADDED from AndroidManifest.xml:27:9 application ADDED from AndroidManifest.xml:30:5 MERGED from com.android.support:appcompat-v7:22.2.0:22:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.android.support:cardview-v7:22.0.0:22:5 MERGED from com.google.android.gms:play-services:7.5.0:19:5 MERGED from com.google.android.gms:play-services-ads:7.5.0:24:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:24:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:24:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-appindexing:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-appinvite:7.5.0:20:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-appstate:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-cast:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.android.support:mediarouter-v7:22.0.0:22:5 MERGED from com.android.support:appcompat-v7:22.2.0:22:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-drive:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-fitness:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-location:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:29:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-games:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-drive:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-gcm:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-identity:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-location:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:29:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:29:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-nearby:7.5.0:20:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-panorama:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-plus:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-safetynet:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-wallet:7.5.0:24:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-identity:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:29:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from com.google.android.gms:play-services-wearable:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:20:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from master-crm:intro-slider-lib:unspecified:11:5 MERGED from com.android.support:support-v4:22.2.0:22:5 MERGED from master-crm:parallax-effect-lib:unspecified:11:5 MERGED from master-crm:odoo-rpc-v2:unspecified:20:5 MERGED from master-crm:calendar-lib:unspecified:11:5 MERGED from master-crm:bottom-sheet-lib:unspecified:11:5 MERGED from master-crm:snackbar-lib:unspecified:11:5 android:label ADDED from AndroidManifest.xml:35:9 android:allowBackup ADDED from AndroidManifest.xml:32:9 android:icon ADDED from AndroidManifest.xml:34:9 android:theme ADDED from AndroidManifest.xml:37:9 android:hardwareAccelerated ADDED from AndroidManifest.xml:33:9 android:largeHeap ADDED from AndroidManifest.xml:36:9 android:name ADDED from AndroidManifest.xml:31:9 activity#com.odoo.core.account.OdooLogin ADDED from AndroidManifest.xml:38:9 android:screenOrientation ADDED from AndroidManifest.xml:40:13 android:name ADDED from AndroidManifest.xml:39:13 intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER ADDED from AndroidManifest.xml:41:13 action#android.intent.action.MAIN ADDED from AndroidManifest.xml:42:17 android:name ADDED from AndroidManifest.xml:42:25 category#android.intent.category.LAUNCHER ADDED from AndroidManifest.xml:44:17 android:name ADDED from AndroidManifest.xml:44:27 activity#com.odoo.core.account.ManageAccounts ADDED from AndroidManifest.xml:47:9 android:name ADDED from AndroidManifest.xml:47:19 activity#com.odoo.OdooActivity ADDED from AndroidManifest.xml:48:9 android:name ADDED from AndroidManifest.xml:48:19 activity#odoo.controls.SearchableItemActivity ADDED from AndroidManifest.xml:49:9 android:name ADDED from AndroidManifest.xml:49:19 activity#com.odoo.SettingsActivity ADDED from AndroidManifest.xml:50:9 android:name ADDED from AndroidManifest.xml:50:19 activity#com.odoo.core.account.AppIntro ADDED from AndroidManifest.xml:51:9 android:screenOrientation ADDED from AndroidManifest.xml:53:13 android:name ADDED from AndroidManifest.xml:52:13 activity#com.odoo.base.addons.mail.widget.MailDetailDialog ADDED from AndroidManifest.xml:54:9 android:theme ADDED from AndroidManifest.xml:56:13 android:name ADDED from AndroidManifest.xml:55:13 activity#com.odoo.base.addons.mail.widget.MailChatterCompose ADDED from AndroidManifest.xml:57:9 android:theme ADDED from AndroidManifest.xml:59:13 android:name ADDED from AndroidManifest.xml:58:13 activity#com.odoo.core.account.OdooAccountQuickManage ADDED from AndroidManifest.xml:60:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:61:13 android:theme ADDED from AndroidManifest.xml:63:13 android:name ADDED from AndroidManifest.xml:62:13 activity#com.odoo.core.account.About ADDED from AndroidManifest.xml:64:9 android:name ADDED from AndroidManifest.xml:64:19 activity#com.odoo.core.account.Profile ADDED from AndroidManifest.xml:65:9 android:name ADDED from AndroidManifest.xml:65:19 receiver#com.odoo.core.utils.reminder.ReminderReceiver ADDED from AndroidManifest.xml:68:9 android:name ADDED from AndroidManifest.xml:68:19 receiver#com.odoo.core.utils.reminder.ReminderActionReceiver ADDED from AndroidManifest.xml:69:9 android:name ADDED from AndroidManifest.xml:69:19 receiver#com.odoo.news.OdooNewsReceiver ADDED from AndroidManifest.xml:70:9 android:name ADDED from AndroidManifest.xml:70:19 intent-filter#odoo.Odoo.ACTION_ODOO_UPDATES ADDED from AndroidManifest.xml:71:13 action#odoo.Odoo.ACTION_ODOO_UPDATES ADDED from AndroidManifest.xml:72:17 android:name ADDED from AndroidManifest.xml:72:25 receiver#com.odoo.server.notifications.OdooServerNotificationReceiver ADDED from AndroidManifest.xml:76:9 android:name ADDED from AndroidManifest.xml:76:19 intent-filter#com.odoo.odoo.mobile.SERVER_NOTIFICATION ADDED from AndroidManifest.xml:77:13 action#com.odoo.odoo.mobile.SERVER_NOTIFICATION ADDED from AndroidManifest.xml:78:17 android:name ADDED from AndroidManifest.xml:78:25 service#com.odoo.core.auth.OdooAuthService ADDED from AndroidManifest.xml:82:9 android:name ADDED from AndroidManifest.xml:82:18 intent-filter#android.accounts.AccountAuthenticator ADDED from AndroidManifest.xml:83:13 action#android.accounts.AccountAuthenticator ADDED from AndroidManifest.xml:84:17 android:name ADDED from AndroidManifest.xml:84:25 meta-data#android.accounts.AccountAuthenticator ADDED from AndroidManifest.xml:87:13 android:resource ADDED from AndroidManifest.xml:89:17 android:name ADDED from AndroidManifest.xml:88:17 provider#com.odoo.core.orm.provider.BaseModelProvider ADDED from AndroidManifest.xml:92:9 android:multiprocess ADDED from AndroidManifest.xml:95:13 android:authorities ADDED from AndroidManifest.xml:94:13 android:name ADDED from AndroidManifest.xml:93:13 provider#com.odoo.base.addons.ir.providers.IrModelProvider ADDED from AndroidManifest.xml:96:9 android:multiprocess ADDED from AndroidManifest.xml:99:13 android:authorities ADDED from AndroidManifest.xml:98:13 android:name ADDED from AndroidManifest.xml:97:13 provider#com.odoo.addons.customers.providers.CustomersSyncProvider ADDED from AndroidManifest.xml:102:9 android:label ADDED from AndroidManifest.xml:105:13 android:multiprocess ADDED from AndroidManifest.xml:106:13 android:authorities ADDED from AndroidManifest.xml:104:13 android:name ADDED from AndroidManifest.xml:103:13 service#com.odoo.addons.customers.services.CustomerSyncService ADDED from AndroidManifest.xml:108:9 android:process ADDED from AndroidManifest.xml:111:13 android:exported ADDED from AndroidManifest.xml:110:13 android:name ADDED from AndroidManifest.xml:109:13 intent-filter#android.content.SyncAdapter ADDED from AndroidManifest.xml:112:13 action#android.content.SyncAdapter ADDED from AndroidManifest.xml:113:17 android:name ADDED from AndroidManifest.xml:113:25 meta-data#android.content.SyncAdapter ADDED from AndroidManifest.xml:116:13 android:resource ADDED from AndroidManifest.xml:118:17 android:name ADDED from AndroidManifest.xml:117:17 provider#com.odoo.addons.calendar.providers.CalendarSyncProvider ADDED from AndroidManifest.xml:121:9 android:label ADDED from AndroidManifest.xml:124:13 android:multiprocess ADDED from AndroidManifest.xml:125:13 android:authorities ADDED from AndroidManifest.xml:123:13 android:name ADDED from AndroidManifest.xml:122:13 service#com.odoo.addons.calendar.services.CalendarSyncService ADDED from AndroidManifest.xml:127:9 android:process ADDED from AndroidManifest.xml:130:13 android:exported ADDED from AndroidManifest.xml:129:13 android:name ADDED from AndroidManifest.xml:128:13 provider#com.odoo.addons.crm.providers.CRMLeadProvider ADDED from AndroidManifest.xml:140:9 android:label ADDED from AndroidManifest.xml:143:13 android:multiprocess ADDED from AndroidManifest.xml:144:13 android:authorities ADDED from AndroidManifest.xml:142:13 android:name ADDED from AndroidManifest.xml:141:13 service#com.odoo.addons.crm.services.CRMLeadSyncService ADDED from AndroidManifest.xml:146:9 android:process ADDED from AndroidManifest.xml:149:13 android:exported ADDED from AndroidManifest.xml:148:13 android:name ADDED from AndroidManifest.xml:147:13 provider#com.odoo.addons.sale.providers.SaleOrderProvider ADDED from AndroidManifest.xml:159:9 android:label ADDED from AndroidManifest.xml:162:13 android:multiprocess ADDED from AndroidManifest.xml:163:13 android:authorities ADDED from AndroidManifest.xml:161:13 android:name ADDED from AndroidManifest.xml:160:13 service#com.odoo.addons.sale.services.SaleOrderSyncService ADDED from AndroidManifest.xml:165:9 android:process ADDED from AndroidManifest.xml:168:13 android:exported ADDED from AndroidManifest.xml:167:13 android:name ADDED from AndroidManifest.xml:166:13 provider#com.odoo.addons.phonecall.providers.PhoneCallProvider ADDED from AndroidManifest.xml:178:9 android:label ADDED from AndroidManifest.xml:181:13 android:multiprocess ADDED from AndroidManifest.xml:182:13 android:authorities ADDED from AndroidManifest.xml:180:13 android:name ADDED from AndroidManifest.xml:179:13 service#com.odoo.addons.phonecall.services.PhoneCallSyncService ADDED from AndroidManifest.xml:184:9 android:process ADDED from AndroidManifest.xml:187:13 android:exported ADDED from AndroidManifest.xml:186:13 android:name ADDED from AndroidManifest.xml:185:13 activity#com.odoo.addons.calendar.EventDetail ADDED from AndroidManifest.xml:197:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:199:13 android:name ADDED from AndroidManifest.xml:198:13 activity#com.odoo.addons.customers.CustomerDetails ADDED from AndroidManifest.xml:200:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:202:13 android:name ADDED from AndroidManifest.xml:201:13 activity#com.odoo.addons.crm.CRMDetail ADDED from AndroidManifest.xml:203:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:205:13 android:name ADDED from AndroidManifest.xml:204:13 activity#com.odoo.addons.crm.ConvertToOpportunityWizard ADDED from AndroidManifest.xml:206:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:209:13 android:theme ADDED from AndroidManifest.xml:208:13 android:name ADDED from AndroidManifest.xml:207:13 activity#com.odoo.addons.crm.ConvertToQuotation ADDED from AndroidManifest.xml:210:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:213:13 android:theme ADDED from AndroidManifest.xml:212:13 android:name ADDED from AndroidManifest.xml:211:13 activity#com.odoo.addons.phonecall.PhoneCallDetail ADDED from AndroidManifest.xml:214:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:216:13 android:name ADDED from AndroidManifest.xml:215:13 activity#com.odoo.addons.sale.SalesDetail ADDED from AndroidManifest.xml:217:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:220:13 android:screenOrientation ADDED from AndroidManifest.xml:219:13 android:name ADDED from AndroidManifest.xml:218:13 receiver#com.odoo.addons.phonecall.features.receivers.PhoneStateReceiver ADDED from AndroidManifest.xml:222:9 android:name ADDED from AndroidManifest.xml:222:19 intent-filter#android.intent.action.PHONE_STATE ADDED from AndroidManifest.xml:223:13 action#android.intent.action.PHONE_STATE ADDED from AndroidManifest.xml:224:17 android:name ADDED from AndroidManifest.xml:224:25 intent-filter#android.intent.action.NEW_OUTGOING_CALL ADDED from AndroidManifest.xml:226:13 action#android.intent.action.NEW_OUTGOING_CALL ADDED from AndroidManifest.xml:227:17 android:name ADDED from AndroidManifest.xml:227:25 activity#com.odoo.addons.sale.AddProductLineWizard ADDED from AndroidManifest.xml:231:9 android:windowSoftInputMode ADDED from AndroidManifest.xml:233:13 android:name ADDED from AndroidManifest.xml:232:13 uses-sdk INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested MERGED from com.android.support:appcompat-v7:22.2.0:20:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.android.support:cardview-v7:22.0.0:20:5 MERGED from com.google.android.gms:play-services:7.5.0:18:5 MERGED from com.google.android.gms:play-services-ads:7.5.0:23:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-analytics:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-appindexing:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-appinvite:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-appstate:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-cast:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.android.support:mediarouter-v7:22.0.0:20:5 MERGED from com.android.support:appcompat-v7:22.2.0:20:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-drive:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-fitness:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-location:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:28:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-games:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-drive:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-gcm:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-identity:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-location:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:28:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:28:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-nearby:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-panorama:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-plus:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-safetynet:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-wallet:7.5.0:19:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-identity:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:28:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from com.google.android.gms:play-services-wearable:7.5.0:18:5 MERGED from com.google.android.gms:play-services-base:7.5.0:18:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from master-crm:intro-slider-lib:unspecified:7:5 MERGED from com.android.support:support-v4:22.2.0:20:5 MERGED from master-crm:parallax-effect-lib:unspecified:7:5 MERGED from master-crm:odoo-rpc-v2:unspecified:7:5 MERGED from master-crm:calendar-lib:unspecified:7:5 MERGED from master-crm:bottom-sheet-lib:unspecified:7:5 MERGED from master-crm:snackbar-lib:unspecified:7:5 android:targetSdkVersion INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 android:minSdkVersion INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 activity#com.google.android.gms.ads.AdActivity ADDED from com.google.android.gms:play-services-ads:7.5.0:26:9 android:configChanges ADDED from com.google.android.gms:play-services-ads:7.5.0:28:13 android:theme ADDED from com.google.android.gms:play-services-ads:7.5.0:29:13 android:name ADDED from com.google.android.gms:play-services-ads:7.5.0:27:13 activity#com.google.android.gms.ads.purchase.InAppPurchaseActivity ADDED from com.google.android.gms:play-services-ads:7.5.0:30:9 android:theme ADDED from com.google.android.gms:play-services-ads:7.5.0:31:13 android:name ADDED from com.google.android.gms:play-services-ads:7.5.0:30:19 meta-data#com.google.android.gms.version ADDED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 MERGED from com.google.android.gms:play-services-base:7.5.0:21:9 android:name ADDED from com.google.android.gms:play-services-base:7.5.0:22:13 android:value ADDED from com.google.android.gms:play-services-base:7.5.0:23:13 uses-permission#android.permission.ACCESS_COARSE_LOCATION ADDED from com.google.android.gms:play-services-maps:7.5.0:23:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:23:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:23:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:23:5 android:name ADDED from com.google.android.gms:play-services-maps:7.5.0:23:22 uses-feature#0x00020000 ADDED from com.google.android.gms:play-services-maps:7.5.0:24:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:24:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:24:5 MERGED from com.google.android.gms:play-services-maps:7.5.0:24:5 android:required ADDED from com.google.android.gms:play-services-maps:7.5.0:26:8 android:glEsVersion ADDED from com.google.android.gms:play-services-maps:7.5.0:25:8 meta-data#com.google.android.gms.wallet.api.enabled ADDED from com.google.android.gms:play-services-wallet:7.5.0:25:9 android:name ADDED from com.google.android.gms:play-services-wallet:7.5.0:26:13 android:value ADDED from com.google.android.gms:play-services-wallet:7.5.0:27:13 receiver#com.google.android.gms.wallet.EnableWalletOptimizationReceiver ADDED from com.google.android.gms:play-services-wallet:7.5.0:28:9 android:exported ADDED from com.google.android.gms:play-services-wallet:7.5.0:30:13 android:name ADDED from com.google.android.gms:play-services-wallet:7.5.0:29:13 intent-filter#com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION ADDED from com.google.android.gms:play-services-wallet:7.5.0:31:13 action#com.google.android.gms.wallet.ENABLE_WALLET_OPTIMIZATION ADDED from com.google.android.gms:play-services-wallet:7.5.0:32:17 android:name ADDED from com.google.android.gms:play-services-wallet:7.5.0:32:25 uses-permission#android.permission.CAMERA ADDED from master-crm:odoo-rpc-v2:unspecified:18:5 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:18:22 receiver#odoo.kernel.handler.ActionHandler ADDED from master-crm:odoo-rpc-v2:unspecified:24:9 android:permission ADDED from master-crm:odoo-rpc-v2:unspecified:26:13 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:25:13 intent-filter#com.google.android.c2dm.intent.RECEIVE+com.google.android.c2dm.intent.REGISTRATION ADDED from master-crm:odoo-rpc-v2:unspecified:27:13 action#com.google.android.c2dm.intent.RECEIVE ADDED from master-crm:odoo-rpc-v2:unspecified:28:17 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:28:25 action#com.google.android.c2dm.intent.REGISTRATION ADDED from master-crm:odoo-rpc-v2:unspecified:29:17 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:29:25 service#odoo.kernel.handler.ActionHandlerService ADDED from master-crm:odoo-rpc-v2:unspecified:33:9 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:33:18 activity#odoo.zxing.handler.OdooMobileQRReader ADDED from master-crm:odoo-rpc-v2:unspecified:35:9 android:screenOrientation ADDED from master-crm:odoo-rpc-v2:unspecified:37:13 android:name ADDED from master-crm:odoo-rpc-v2:unspecified:36:13 ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/dpr/eclipse-adt/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/odoo/App.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 17/12/14 6:06 PM */ package com.odoo; import android.app.ActivityManager; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.odoo.core.support.OUser; import java.util.HashMap; import java.util.List; import odoo.Odoo; public class App extends Application { public static final String TAG = App.class.getSimpleName(); private static HashMap mOdooInstances = new HashMap<>(); @Override public void onCreate() { super.onCreate(); } public Odoo getOdoo(OUser user) { if (mOdooInstances.containsKey(user.getAndroidName())) { return mOdooInstances.get(user.getAndroidName()); } return null; } public void setOdoo(Odoo odoo, OUser user) { if (user != null) mOdooInstances.put(user.getAndroidName(), odoo); } /** * Checks for network availability * * @return true, if network available */ public boolean inNetwork() { boolean isConnected = false; ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo nInfo = manager.getActiveNetworkInfo(); if (nInfo != null && nInfo.isConnectedOrConnecting()) { isConnected = true; } return isConnected; } /** * Checks for installed application * * @param appPackage * @return true, if application installed on device */ public boolean appInstalled(String appPackage) { boolean mInstalled = false; try { PackageManager mPackage = getPackageManager(); mPackage.getPackageInfo(appPackage, PackageManager.GET_ACTIVITIES); mInstalled = true; } catch (Exception e) { e.printStackTrace(); } return mInstalled; } /** * Check for app is on top of screen * * @return true, if application is running on top */ public boolean meOnTop() { boolean meOnTop = false; ActivityManager aManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List taskInfo = aManager.getRunningTasks(1); ComponentName componentName = taskInfo.get(0).topActivity; if (componentName.getPackageName().equalsIgnoreCase(getPackageName())) { meOnTop = true; } return meOnTop; } } ================================================ FILE: app/src/main/java/com/odoo/OdooActivity.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 5:25 PM */ package com.odoo; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarDrawerToggle; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import com.odoo.core.account.AppIntro; import com.odoo.core.account.ManageAccounts; import com.odoo.core.account.OdooLogin; import com.odoo.core.account.OdooUserAskPassword; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.auth.OdooAuthenticator; import com.odoo.core.orm.OModel; import com.odoo.core.support.OUser; import com.odoo.core.support.addons.fragment.IBaseFragment; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.sync.SyncUtils; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OFragmentUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.core.utils.drawer.DrawerUtils; import com.odoo.core.utils.sys.IOnActivityResultListener; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.R; import java.util.List; public class OdooActivity extends ActionBarActivity { public static final String TAG = OdooActivity.class.getSimpleName(); public static final Integer DRAWER_ITEM_LAUNCH_DELAY = 300; public static final Integer DRAWER_ACCOUNT_BOX_ANIMATION_DURATION = 250; public static final String KEY_ACCOUNT_REQUEST = "key_account_request"; public static final String KEY_NEW_USER_NAME = "key_new_account_username"; public static final String KEY_CURRENT_DRAWER_ITEM = "key_drawer_item_index"; public static final String KEY_APP_TITLE = "key_app_title"; public static final String KEY_HAS_ACTIONBAR_SPINNER = "key_has_actionbar_spinner"; public static final Integer REQUEST_ACCOUNT_CREATE = 1101; public static final Integer REQUEST_ACCOUNTS_MANAGE = 1102; public static final String KEY_FRESH_LOGIN = "key_fresh_login"; private DrawerLayout mDrawerLayout = null; private ActionBarDrawerToggle mDrawerToggle = null; private IOnBackPressListener backPressListener = null; private IOnActivityResultListener mIOnActivityResultListener = null; //Drawer Containers private LinearLayout mDrawerAccountContainer = null; private LinearLayout mDrawerItemContainer = null; private Boolean mAccountBoxExpanded = false; private Bundle mSavedInstanceState = null; private Spinner spinner_nav = null; private Integer mDrawerSelectedIndex = -1; private Boolean mHasActionBarSpinner = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "OdooActivity->onCreate"); mSavedInstanceState = savedInstanceState; OPreferenceManager preferenceManager = new OPreferenceManager(this); if (!preferenceManager.getBoolean(KEY_FRESH_LOGIN, false)) { preferenceManager.setBoolean(KEY_FRESH_LOGIN, true); new Handler().postDelayed(new Runnable() { @Override public void run() { startActivity(new Intent(OdooActivity.this, AppIntro.class)); } }, 1000); } setContentView(R.layout.odoo_activity); OActionBarUtils.setActionBar(this, true); setupDrawer(); } // Creating drawer private void setupDrawer() { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.app_name, R.string.app_name) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); setTitle(getResources().getString(R.string.app_name)); invalidateOptionsMenu(); } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); invalidateOptionsMenu(); setTitle(R.string.app_name); } @Override public void onDrawerStateChanged(int newState) { super.onDrawerStateChanged(newState); invalidateOptionsMenu(); } @Override public void onDrawerSlide(View drawerView, float slideOffset) { super.onDrawerSlide(drawerView, slideOffset); invalidateOptionsMenu(); } }; mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, Gravity.START); mDrawerToggle.syncState(); setupAccountBox(); setupDrawerBox(); } private void setupDrawerBox() { mDrawerItemContainer = (LinearLayout) findViewById(R.id.drawerItemList); mDrawerItemContainer.removeAllViews(); List items = DrawerUtils.getDrawerItems(this); for (ODrawerItem item : items) { View view = LayoutInflater.from(this). inflate((item.isGroupTitle()) ? R.layout.base_drawer_group_layout : R.layout.base_drawer_menu_item, mDrawerItemContainer, false); view.setTag(item); if (!item.isGroupTitle()) { view.setOnClickListener(drawerItemClick); } mDrawerItemContainer.addView(DrawerUtils.fillDrawerItemValue(view, item)); } } private View.OnClickListener drawerItemClick = new View.OnClickListener() { @Override public void onClick(View v) { int index = mDrawerItemContainer.indexOfChild(v); if (mDrawerSelectedIndex != index) { ODrawerItem item = (ODrawerItem) v.getTag(); if (item.getInstance() instanceof Fragment) { focusOnDrawerItem(index); setTitle(item.getTitle()); } loadDrawerItemInstance(item.getInstance(), item.getExtra()); } else { closeDrawer(); } } }; public void closeDrawer() { new Handler().postDelayed(new Runnable() { @Override public void run() { mDrawerLayout.closeDrawer(Gravity.START); } }, DRAWER_ITEM_LAUNCH_DELAY); } /** * Loads fragment or start intent * * @param instance, instance of fragment or intent */ private void loadDrawerItemInstance(Object instance, Bundle extra) { if (instance != null) { if (instance instanceof Intent) { Log.i(TAG, "Loading intent: " + instance.getClass().getCanonicalName()); startActivity((Intent) instance); } if (instance instanceof Class) { Class cls = (Class) instance; Intent intent = null; if (cls.getSuperclass().isAssignableFrom(Activity.class)) { intent = new Intent(this, cls); } if (cls.getSuperclass().isAssignableFrom(ActionBarActivity.class)) { intent = new Intent(this, cls); } if (intent != null) { if (extra != null) intent.putExtras(extra); loadDrawerItemInstance(intent, null); return; } } if (instance instanceof Fragment) { Log.i(TAG, "Loading fragment: " + instance.getClass().getCanonicalName()); OFragmentUtils.get(this, mSavedInstanceState).startFragment((Fragment) instance, false, extra); } } closeDrawer(); } public void loadFragment(Fragment fragment, Boolean addToBackState, Bundle extra) { OFragmentUtils.get(this, null).startFragment(fragment, addToBackState, extra); } private void setupAccountBox() { mDrawerAccountContainer = (LinearLayout) findViewById(R.id.accountList); View chosenAccountView = findViewById(R.id.drawerAccountView); OUser currentUser = OUser.current(this); if (currentUser == null) { chosenAccountView.setVisibility(View.GONE); mDrawerAccountContainer.setVisibility(View.GONE); return; } else { chosenAccountView.setVisibility(View.VISIBLE); mDrawerAccountContainer.setVisibility(View.INVISIBLE); } ImageView avatar = (ImageView) chosenAccountView.findViewById(R.id.profile_image); TextView name = (TextView) chosenAccountView.findViewById(R.id.profile_name_text); TextView url = (TextView) chosenAccountView.findViewById(R.id.profile_url_text); name.setText(currentUser.getName()); url.setText((currentUser.isOAauthLogin()) ? currentUser.getInstanceUrl() : currentUser.getHost()); if (!currentUser.getAvatar().equals("false")) { Bitmap bitmap = BitmapUtils.getBitmapImage(this, currentUser.getAvatar()); if (bitmap != null) avatar.setImageBitmap(bitmap); } // Setting Accounts List accounts = OdooAccountManager.getAllAccounts(this); if (accounts.size() > 0) { chosenAccountView.setEnabled(true); ImageView boxIndicator = (ImageView) findViewById(R.id.expand_account_box_indicator); boxIndicator.setVisibility(View.VISIBLE); chosenAccountView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mAccountBoxExpanded = !mAccountBoxExpanded; accountBoxToggle(); } }); populateAccountList(currentUser, accounts); } } private void accountBoxToggle() { ImageView boxIndicator = (ImageView) findViewById(R.id.expand_account_box_indicator); boxIndicator.setImageResource(mAccountBoxExpanded ? R.drawable.ic_drawer_accounts_collapse : R.drawable.ic_drawer_accounts_expand); int hideTranslateY = -mDrawerAccountContainer.getHeight() / 4; if (mAccountBoxExpanded && mDrawerAccountContainer.getTranslationY() == 0) { // initial setup mDrawerAccountContainer.setAlpha(0); mDrawerAccountContainer.setTranslationY(hideTranslateY); } AnimatorSet set = new AnimatorSet(); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mDrawerItemContainer .setVisibility(mAccountBoxExpanded ? View.INVISIBLE : View.VISIBLE); mDrawerAccountContainer .setVisibility(mAccountBoxExpanded ? View.VISIBLE : View.INVISIBLE); } @Override public void onAnimationCancel(Animator animation) { onAnimationEnd(animation); } }); if (mAccountBoxExpanded) { mDrawerAccountContainer.setVisibility(View.VISIBLE); AnimatorSet subSet = new AnimatorSet(); subSet.playTogether( ObjectAnimator .ofFloat(mDrawerAccountContainer, View.ALPHA, 1) .setDuration(DRAWER_ACCOUNT_BOX_ANIMATION_DURATION), ObjectAnimator.ofFloat(mDrawerAccountContainer, View.TRANSLATION_Y, 0).setDuration( DRAWER_ACCOUNT_BOX_ANIMATION_DURATION)); set.playSequentially( ObjectAnimator.ofFloat(mDrawerItemContainer, View.ALPHA, 0).setDuration( DRAWER_ACCOUNT_BOX_ANIMATION_DURATION), subSet); set.start(); } else { mDrawerItemContainer.setVisibility(View.VISIBLE); AnimatorSet subSet = new AnimatorSet(); subSet.playTogether( ObjectAnimator .ofFloat(mDrawerAccountContainer, View.ALPHA, 0) .setDuration(DRAWER_ACCOUNT_BOX_ANIMATION_DURATION), ObjectAnimator.ofFloat(mDrawerAccountContainer, View.TRANSLATION_Y, hideTranslateY).setDuration( DRAWER_ACCOUNT_BOX_ANIMATION_DURATION)); set.playSequentially( subSet, ObjectAnimator.ofFloat(mDrawerItemContainer, View.ALPHA, 1).setDuration( DRAWER_ACCOUNT_BOX_ANIMATION_DURATION)); set.start(); } set.start(); } private void populateAccountList(OUser me, List accounts) { mDrawerAccountContainer.removeAllViews(); for (final OUser user : accounts) { if (!user.getAndroidName().equals(me.getAndroidName())) { View view = LayoutInflater.from(this).inflate(R.layout.base_drawer_account_item, mDrawerAccountContainer, false); ImageView avatar = (ImageView) view.findViewById(R.id.profile_image); if (!user.getAvatar().equals("false")) { Bitmap img = BitmapUtils.getBitmapImage(this, user.getAvatar()); if (img != null) avatar.setImageBitmap(img); } OControls.setText(view, R.id.profile_name_text, user.getName()); OControls.setText(view, R.id.profile_url_text, (user.isOAauthLogin()) ? user.getInstanceUrl() : user.getHost()); // Setting login event for other account view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { OdooUserAskPassword.get(OdooActivity.this, user) .setOnUserPasswordValidateListener( new OdooUserAskPassword.OnUserPasswordValidateListener() { @Override public void onSuccess() { // Logging in to other account OdooAccountManager.login(OdooActivity.this, user.getAndroidName()); OModel.sqLite = null; mAccountBoxExpanded = false; accountBoxToggle(); mDrawerLayout.closeDrawer(Gravity.START); // Restarting activity restartActivity(); } @Override public void onCancel() { } @Override public void onFail() { OAlert.showError(OdooActivity.this, OResource.string(OdooActivity.this, R.string.error_invalid_password)); } }).show(); } }); mDrawerAccountContainer.addView(view); } } accountListDefaultItems(); } private void restartActivity() { new Handler().postDelayed(new Runnable() { @Override public void run() { Intent intent = new Intent(OdooActivity.this, OdooActivity.class); finish(); startActivity(intent); } }, DRAWER_ITEM_LAUNCH_DELAY); } private void accountListDefaultItems() { // Adding add account View view = generateView(OResource.string(this, R.string.label_drawer_account_add_account), R.drawable.ic_action_add); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent loginActivity = new Intent(OdooActivity.this, OdooLogin.class); loginActivity.putExtra(OdooAuthenticator.KEY_NEW_ACCOUNT_REQUEST, true); loginActivity.putExtra(KEY_ACCOUNT_REQUEST, true); startActivityForResult(loginActivity, REQUEST_ACCOUNT_CREATE); } }); mDrawerAccountContainer.addView(view); // Adding add account view = generateView(OResource.string(this, R.string.label_drawer_account_manage_accounts), R.drawable.ic_action_settings); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivityForResult(new Intent(OdooActivity.this, ManageAccounts.class), REQUEST_ACCOUNTS_MANAGE); } }); mDrawerAccountContainer.addView(view); } private View generateView(String title, int res_id) { View view = LayoutInflater.from(this).inflate(R.layout.base_drawer_account_item, mDrawerAccountContainer, false); OControls.setGone(view, R.id.profile_url_text); ImageView icon = (ImageView) view.findViewById(R.id.profile_image); icon.setImageResource(res_id); icon.setColorFilter(OResource.color(this, R.color.body_text_2)); TextView name = (TextView) view.findViewById(R.id.profile_name_text); name.setTypeface(name.getTypeface(), Typeface.BOLD); name.setText(title); return view; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mDrawerToggle != null) { mDrawerToggle.onConfigurationChanged(newConfig); } } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); } @Override public void onBackPressed() { if (backPressListener != null) { if (backPressListener.onBackPressed()) { super.onBackPressed(); } } else super.onBackPressed(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mIOnActivityResultListener != null) { mIOnActivityResultListener.onOdooActivityResult(requestCode, resultCode, data); } if (resultCode == RESULT_OK) { if (requestCode == REQUEST_ACCOUNT_CREATE) { if (mDrawerLayout != null) { mDrawerLayout.closeDrawer(Gravity.START); accountBoxToggle(); } OdooAccountManager.login(this, data.getStringExtra(KEY_NEW_USER_NAME)); OModel.sqLite = null; restartActivity(); } if (requestCode == REQUEST_ACCOUNTS_MANAGE) { startActivity(new Intent(this, OdooLogin.class)); finish(); } } } /** * Set system back button press listener * * @param listener */ public void setOnBackPressListener(IOnBackPressListener listener) { backPressListener = listener; } public void setOnActivityResultListener(IOnActivityResultListener listener) { mIOnActivityResultListener = listener; } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mSavedInstanceState = savedInstanceState; if (savedInstanceState == null) { // Loading Default Fragment (if any) new Handler().postDelayed(new Runnable() { @Override public void run() { IBaseFragment fragment = DrawerUtils.getDefaultDrawerFragment(); if (fragment != null) { ODrawerItem item = DrawerUtils.getStartableObject(OdooActivity.this, fragment); setTitle(item.getTitle()); loadDrawerItemInstance(item.getInstance(), item.getExtra()); int selected_item = DrawerUtils.findItemIndex(item, mDrawerItemContainer); if (selected_item > -1) { focusOnDrawerItem(selected_item); } } } }, DRAWER_ITEM_LAUNCH_DELAY); } else { mHasActionBarSpinner = savedInstanceState.getBoolean(KEY_HAS_ACTIONBAR_SPINNER); mDrawerSelectedIndex = savedInstanceState.getInt(KEY_CURRENT_DRAWER_ITEM); setTitle(savedInstanceState.getString(KEY_APP_TITLE)); focusOnDrawerItem(mDrawerSelectedIndex); } } private void focusOnDrawerItem(int index) { mDrawerSelectedIndex = index; for (int i = 0; i < mDrawerItemContainer.getChildCount(); i++) { DrawerUtils.focusOnView(this, mDrawerItemContainer.getChildAt(i), i == index); } } @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_CURRENT_DRAWER_ITEM, mDrawerSelectedIndex); outState.putString(KEY_APP_TITLE, getTitle().toString()); outState.putBoolean(KEY_HAS_ACTIONBAR_SPINNER, mHasActionBarSpinner); super.onSaveInstanceState(outState); } public SyncUtils sync() { return SyncUtils.get(this); } /** * Actionbar Spinner handler */ public void setHasActionBarSpinner(Boolean hasActionBarSpinner) { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { Spinner spinner = (Spinner) findViewById(R.id.spinner_nav); if (hasActionBarSpinner) { if (spinner != null) spinner.setVisibility(View.VISIBLE); actionBar.setDisplayShowTitleEnabled(false); } else { if (spinner != null) spinner.setVisibility(View.GONE); actionBar.setDisplayShowTitleEnabled(true); } mHasActionBarSpinner = hasActionBarSpinner; } } public Spinner getActionBarSpinner() { Spinner spinner = null; if (mHasActionBarSpinner) { spinner = (Spinner) findViewById(R.id.spinner_nav); spinner.setAdapter(null); } return spinner; } public void refreshDrawer() { setupDrawerBox(); } } ================================================ FILE: app/src/main/java/com/odoo/SettingsActivity.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 11:32 AM */ package com.odoo; import android.accounts.Account; import android.content.ContentResolver; import android.content.Intent; import android.content.SyncAdapterType; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.MenuItem; import android.widget.Toast; import com.odoo.core.account.About; import com.odoo.core.account.OdooLogin; import com.odoo.core.support.OUser; import com.odoo.core.support.sync.SyncUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import java.util.ArrayList; import java.util.List; public class SettingsActivity extends ActionBarActivity { public static final String TAG = SettingsActivity.class.getSimpleName(); public static final String ACTION_ABOUT = "com.odoo.ACTION_ABOUT"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_setting_activity); OActionBarUtils.setActionBar(this, true); ActionBar actionbar = getSupportActionBar(); actionbar.setHomeButtonEnabled(true); actionbar.setDisplayHomeAsUpEnabled(true); actionbar.setTitle(R.string.title_application_settings); } @Override public void startActivity(Intent intent) { if (intent.getAction() != null && intent.getAction().equals(ACTION_ABOUT)) { Intent about = new Intent(this, About.class); super.startActivity(about); return; } super.startActivity(intent); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { finish(); } return super.onOptionsItemSelected(item); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); settingUpdated(); } private void settingUpdated() { OUser user = OUser.current(this); if (user == null) { Intent loginActivity = new Intent(this, OdooLogin.class); loginActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(loginActivity); finish(); } else { Account mAccount = user.getAccount(); OPreferenceManager mPref = new OPreferenceManager(this); int sync_interval = mPref.getInt("sync_interval", 1440); List default_authorities = new ArrayList<>(); default_authorities.add("com.android.calendar"); default_authorities.add("com.android.contacts"); SyncAdapterType[] list = ContentResolver.getSyncAdapterTypes(); for (SyncAdapterType lst : list) { if (lst.authority.contains("com.odoo") && lst.authority.contains("providers")) { default_authorities.add(lst.authority); } } for (String authority : default_authorities) { boolean isSyncActive = ContentResolver.getSyncAutomatically( mAccount, authority); if (isSyncActive) { SyncUtils.get(this).setSyncPeriodic(authority, sync_interval, 60, 1); } } Toast.makeText(this, OResource.string(this, R.string.toast_setting_saved), Toast.LENGTH_LONG).show(); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/CalendarDashboard.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 10:34 AM */ package com.odoo.addons.calendar; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.odoo.R; import com.odoo.addons.calendar.models.CalendarEvent; import com.odoo.addons.calendar.utils.CalendarUtils; import com.odoo.addons.calendar.utils.TodayIcon; import com.odoo.addons.crm.CRMDetail; import com.odoo.addons.crm.CRMLeads; import com.odoo.addons.crm.ConvertToQuotation; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.phonecall.PhoneCallDetail; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.base.addons.res.ResCurrency; import com.odoo.base.addons.res.ResPartner; import com.odoo.libs.calendar.SysCal; import com.odoo.libs.calendar.view.OdooCalendar; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.support.sync.SyncUtils; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import com.odoo.core.utils.dialog.OChoiceDialog; import com.odoo.core.utils.sys.IOnActivityResultListener; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import com.odoo.widgets.snackbar.SnackBar; import com.odoo.widgets.snackbar.SnackbarBuilder; import com.odoo.widgets.snackbar.listeners.EventListener; import java.util.ArrayList; import java.util.Date; import java.util.List; public class CalendarDashboard extends BaseFragment implements View.OnClickListener, BottomSheetListeners.OnSheetItemClickListener, IOnBackPressListener, OdooCalendar.OdooCalendarDateSelectListener, LoaderManager.LoaderCallbacks, OCursorListAdapter.OnViewBindListener, SwipeRefreshLayout.OnRefreshListener, ISyncStatusObserverListener, BottomSheetListeners.OnSheetActionClickListener, BottomSheetListeners.OnSheetMenuCreateListener, EventListener, IOnSearchViewChangeListener, IOnItemClickListener, OCursorListAdapter.OnViewCreateListener, AdapterView.OnItemSelectedListener, IOnActivityResultListener, OCursorListAdapter.BeforeBindUpdateData { public static final String TAG = CalendarDashboard.class.getSimpleName(); public static final String KEY = "key_calendar_dashboard"; public static final String KEY_DATE = "key_date"; public static final int REQUEST_CONVERT_TO_QUOTATION_WIZARD = 229; private BottomSheet mSheet = null; private OdooCalendar odooCalendar; private View calendarView = null; private ListView dashboardListView; private View mView; private String mFilterDate; private OCursorListAdapter mAdapter; private boolean syncRequested = false; private String mFilter = null; private String wonLost = "won"; private CRMLead crmLead; private ODataRow convertRequestRecord; private Spinner navSpinner; private OListAdapter navSpinnerAdapter; private FilterType mFilterType = FilterType.All; private enum SheetType { Event, PhoneCall, Opportunity } private enum FilterType { All(R.string.label_all), Meetings(R.string.label_meetings), Opportunities(R.string.label_opportunity), PhoneCalls(R.string.label_phone_calls); int str_id = 0; FilterType(int type) { str_id = type; } public String getString(Context context) { return OResource.string(context, str_id); } } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setHasOptionsMenu(true); return inflater.inflate(R.layout.calendar_dashboard, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; setHasFloatingButton(view, R.id.fabButton, null, this); parent().setOnBackPressListener(this); parent().setHasActionBarSpinner(true); parent().setOnActivityResultListener(this); navSpinner = parent().getActionBarSpinner(); initActionSpinner(); odooCalendar = (OdooCalendar) view.findViewById(R.id.dashboard_calendar); crmLead = new CRMLead(getActivity(), null); odooCalendar.setOdooCalendarDateSelectListener(this); } private void initActionSpinner() { List spinnerItems = new ArrayList<>(); ODataRow row = new ODataRow(); row.put("key", FilterType.All.toString()); row.put("name", FilterType.All.getString(getActivity())); spinnerItems.add(row); row = new ODataRow(); row.put("key", FilterType.Meetings.toString()); row.put("name", FilterType.Meetings.getString(getActivity())); spinnerItems.add(row); row = new ODataRow(); row.put("key", FilterType.Opportunities.toString()); row.put("name", FilterType.Opportunities.getString(getActivity())); spinnerItems.add(row); row = new ODataRow(); row.put("key", FilterType.PhoneCalls.toString()); row.put("name", FilterType.PhoneCalls.getString(getActivity())); spinnerItems.add(row); navSpinnerAdapter = new OListAdapter(getActivity(), R.layout.base_simple_list_item_1, spinnerItems) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate(R.layout.base_simple_list_item_1_selected , parent, false); } return getSpinnerView(getItem(position), position, convertView, parent); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate(getResource(), parent, false); } return getSpinnerView(getItem(position), position, convertView, parent); } }; navSpinner.setAdapter(navSpinnerAdapter); navSpinner.setOnItemSelectedListener(this); } @Override public void onNothingSelected(AdapterView parent) { } @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { ODataRow row = (ODataRow) navSpinnerAdapter.getItem(position); mFilterType = FilterType.valueOf(row.getString("key")); if (mFilterDate != null && getActivity() != null) getLoaderManager().restartLoader(0, null, this); } private View getSpinnerView(Object row, int pos, View view, ViewGroup parent) { ODataRow r = (ODataRow) row; OControls.setText(view, android.R.id.text1, r.getString("name")); return view; } @Override public List weekDataInfo( List week_dates) { List items = new ArrayList<>(); CalendarEvent event = (CalendarEvent) db(); CRMPhoneCalls calls = new CRMPhoneCalls(getActivity(), event.getUser()); CRMLead lead = new CRMLead(getActivity(), event.getUser()); for (SysCal.DateInfo date : week_dates) { String date_str = date.getDateString(); int total = 0; switch (mFilterType) { case All: case Meetings: // Checking for events total += event.countGroupBy("date_start", "date(date_start)" , "(date(date_start) <= ? and date(date_end) >= ? )", new String[]{date_str, date_str}).getInt("total"); if (mFilterType != FilterType.All) break; case PhoneCalls: // Checking for phone calls total += calls.countGroupBy("date", "date(date)", "date(date) >= ? and date(date) <= ? and (state = ? or state = ?)", new String[]{date_str, date_str, "open", "pending"}) .getInt("total"); if (mFilterType != FilterType.All) break; case Opportunities: // Leads total += lead.countGroupBy("date_deadline", "date(date_deadline)", "(date(date_deadline) >= ? and date(date_deadline) <= ? or " + "date(date_action) >= ? and date(date_action) <= ?) and type = ?", new String[]{date_str, date_str, date_str, date_str, "opportunity"}).getInt("total"); if (mFilterType != FilterType.All) break; } items.add(new OdooCalendar.DateDataObject(date_str, (total > 0))); } return items; } @Override public View getEventsView(ViewGroup parent, SysCal.DateInfo date) { calendarView = LayoutInflater.from(getActivity()).inflate( R.layout.calendar_dashboard_items, parent, false); calendarView.findViewById(R.id.dashboard_no_item_view) .setOnClickListener(this); dashboardListView = (ListView) calendarView .findViewById(R.id.items_container); setHasFloatingButton(mView, R.id.fabButton, dashboardListView, this); initAdapter(); mFilterDate = date.getDateString(); if (getActivity() != null) { getLoaderManager().restartLoader(0, null, CalendarDashboard.this); } return calendarView; } private void initAdapter() { mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.calendar_dashboard_item_view); mAdapter.setBeforeBindUpdateData(this); mAdapter.setOnViewBindListener(this); mAdapter.setOnViewCreateListener(this); dashboardListView.setAdapter(mAdapter); mAdapter.changeCursor(null); mAdapter.handleItemClickListener(dashboardListView, this); setHasSyncStatusObserver(KEY, this, db()); } @Override public void onItemDoubleClick(View view, int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); String type = row.getString("data_type"); Class cls = EventDetail.class; if (type.equals("phone_call")) { cls = PhoneCallDetail.class; } if (type.equals("opportunity")) { cls = CRMDetail.class; } IntentUtils.startActivity(getActivity(), cls, row.getPrimaryBundleData()); } @Override public void onItemClick(View view, int position) { Cursor cr = (Cursor) mAdapter.getItem(position); String data_type = cr.getString(cr.getColumnIndex("data_type")); if (data_type.equals("event")) { showSheet(SheetType.Event, cr); } if (data_type.equals("phone_call")) { showSheet(SheetType.PhoneCall, cr); } if (data_type.equals("opportunity")) { showSheet(SheetType.Opportunity, cr); } } private void showSheet(SheetType type, Cursor data) { if (mSheet != null) { mSheet.dismiss(); } BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.body_text_2)); builder.setTextColor(_c(R.color.body_text_1)); builder.setData(data); builder.actionListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); builder.setOnSheetMenuCreateListener(this); switch (type) { case Event: builder.setOnSheetMenuCreateListener(this); builder.menu(R.menu.menu_dashboard_events); break; case PhoneCall: builder.menu(R.menu.menu_dashboard_phonecalls); break; case Opportunity: builder.menu(R.menu.menu_dashboard_opportunity); break; } mSheet = builder.create(); mSheet.show(); } @Override public void onSheetActionClick(BottomSheet sheet, final Object extras) { sheet.dismiss(); new Handler().postDelayed(new Runnable() { @Override public void run() { Cursor cr = (Cursor) extras; String data_type = cr.getString(cr.getColumnIndex("data_type")); int record_id = cr.getInt(cr.getColumnIndex(OColumn.ROW_ID)); if (data_type.equals("phone_call")) { Bundle extra = new Bundle(); extra.putInt(OColumn.ROW_ID, record_id); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); } if (data_type.equals("event")) { Bundle extra = new Bundle(); extra.putInt(OColumn.ROW_ID, record_id); IntentUtils.startActivity(getActivity(), EventDetail.class, extra); } if (data_type.equals("opportunity")) { Bundle bundle = new Bundle(); bundle.putInt(OColumn.ROW_ID, record_id); IntentUtils.startActivity(getActivity(), CRMDetail.class, bundle); } } }, 250); } @Override public void onSheetMenuCreate(Menu menu, Object extras) { Cursor cr = (Cursor) extras; String type = cr.getString(cr.getColumnIndex("data_type")); String is_done = cr.getString(cr.getColumnIndex("is_done")); if (type.equals("event")) { ODataRow row = OCursorUtils.toDatarow(cr); if (row.getString("location").equals("false")) { menu.findItem(R.id.menu_events_location).setVisible(false); } if (is_done.equals("1")) { menu.findItem(R.id.menu_events_all_done).setVisible(false); } } } @Override public View onViewCreated(Context context, ViewGroup view, Cursor cr, int position) { String data_type = cr.getString(cr.getColumnIndex("data_type")); if (data_type.equals("separator")) { return LayoutInflater.from(getActivity()).inflate( R.layout.calendar_dashboard_item_separator, view, false); } return LayoutInflater.from(getActivity()).inflate( R.layout.calendar_dashboard_item_view, view, false); } @Override public ODataRow updateDataRow(Cursor cr) { ODataRow row = OCursorUtils.toDatarow(cr); String type = row.getString("data_type"); ODataRow record = new ODataRow(); if (type.equals("opportunity")) { record.put("stage_id", crmLead.browse(new String[]{"stage_id"}, row.getInt(OColumn.ROW_ID)).get("stage_id")); } return record; } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { String type = row.getString("data_type"); GradientDrawable shape = (GradientDrawable) getActivity().getResources() .getDrawable(R.drawable.circle_mask_secondary); int icon = -1; ImageView iconView = (ImageView) view.findViewById(R.id.event_icon); if (type.equals("separator")) { OControls.setText(view, R.id.list_separator, row.getString("name")); } else { String colorCode = CalendarUtils.getColorData(row.getInt("color_index")). getString("code"); shape.setColor(Color.parseColor(colorCode)); String date = "false"; String desc = null; if (row.getString("description").equals("false")) { row.put("description", ""); } if (type.equals("event")) { desc = row.getString("description"); icon = R.drawable.ic_action_event; if (row.getString("allday").equals("false")) { date = row.getString("date_start"); view.findViewById(R.id.allDay).setVisibility(View.GONE); } else { TextView allDayTag = (TextView) view.findViewById(R.id.allDay); allDayTag.setTextColor(Color.parseColor(colorCode)); allDayTag.setVisibility(View.VISIBLE); } } if (type.equals("phone_call")) { icon = R.drawable.ic_action_phone; date = row.getString("date"); desc = row.getString("description"); } if (type.equals("opportunity")) { icon = R.drawable.ic_action_opportunities; ODataRow stage_id = row.getM2ORecord("stage_id").browse(); float probability = -1; if (stage_id != null && !stage_id.getString("probability").equals("false") && (stage_id.getString("type").equals("opportunity") || stage_id.getString("type").equals("both"))) { if (!stage_id.getString("name").equals("New")) probability = stage_id.getFloat("probability"); } if (probability == 0) { // Lost icon = R.drawable.ic_action_mark_lost; } else if (probability >= 100) { // Won icon = R.drawable.ic_action_mark_won; } desc = row.getString("planned_revenue") + " " + ResCurrency.getSymbol(getActivity(), row.getInt("company_currency")) + " at " + row.getString("probability") + " %"; if (!row.getString("title_action").equals("false")) { desc += "\n" + row.getString("title_action"); } OControls.setText(view, R.id.event_description, desc); } if (!date.equals("false")) { Date dateNow = new Date(); Date eventDate = ODateUtils.createDateObject(date, ODateUtils.DEFAULT_FORMAT, false); date = ODateUtils.convertToDefault(date, ODateUtils.DEFAULT_FORMAT, "hh:mm a"); OControls.setText(view, R.id.event_time, date); if (dateNow.after(eventDate) && !row.getBoolean("is_done")) { colorCode = "#cc0000"; } } OControls.setText(view, R.id.event_description, desc); Boolean is_done = row.getString("is_done").equals("1"); OControls.setImage(view, R.id.event_icon, icon); iconView.setBackgroundDrawable(shape); int title_color = (is_done) ? Color.LTGRAY : Color.parseColor("#414141"); int time_color = (is_done) ? Color.LTGRAY : Color.parseColor(colorCode); int desc_color = (is_done) ? Color.LTGRAY : _c(R.color.body_text_2); int allDay_color = (is_done) ? Color.LTGRAY : Color.parseColor(colorCode); OControls.setTextColor(view, R.id.event_name, title_color); OControls.setTextColor(view, R.id.event_time, time_color); OControls.setTextColor(view, R.id.event_description, desc_color); OControls.setTextColor(view, R.id.allDay, allDay_color); if (is_done) { view.findViewById(R.id.event_icon).setBackgroundResource( R.drawable.circle_mask_gray); OControls.setTextViewStrikeThrough(view, R.id.event_name); OControls.setTextViewStrikeThrough(view, R.id.event_time); OControls.setTextViewStrikeThrough(view, R.id.event_description); OControls.setTextViewStrikeThrough(view, R.id.allDay); } OControls.setText(view, R.id.event_name, row.getString("name")); } } @Override public Loader onCreateLoader(int id, Bundle data) { List args = new ArrayList<>(); String where = ""; args.add(mFilterDate); CalendarEvent event = (CalendarEvent) db(); Uri uri = event.agendaUri(); switch (mFilterType) { case PhoneCalls: CRMPhoneCalls phoneCalls = new CRMPhoneCalls(getActivity(), db().getUser()); uri = phoneCalls.uri(); where = "date(date) >= ? and date(date) <= ? and (state = ? or state = ?)"; args.add(mFilterDate); args.add("open"); args.add("pending"); if (mFilter != null) { where += " and (name like ? or description like ?)"; args.add("%" + mFilter + "%"); } break; case Opportunities: CRMLead leads = new CRMLead(getActivity(), db().getUser()); uri = leads.uri(); where = "(date(date_deadline) >= ? and date(date_deadline) <= ? or " + "date(date_action) >= ? and date(date_action) <= ?) and type = ?"; args.add(mFilterDate); args.add(mFilterDate); args.add(mFilterDate); args.add("opportunity"); if (mFilter != null) { where += " and (name like ? or description like ?)"; args.add("%" + mFilter + "%"); } break; case Meetings: uri = db().uri(); where = "(date(date_start) <= ? and date(date_end) >= ? )"; args.add(mFilterDate); if (mFilter != null) { where += " and name like ?"; } break; } if (mFilter != null) { args.add("%" + mFilter + "%"); } return new CursorLoader(getActivity(), uri, null, where, args.toArray(new String[args.size()]), null); } @Override public void onLoadFinished(Loader loader, final Cursor cr) { mAdapter.changeCursor(cr); if (cr != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { if (cr.getCount() > 0) { OControls.setGone(calendarView, R.id.dashboard_progress); OControls.setVisible(calendarView, R.id.items_container); OControls.setGone(calendarView, R.id.dashboard_no_items); setHasSwipeRefreshView(calendarView, R.id.swipe_container, CalendarDashboard.this); } else { setHasSwipeRefreshView(calendarView, R.id.dashboard_no_items, CalendarDashboard.this); OControls.setGone(calendarView, R.id.dashboard_progress); OControls.setVisible(calendarView, R.id.dashboard_no_items); } } }, 300); } if (db().isEmptyTable() && !syncRequested) { syncRequested = true; parent().sync().requestSync(CalendarEvent.AUTHORITY); setSwipeRefreshing(true); } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public List drawerMenus(Context context) { List menu = new ArrayList<>(); menu.add(new ODrawerItem(KEY).setTitle("Calendar") .setInstance(new CalendarDashboard()) .setIcon(R.drawable.ic_action_dashboard)); return menu; } @Override public Class database() { return CalendarEvent.class; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fabButton: case R.id.dashboard_no_item_view: createEvent(); break; } } @Override public void onItemClick(BottomSheet sheet, MenuItem menu, Object extras) { dismissSheet(sheet); actionEvent(menu, (Cursor) extras); } private void actionEvent(MenuItem menu, Cursor cr) { String is_done = cr.getString(cr.getColumnIndex("is_done")); final OValues values = new OValues(); values.put("_is_dirty", "false"); // to ignore update on server final int row_id = cr.getInt(cr.getColumnIndex(OColumn.ROW_ID)); values.put("is_done", (is_done.equals("0")) ? 1 : 0); String done_label = (is_done.equals("0")) ? "done" : "undone"; final ODataRow row = OCursorUtils.toDatarow(cr); convertRequestRecord = row; Bundle data = row.getPrimaryBundleData(); switch (menu.getItemId()) { // Event menus case R.id.menu_events_location: String location = cr.getString(cr.getColumnIndex("location")); if (location.equals("false")) { Toast.makeText(getActivity(), _s(R.string.label_no_location_found), Toast.LENGTH_LONG).show(); } else { IntentUtils.redirectToMap(getActivity(), location); } break; case R.id.menu_events_reschedule: IntentUtils.startActivity(getActivity(), EventDetail.class, data); break; // Opportunity menus case R.id.menu_opp_customer_location: String address = cr.getString(cr.getColumnIndex("street")) + " "; address += cr.getString(cr.getColumnIndex("street2")) + " "; address += cr.getString(cr.getColumnIndex("city")) + " "; address += cr.getString(cr.getColumnIndex("zip")); address = address.replaceAll("false", ""); if (TextUtils.isEmpty(address.trim())) { Toast.makeText(getActivity(), _s(R.string.label_no_location_found), Toast.LENGTH_LONG).show(); } else { IntentUtils.redirectToMap(getActivity(), address); } break; case R.id.menu_opp_call_customer: case R.id.menu_phonecall_call: int partner_id = cr.getInt(cr.getColumnIndex("partner_id")); if (partner_id != 0) { String contact = ResPartner.getContact(getActivity(), partner_id); if (contact != null && !contact.equals("false")) { IntentUtils.requestCall(getActivity(), contact); } else { Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); } } else { Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); } break; case R.id.menu_opp_lost: if (inNetwork()) { wonLost = "lost"; crmLead.markWonLost(wonLost, row, markDoneListener); } else { Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG).show(); } break; case R.id.menu_opp_won: if (inNetwork()) { wonLost = "won"; crmLead.markWonLost(wonLost, row, markDoneListener); } else { Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG).show(); } break; case R.id.menu_opp_reschedule: List choices = new ArrayList<>(); choices.add(OResource.string(getActivity(), R.string.label_opt_schedule_log_call)); choices.add(OResource.string(getActivity(), R.string.label_opt_schedule_meeting)); OChoiceDialog.get(getActivity()).withOptions(choices, -1) .show(new OChoiceDialog.OnChoiceSelectListener() { @Override public void choiceSelected(int position, String value) { int opp_id = row.getInt(OColumn.ROW_ID); switch (position) { case 0: Bundle extra = new Bundle(); extra.putInt("opp_id", opp_id); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); break; case 1: // Schedule meeting Bundle data = new Bundle(); data.putString(KEY_DATE, mFilterDate); data.putInt("opp_id", opp_id); IntentUtils.startActivity(getActivity(), EventDetail.class, data); break; } } }); break; case R.id.menu_phonecall_reschedule: choices = new ArrayList<>(); choices.add("Re-Schedule call"); choices.add("Schedule other call"); OChoiceDialog.get(getActivity()).withOptions(choices, -1) .show(new OChoiceDialog.OnChoiceSelectListener() { @Override public void choiceSelected(int position, String value) { switch (position) { case 0: // Re-Schedule IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, row.getPrimaryBundleData()); break; case 1: // Schedule other call Bundle extra = row.getPrimaryBundleData(); extra.putInt("call_id", row.getInt(OColumn.ROW_ID)); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); break; } } }); break; // All done menu case R.id.menu_phonecall_all_done: final CRMPhoneCalls phone_call = new CRMPhoneCalls(getActivity(), null); values.put("state", "done"); phone_call.update(row_id, values); getLoaderManager().restartLoader(0, null, this); SnackBar.get(getActivity()).text(_s(R.string.toast_phone_call_marked_done) + " " + done_label) .duration(SnackbarBuilder.SnackbarDuration.LENGTH_LONG) .withEventListener(this).show(); break; case R.id.menu_events_all_done: db().update(row_id, values); getLoaderManager().restartLoader(0, null, this); SnackBar.get(getActivity()).text(_s(R.string.label_event_marked) + " " + done_label) .duration(SnackbarBuilder.SnackbarDuration.LENGTH_LONG) .withEventListener(this).show(); break; case R.id.menu_lead_convert_to_quotation: if (inNetwork()) { Intent intent = new Intent(getActivity(), ConvertToQuotation.class); intent.putExtras(row.getPrimaryBundleData()); parent().startActivityForResult(intent, REQUEST_CONVERT_TO_QUOTATION_WIZARD); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; } } CRMLead.OnOperationSuccessListener markDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), StringUtils.capitalizeString(convertRequestRecord.getString("type")) + " " + _s(R.string.toast_marked) + " " + wonLost, Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; private void dismissSheet(final BottomSheet sheet) { new Handler().postDelayed(new Runnable() { @Override public void run() { sheet.dismiss(); } }, 100); } @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(CalendarEvent.AUTHORITY); // Syncing phone calls and sales order SyncUtils.get(getActivity(), db().getUser()).requestSync(CRMPhoneCalls.AUTHORITY); // Syncing only Opportunity from agenda Bundle syncData = new Bundle(); syncData.putBoolean(CRMLeads.KEY_IS_LEAD, false); SyncUtils.get(getActivity(), db().getUser()).requestSync(CRMLead.AUTHORITY, syncData); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG).show(); } } @Override public void onStatusChange(Boolean refreshing) { getLoaderManager().restartLoader(0, null, this); setSwipeRefreshing(refreshing); } @Override public void onShow(int height) { hideFab(); } @Override public void onDismiss(int height) { showFab(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.clear(); inflater.inflate(R.menu.menu_calendar_dashboard, menu); if (getActivity() != null) { MenuItem today = menu.findItem(R.id.menu_dashboard_goto_today); today.setIcon(TodayIcon.get(getActivity()).getIcon()); } setHasSearchView(this, menu, R.id.menu_search); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_dashboard_goto_today: odooCalendar.goToToday(); break; } return super.onOptionsItemSelected(item); } @Override public boolean onSearchViewTextChange(String newFilter) { mFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { // Nothing to do } private void createEvent() { Bundle data = new Bundle(); data.putString(KEY_DATE, mFilterDate); IntentUtils.startActivity(getActivity(), EventDetail.class, data); } @Override public void onOdooActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CONVERT_TO_QUOTATION_WIZARD && resultCode == Activity.RESULT_OK) { crmLead.createQuotation(convertRequestRecord, data.getStringExtra("partner_id"), data.getBooleanExtra("mark_won", false), createQuotationListener); } } CRMLead.OnOperationSuccessListener createQuotationListener = new CRMLead. OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), _s(R.string.label_quotation_created) + " " + convertRequestRecord.getString("name"), Toast.LENGTH_LONG).show(); parent().sync().requestSync(SaleOrder.AUTHORITY); } @Override public void OnCancelled() { } }; } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/EventDetail.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 5:46 PM */ package com.odoo.addons.calendar; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.text.format.DateUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.Toast; import com.odoo.addons.calendar.models.CalendarEvent; import com.odoo.addons.calendar.utils.CalendarUtils; import com.odoo.addons.calendar.utils.EventColorDialog; import com.odoo.addons.calendar.utils.ReminderDialog; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; import com.odoo.core.utils.reminder.ReminderReceiver; import com.odoo.core.utils.reminder.ReminderUtils; import com.odoo.R; import java.util.Date; import odoo.controls.OField; import odoo.controls.OForm; public class EventDetail extends ActionBarActivity implements View.OnClickListener, EventColorDialog.OnColorSelectListener, OField.IOnFieldValueChangeListener, ReminderDialog.OnReminderValueSelectListener { public static final String TAG = EventDetail.class.getSimpleName(); private ActionBar actionBar; public static final String KEY_RESCHEDULE = "key_reschedule"; private static final String KEY_EXTRA_EVENT_COLOR = "event_color"; private static final String KEY_COLOR_DATA = "color_data"; private String mEventColor = CalendarUtils.getBackgroundColors()[0]; private OForm eventForm; private Integer mEventColorCode = 0; private ReminderDialog.ReminderItem mReminder; private ODataRow color_data = null; private Boolean mAllDay = false; private View mView = null; private CalendarEvent calendarEvent; private int row_id = -1; private OField event_date_end, event_date_start, event_time_end, event_time_start, allDay; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.calendar_event_detail_form); OActionBarUtils.setActionBar(this, true); calendarEvent = new CalendarEvent(this, null); actionBar = getSupportActionBar(); actionBar.setHomeAsUpIndicator(R.drawable.ic_action_mark_undone); actionBar.setTitle(R.string.label_new_meeting); mView = findViewById(R.id.eventForm); event_date_end = (OField) findViewById(R.id.event_date_end); event_time_end = (OField) findViewById(R.id.event_end_time); event_date_start = (OField) findViewById(R.id.event_date_start); event_time_start = (OField) findViewById(R.id.event_start_time); allDay = (OField) findViewById(R.id.fieldAllDay); if (savedInstanceState != null) { mEventColor = savedInstanceState.getString(KEY_EXTRA_EVENT_COLOR); color_data = savedInstanceState.getParcelable(KEY_COLOR_DATA); colorSelected(color_data); } else { setThemeColor(mEventColor); } allDay.setOnValueChangeListener(this); ((OField) findViewById(R.id.event_date_start)).setOnValueChangeListener(this); ((OField) findViewById(R.id.event_start_time)).setOnValueChangeListener(this); // OnClicks findViewById(R.id.event_color).setOnClickListener(this); findViewById(R.id.reminderForEvent).setOnClickListener(this); Bundle extra = getIntent().getExtras(); eventForm = (OForm) mView; eventForm.setModel(calendarEvent.getModelName()); if (extra != null) { row_id = getIntent().getIntExtra(OColumn.ROW_ID, -1); if (row_id != -1) { findViewById(R.id.meetingDeleteLayout).setVisibility(View.VISIBLE); findViewById(R.id.meetingDeleteLayout).setOnClickListener(this); actionBar.setTitle(R.string.label_edit_meeting); ODataRow record = calendarEvent.browse(row_id); eventForm.initForm(record); allDay.setValue(record.getBoolean("allday")); String dateFormat = (record.getBoolean("allday")) ? ODateUtils.DEFAULT_DATE_FORMAT : ODateUtils.DEFAULT_FORMAT; event_date_start.setValue(ODateUtils.parseDate(record.getString("date_start"), dateFormat, ODateUtils.DEFAULT_DATE_FORMAT)); event_date_end.setValue(ODateUtils.parseDate(record.getString("date_end"), dateFormat, ODateUtils.DEFAULT_DATE_FORMAT)); event_time_start.setValue(ODateUtils.parseDate(record.getString("date_start"), dateFormat, ODateUtils.DEFAULT_TIME_FORMAT)); event_time_end.setValue(ODateUtils.parseDate(record.getString("date_end"), dateFormat, ODateUtils.DEFAULT_TIME_FORMAT)); colorSelected(CalendarUtils.getColorData(record.getInt("color_index"))); } else { ODataRow opp_data = new ODataRow(); if (extra.containsKey(CalendarDashboard.KEY_DATE)) { event_date_start.setValue(extra.getString(CalendarDashboard.KEY_DATE)); event_date_end.setValue(extra.getString(CalendarDashboard.KEY_DATE)); } OField opp_field = (OField) findViewById(R.id.opportunity_id); if (extra.containsKey("opp_id")) { opp_field.setVisibility(View.VISIBLE); opp_data.put("opportunity_id", extra.getInt("opp_id")); } eventForm.initForm(opp_data); opp_field.setEditable(false); } } else { eventForm.initForm(null); } String action = getIntent().getAction(); if (action != null && (action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_DONE) || action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_RE_SCHEDULE))) { ONotificationBuilder.cancelNotification(this, getIntent().getExtras(). getInt(OColumn.ROW_ID)); if (action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_DONE)) { int row_id = getIntent().getExtras().getInt(OColumn.ROW_ID); OValues values = new OValues(); values.put("is_done", 1); calendarEvent.update(row_id, values); Toast.makeText(this, R.string.toast_event_marked_done, Toast.LENGTH_LONG).show(); extra.remove(KEY_RESCHEDULE); } } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.event_color: CalendarUtils.colorDialog(this, mEventColor, this).show(); break; case R.id.reminderForEvent: ReminderDialog dialog = new ReminderDialog(this, (mAllDay) ? ReminderDialog.ReminderType.FullDayEvent : ReminderDialog.ReminderType.TimeBasedEvent); dialog.setOnReminderValueSelectListener(this); dialog.show(); break; case R.id.meetingDeleteLayout: OAlert.showConfirm(this, "Are you sure want to delete meeting ?", new OAlert.OnAlertConfirmListener() { @Override public void onConfirmChoiceSelect(OAlert.ConfirmType type) { switch (type) { case POSITIVE: calendarEvent.delete(row_id); Toast.makeText(EventDetail.this, "Meeting deleted", Toast.LENGTH_LONG).show(); finish(); break; case NEGATIVE: } } }); break; } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_EXTRA_EVENT_COLOR, mEventColor); outState.putParcelable(KEY_COLOR_DATA, color_data); } @Override public void colorSelected(ODataRow color_data) { if (color_data != null) { mEventColor = color_data.getString("code"); this.color_data = color_data; ImageView event_color_view = (ImageView) findViewById(R.id.event_color_view); event_color_view.setColorFilter(Color.parseColor(mEventColor)); OControls.setText(mView, R.id.event_color_label, color_data.getString("label")); mEventColorCode = color_data.getInt("index"); setThemeColor(mEventColor); } } private void setThemeColor(String color_code) { int color = Color.parseColor(color_code); actionBar.setBackgroundDrawable(new ColorDrawable(color)); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_calendar_detail, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; case R.id.menu_calendar_detail_save: OValues values = eventForm.getValues(); if (values != null) { createMeeting(values); } break; } return super.onOptionsItemSelected(item); } private void createMeeting(OValues values) { OValues meeting = new OValues(); meeting.put("name", values.get("name")); meeting.put("allday", values.get("allday")); meeting.put("location", values.get("location")); meeting.put("description", values.get("description")); meeting.put("class", values.get("class")); meeting.put("color_index", mEventColorCode); if (values.contains("opportunity_id")) { meeting.put("opportunity_id", values.get("opportunity_id")); } if (calendarEvent.getColumn("date") == null) { //v7+ if (values.getBoolean("allday")) { meeting.put("start_date", values.get("event_date_start")); meeting.put("stop_date", values.get("event_date_end")); meeting.put("date_start", meeting.get("start_date")); meeting.put("date_end", meeting.get("stop_date")); } else { String start_datetime = values.get("event_date_start") + " " + values.get("event_time_start"); String stop_datetime = values.get("event_date_end") + " " + values.get("event_time_end"); meeting.put("start_datetime", start_datetime); meeting.put("stop_datetime", stop_datetime); meeting.put("date_start", meeting.get("start_datetime")); meeting.put("date_end", meeting.get("stop_datetime")); } } else { //v7 String start_datetime = values.get("event_date_start") + " " + values.get("event_time_start"); String stop_datetime = values.get("event_date_end") + " " + values.get("event_time_end"); meeting.put("date", start_datetime); meeting.put("date_deadline", stop_datetime); meeting.put("date_start", meeting.get("date")); meeting.put("date_end", meeting.get("date_deadline")); } String format = (meeting.getBoolean("allday")) ? ODateUtils.DEFAULT_DATE_FORMAT : ODateUtils.DEFAULT_FORMAT; Date date_start = ODateUtils.createDateObject(meeting.getString("date_start"), format, false); Date date_end = ODateUtils.createDateObject(meeting.getString("date_end"), format, false); if (date_end.compareTo(date_start) < 0) { OAlert.showWarning(this, OResource.string(this, R.string.error_end_date_small_than_start_date)); } else { Date now = new Date(); Date reminderDate = null; int diff = 99; if (meeting.getBoolean("allday")) { if (DateUtils.isToday(date_start.getTime())) { diff = 0; } } if (diff == 0 || now.compareTo(date_start) <= 0) { meeting.put("has_reminder", "true"); if (mReminder == null) { mReminder = ReminderDialog.getDefault(this, meeting.getBoolean("allday")); } reminderDate = ReminderDialog.getReminderDateTime(meeting.getString("date_start"), meeting.getBoolean("allday"), mReminder); if (reminderDate != null) { meeting.put("reminder_datetime", ODateUtils.getDate(reminderDate, ODateUtils.DEFAULT_FORMAT)); } } if (row_id != -1) { Log.i(TAG, "Event updated"); calendarEvent.update(row_id, meeting); } else { Log.i(TAG, "Event created"); row_id = calendarEvent.insert(meeting); } Bundle extra = new Bundle(); extra.putInt(OColumn.ROW_ID, row_id); extra.putString(ReminderUtils.KEY_REMINDER_TYPE, "event"); if (reminderDate != null) { if (ReminderUtils.get(getApplicationContext()).resetReminder(reminderDate, extra)) { Log.i(TAG, "Reminder added."); } } finish(); } } public void onCheckedChanged(boolean isChecked) { mAllDay = isChecked; if (isChecked) { OControls.setText(mView, R.id.reminderTypeName, String.format(OResource.string(this, R.string.on_the_day_at), "9 AM")); findViewById(R.id.event_start_time).setVisibility(View.GONE); findViewById(R.id.event_end_time).setVisibility(View.GONE); } else { OControls.setText(mView, R.id.reminderTypeName, OResource.string(this, R.string.at_the_time_of_event)); findViewById(R.id.event_start_time).setVisibility(View.VISIBLE); findViewById(R.id.event_end_time).setVisibility(View.VISIBLE); } } @Override public void onFieldValueChange(OField field, Object value) { if (field.getFieldName().equals("allday")) onCheckedChanged(Boolean.parseBoolean(value.toString())); if (field.getFieldName().equals("event_date_start")) { event_date_end.setValue(value); } if (field.getFieldName().equals("event_time_start")) { event_time_end.setValue(value); } } @Override public void onReminderItemSelect(ReminderDialog.ReminderItem value) { OControls.setText(mView, R.id.reminderTypeName, value.getTitle()); mReminder = value; } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/models/CalendarEvent.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 10:26 AM */ package com.odoo.addons.calendar.models; import android.content.Context; import android.net.Uri; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODate; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OSelection; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import org.json.JSONArray; import odoo.ODomain; public class CalendarEvent extends OModel { public static final String TAG = CalendarEvent.class.getSimpleName(); public static final String AUTHORITY = "com.odoo.core.crm.provider.content.sync.calendar_event"; private Context mContext; OColumn name = new OColumn("Meeting Name", OVarchar.class).setSize(64).setRequired(); @Odoo.api.v7 OColumn date = new OColumn("Start Date", ODateTime.class); @Odoo.api.v8 @Odoo.api.v9alpha OColumn start_date = new OColumn("Start Date", ODate.class); @Odoo.api.v8 @Odoo.api.v9alpha OColumn start_datetime = new OColumn("Start Date", ODateTime.class); @Odoo.api.v7 OColumn date_deadline = new OColumn("Dead Line", ODateTime.class); @Odoo.api.v8 @Odoo.api.v9alpha OColumn stop_date = new OColumn("Stop Date", ODate.class); @Odoo.api.v8 @Odoo.api.v9alpha OColumn stop_datetime = new OColumn("Stop Date", ODateTime.class); OColumn duration = new OColumn("Duration", OVarchar.class).setSize(32); OColumn allday = new OColumn("All Day", OBoolean.class); OColumn description = new OColumn("Description", OText.class); OColumn location = new OColumn("Location", OText.class); OColumn _class = new OColumn("Privacy", OSelection.class) .addSelection("public", "Public") .addSelection("private", "Private") .addSelection("confidential", "Public for Employees") .setDefaultValue("public"); @Odoo.Functional(store = true, depends = {"date", "start_date", "start_datetime"}, method = "storeStartDate") OColumn date_start = new OColumn("Start Date", ODateTime.class) .setLocalColumn(); @Odoo.Functional(store = true, depends = {"date_deadline", "stop_date", "stop_datetime"}, method = "storeStopDate") OColumn date_end = new OColumn("Start Date", ODateTime.class) .setLocalColumn(); OColumn data_type = new OColumn("Data type", OVarchar.class).setSize(34) .setLocalColumn().setDefaultValue("event"); OColumn is_done = new OColumn("Mark as Done", OInteger.class) .setLocalColumn().setDefaultValue("0"); OColumn color_index = new OColumn("Color index", OInteger.class).setSize(5) .setLocalColumn().setDefaultValue(0); OColumn has_reminder = new OColumn("Has reminder", OBoolean.class).setLocalColumn() .setDefaultValue("false"); OColumn reminder_datetime = new OColumn("Reminder type", ODateTime.class) .setDefaultValue("false").setLocalColumn(); OColumn user_id = new OColumn("Owner", ResUsers.class, OColumn.RelationType.ManyToOne); OColumn partner_ids = new OColumn("Attendees", ResPartner.class, OColumn.RelationType.ManyToMany); // PhoneCalls link OColumn phonecall_id = new OColumn("Phone calls", CRMPhoneCalls.class, OColumn.RelationType.ManyToOne); //Opportunities id OColumn opportunity_id = new OColumn("Opportunities", CRMLead.class, OColumn.RelationType.ManyToOne) .addDomain("type", "=", "opportunity"); public CalendarEvent(Context context, OUser user) { super(context, "calendar.event", user); mContext = context; if (getUser() != null && getUser().getVersion_number() != null) { int version = getUser().getVersion_number(); if (version <= 7) { setModelName("crm.meeting"); } } // Setting 'class' variable name not allowed in java. _class.setName("class"); setHasMailChatter(true); } @Override public ODomain defaultDomain() { ODomain domain = new ODomain(); if (getOdooVersion().getVersion_number() <= 7) { domain.add("|"); domain.add("user_id", "=", getUser().getUser_id()); domain.add("partner_ids", "in", new JSONArray().put(getUser().getPartner_id())); } else { domain.add("partner_ids", "in", new JSONArray().put(getUser().getPartner_id())); } domain.add("recurrency", "=", false); return domain; } @Override public Uri uri() { return buildURI(AUTHORITY); } public Uri agendaUri() { return uri().buildUpon().appendPath("full_agenda").build(); } public String storeStartDate(OValues value) { if (value.contains("date")) { return value.getString("date"); } if (!value.getString("start_date").equals("false")) return value.getString("start_date"); return value.getString("start_datetime"); } public String storeStopDate(OValues value) { if (value.contains("date_deadline")) { return value.getString("date_deadline"); } if (!value.getString("stop_date").equals("false")) return value.getString("stop_date"); return value.getString("stop_datetime"); } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/providers/CalendarSyncProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 10:25 AM */ package com.odoo.addons.calendar.providers; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.net.Uri; import com.odoo.addons.calendar.models.CalendarEvent; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.provider.BaseModelProvider; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class CalendarSyncProvider extends BaseModelProvider { public static final String TAG = CalendarSyncProvider.class.getSimpleName(); public static final int FULL_AGENDA = 114; @Override public boolean onCreate() { String path = new CalendarEvent(getContext(), null).getModelName().toLowerCase(Locale.getDefault()); matcher.addURI(authority(), path + "/full_agenda", FULL_AGENDA); return super.onCreate(); } @Override public void setModel(Uri uri) { super.setModel(uri); mModel = new CalendarEvent(getContext(), getUser(uri)); } @Override public Cursor query(Uri uri, String[] base_projection, String selection, String[] selectionArgs, String sortOrder) { int match = matcher.match(uri); CalendarEvent events = new CalendarEvent(getContext(), null); if (match != FULL_AGENDA) { return super.query(uri, base_projection, selection, selectionArgs, sortOrder); } String date_start = selectionArgs[0]; String filter = null; if (selectionArgs.length > 1) filter = selectionArgs[1]; String where; List args = new ArrayList<>(); // Getting events MatrixCursor event_separator = new MatrixCursor( new String[]{OColumn.ROW_ID, "data_type", "name"}); // Comparing date_start and date_end where = "(date(date_start) <= ? and date(date_end) >= ? )"; args.add(date_start); args.add(date_start); if (filter != null) { where += " and name like ?"; args.add(filter); } Cursor eventCR = getContext().getContentResolver().query(events.uri(), base_projection, where, args.toArray(new String[args.size()]), "is_done, date_start"); if (eventCR.getCount() > 0) event_separator.addRow(new String[]{"0", "separator", "Meetings"}); // Getting phone calls CRMPhoneCalls phoneCalls = new CRMPhoneCalls(getContext(), null); MatrixCursor phone_calls_separator = new MatrixCursor( new String[]{OColumn.ROW_ID, "data_type", "name"}); // Comparing date where = "date(date) >= ? and date(date) <= ? and (state = ? or state = ?)"; args.clear(); args.add(date_start); args.add(date_start); args.add("open"); args.add("pending"); if (filter != null) { where += " and (name like ? or description like ?)"; args.add(filter); args.add(filter); } Cursor phoneCallsCR = getContext().getContentResolver().query(phoneCalls.uri(), base_projection, where, args.toArray(new String[args.size()]), "is_done , date"); if (phoneCallsCR.getCount() > 0) phone_calls_separator.addRow(new String[]{"0", "separator", "Phone Calls"}); // Getting opportunity CRMLead opportunity = new CRMLead(getContext(), null); MatrixCursor opportunity_separator = new MatrixCursor( new String[]{OColumn.ROW_ID, "data_type", "name"}); // Comparing with create_date and date_action and type where = "(date(date_deadline) >= ? and date(date_deadline) <= ? or date(date_action) >= ? " + "and date(date_action) <= ?) and type = ?"; args.clear(); args.add(date_start); args.add(date_start); args.add(date_start); args.add(date_start); args.add("opportunity"); if (filter != null) { where += " and (name like ? or description like ?)"; args.add(filter); args.add(filter); } Cursor opportunityCR = getContext().getContentResolver().query(opportunity.uri(), base_projection, where, args.toArray(new String[args.size()]), sortOrder); if (opportunityCR.getCount() > 0) opportunity_separator.addRow(new String[]{"0", "separator", "Opportunities"}); MergeCursor mergedData = new MergeCursor(new Cursor[]{ event_separator, eventCR, phone_calls_separator, phoneCallsCR, opportunity_separator, opportunityCR}); return mergedData; } @Override public String authority() { return CalendarEvent.AUTHORITY; } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/services/CalendarSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 3:57 PM */ package com.odoo.addons.calendar.services; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import android.util.Log; import com.odoo.addons.calendar.models.CalendarEvent; import com.odoo.core.account.BaseSettings; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.service.ISyncFinishListener; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.service.OSyncService; import com.odoo.core.support.OUser; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.reminder.ReminderUtils; import java.util.Date; import java.util.List; public class CalendarSyncService extends OSyncService implements ISyncFinishListener { public static final String TAG = CalendarSyncService.class.getSimpleName(); public static final int SYNC_SLEEP_DELAY = 2000; @Override public OSyncAdapter getSyncAdapter(OSyncService service, Context context) { return new OSyncAdapter(context, CalendarEvent.class, service, true); } @Override public void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user) { if (adapter.getModel().getModelName().equals("calendar.event")) { adapter.onSyncFinish(this).syncDataLimit(50); } } @Override public OSyncAdapter performNextSync(OUser user, SyncResult syncResult) { // Setting reminders to events CalendarEvent event = new CalendarEvent(getApplicationContext(), null); List rows = event.select(); int count = 0; for (ODataRow row : rows) { if (row.getBoolean("allday")) { String defaultTime = BaseSettings.getDayStartTime(getApplicationContext()); row.put("date_start", row.getString("date_start") + " " + defaultTime); } Date start_date = ODateUtils.createDateObject(row.getString("date_start"), ODateUtils.DEFAULT_FORMAT, false); Date now = new Date(); if (now.compareTo(start_date) < 0) { Bundle extra = row.getPrimaryBundleData(); extra.putString(ReminderUtils.KEY_REMINDER_TYPE, "event"); if (ReminderUtils.get(getApplicationContext()).resetReminder(start_date, extra)) { OValues values = new OValues(); values.put("_is_dirty", "false"); values.put("has_reminder", "true"); event.update(row.getInt(OColumn.ROW_ID), values); count++; } } } Log.i(TAG, count + " reminder updated"); return null; } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/utils/CalendarUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 12/1/15 11:12 AM */ package com.odoo.addons.calendar.utils; import android.app.AlertDialog; import android.content.Context; import android.graphics.Color; import com.odoo.core.orm.ODataRow; public class CalendarUtils { public static final String TAG = CalendarUtils.class.getSimpleName(); private static String[] background_colors = {"#a24689", "#d40000", "#f24f1d", "#f5be27", "#0a7d40", "#35b579", "#029ce3", "#405ea8", "#7986c9", "#8b23a8", "#e37971", "#616161"}; private static String[] color_label = {"Default Color", "Tomato", "Tangerine", "Banana", "Basil", "Sage", "Peacock", "Blueberry", "Lavender", "Grape", "Flamingo", "Graphite"}; public static String[] getBackgroundColors() { return background_colors; } public static String[] getColorLabels() { return color_label; } public static int getBackgroundColor(int color_number) { if (color_number < background_colors.length) { return Color.parseColor(background_colors[color_number]); } return Color.parseColor("#ffffff"); } public static String getColorLabel(int color_number) { if (color_number < color_label.length) { return color_label[color_number]; } return "Default Color"; } public static ODataRow getColorData(int index) { ODataRow clr = new ODataRow(); clr.put("index", index); clr.put("code", background_colors[index]); clr.put("label", color_label[index]); return clr; } public static AlertDialog colorDialog(Context context, String selected, EventColorDialog.OnColorSelectListener listener) { EventColorDialog dialog = new EventColorDialog(context, selected, listener); return dialog.build(); } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/utils/EventColorDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 12/1/15 11:13 AM */ package com.odoo.addons.calendar.utils; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.Context; import android.graphics.Color; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.core.orm.ODataRow; import com.odoo.R; import java.util.ArrayList; import java.util.List; public class EventColorDialog implements AdapterView.OnItemClickListener { public static final String TAG = EventColorDialog.class.getSimpleName(); private Builder builder = null; private Context mContext; private ArrayAdapter mAdapter; private List colors = new ArrayList(); private String selectedColor; private OnColorSelectListener mOnColorSelectListener; private AlertDialog alertDialog; public EventColorDialog(Context context, String selected_color, OnColorSelectListener listener) { mContext = context; selectedColor = selected_color; mOnColorSelectListener = listener; String[] bg_colors = CalendarUtils.getBackgroundColors(); String[] color_labels = CalendarUtils.getColorLabels(); for (int i = 0; i < bg_colors.length; i++) { ODataRow clr = new ODataRow(); clr.put("index", i); clr.put("code", bg_colors[i]); clr.put("label", color_labels[i]); colors.add(clr); } } public AlertDialog build() { builder = new Builder(mContext); builder.setView(getColorGrid()); alertDialog = builder.create(); return alertDialog; } private View getColorGrid() { LinearLayout layout = (LinearLayout) LayoutInflater.from(mContext) .inflate(R.layout.event_color_grid, null, false); initGrid((GridView) layout.findViewById(R.id.event_grid)); return layout; } private void initGrid(GridView view) { mAdapter = new ArrayAdapter(mContext, R.layout.event_color_chooser_item, colors) { @Override public View getView(int position, View convertView, ViewGroup parent) { ODataRow row = colors.get(position); View view = convertView; if (view == null) { view = LayoutInflater.from(mContext).inflate( R.layout.event_color_chooser_item, parent, false); } TextView color_label = (TextView) view .findViewById(R.id.color_label); color_label.setText(row.getString("label")); ImageView color_view = (ImageView) view .findViewById(R.id.color_view); color_view.setColorFilter(Color.parseColor(row .getString("code"))); boolean mSelected = (selectedColor.equals(row.getString("code"))); if (mSelected) { color_label.setTextColor(mContext.getResources().getColor( R.color.theme_secondary_dark)); view.findViewById(R.id.color_view_selected).setVisibility( View.VISIBLE); } else { color_label.setTextColor(Color.parseColor("#414141")); view.findViewById(R.id.color_view_selected).setVisibility( View.GONE); } return view; } }; view.setAdapter(mAdapter); view.setOnItemClickListener(this); } public interface OnColorSelectListener { public void colorSelected(ODataRow color_data); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { if (mOnColorSelectListener != null) { mOnColorSelectListener.colorSelected(colors.get(position)); } alertDialog.dismiss(); } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/utils/ReminderDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 12/1/15 3:47 PM */ package com.odoo.addons.calendar.utils; import android.app.AlertDialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ListView; import com.odoo.core.account.BaseSettings; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.R; import java.util.ArrayList; import java.util.Date; import java.util.List; public class ReminderDialog implements AdapterView.OnItemClickListener { public static final String TAG = ReminderDialog.class.getSimpleName(); private AlertDialog.Builder mBuilder; private AlertDialog mDialog; private Context mContext; private ReminderType mType; private OListAdapter mAdapter; private OnReminderValueSelectListener mOnReminderValueSelectListener = null; private List reminderTypes = new ArrayList<>(); private OPreferenceManager mPref; public enum ReminderType { FullDayEvent, TimeBasedEvent } public ReminderDialog(Context context, ReminderType type) { mContext = context; reminderTypes.clear(); mPref = new OPreferenceManager(mContext); mType = type; List reminders = new ArrayList<>(); reminders.add(0, new ReminderItem(0, OResource.string(mContext, R.string.no_notification), "false")); switch (mType) { case FullDayEvent: // At your working day start time String workingStartTime = ODateUtils.parseDate(mPref.getString(BaseSettings.KEY_LEAD_WORK_DAY_START_TIME, OResource.string(mContext, R.string.default_day_start_time)), ODateUtils.DEFAULT_TIME_FORMAT, ODateUtils.DEFAULT_TIME_FORMAT); reminders.add(1, new ReminderItem(1, OResource.string(mContext, R.string.on_your_working_day_start_time), workingStartTime)); // At 9 AM reminders.add(2, new ReminderItem(2, String.format(OResource.string(mContext, R.string.on_the_day_at), "9 AM"), "9:00 AM")); // before day at 11:30 PM reminders.add(3, new ReminderItem(3, String.format(OResource.string(mContext, R.string.day_before_at), "11:30 PM"), "11:30 PM")); // before day at 5:00 PM reminders.add(4, new ReminderItem(4, String.format(OResource.string(mContext, R.string.day_before_at), "5 PM"), "5:00 PM")); break; case TimeBasedEvent: // At the time of event reminders.add(new ReminderItem(1, OResource.string(mContext, R.string.at_the_time_of_event), 1)); // 30 min before reminders.add(new ReminderItem(2, String.format(OResource.string(mContext, R.string.minutes_before), "30"), 30)); // 10 min before reminders.add(new ReminderItem(3, String.format(OResource.string(mContext, R.string.minutes_before), "10"), 10)); break; } //TODO Custom reminder: reminders.add(new ReminderItem(4, OResource.string(mContext, R.string.custom), -1)); reminderTypes.addAll(reminders); } public List getReminderTypes() { return reminderTypes; } public void show() { mBuilder = new AlertDialog.Builder(mContext); mBuilder.setView(generateView()); mDialog = mBuilder.create(); mDialog.show(); } public static ReminderItem getDefault(Context context, boolean allDay) { ReminderDialog dialog = new ReminderDialog(context, (allDay) ? ReminderType.FullDayEvent : ReminderType.TimeBasedEvent); return (ReminderItem) dialog.getReminderTypes().get(1); } private View generateView() { AbsListView.LayoutParams param = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); ListView list = new ListView(mContext); list.setLayoutParams(param); mAdapter = new OListAdapter(mContext, R.layout.reminder_item_view, reminderTypes) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(mContext).inflate(getResource(), parent, false); ReminderItem item = (ReminderItem) getItem(position); OControls.setText(convertView, R.id.reminderTitle, item.getTitle()); return convertView; } }; list.setAdapter(mAdapter); list.setOnItemClickListener(this); return list; } public static Date getReminderDateTime(String eventDateTime, Boolean allDay, ReminderItem item) { String format = (allDay) ? ODateUtils.DEFAULT_DATE_FORMAT : ODateUtils.DEFAULT_FORMAT; Date eventDate = ODateUtils.createDateObject(eventDateTime, format, false); if (item.getRequest_code() != 0) { Date dayBefore = ODateUtils.getDateDayBefore(eventDate, 1); if (allDay) { switch (item.getRequest_code()) { case 1: return ODateUtils.createDateObject(eventDateTime + " " + item.getValue(), ODateUtils.DEFAULT_FORMAT, true); case 2: return ODateUtils.setDateTime(eventDate, 9, 0, 0); case 3: return ODateUtils.setDateTime(dayBefore, 23, 30, 0); case 4: return ODateUtils.setDateTime(dayBefore, 17, 0, 0); } } else { switch (item.getRequest_code()) { case 1: return eventDate; case 2: case 3: return ODateUtils.getDateMinuteBefore(eventDate, (Integer) item.getValue()); } } } return null; } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { ReminderItem item = (ReminderItem) mAdapter.getItem(position); switch (item.getRequest_code()) { case 0: case 1: case 2: case 3: if (mOnReminderValueSelectListener != null) { mOnReminderValueSelectListener.onReminderItemSelect(item); } mDialog.dismiss(); break; case 4: // TODO: Open custom dialog for reminder config break; } } public void setOnReminderValueSelectListener(OnReminderValueSelectListener listener) { mOnReminderValueSelectListener = listener; } public interface OnReminderValueSelectListener { public void onReminderItemSelect(ReminderItem value); } public static class ReminderItem { int request_code; String title; Object value; public ReminderItem(int request_code, String title, Object value) { this.request_code = request_code; this.title = title; this.value = value; } public int getRequest_code() { return request_code; } public void setRequest_code(int request_code) { this.request_code = request_code; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } } ================================================ FILE: app/src/main/java/com/odoo/addons/calendar/utils/TodayIcon.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 5:27 PM */ package com.odoo.addons.calendar.utils; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.text.TextPaint; import com.odoo.R; import java.util.Calendar; import java.util.Locale; public class TodayIcon { public static final String TAG = TodayIcon.class.getSimpleName(); private Context mContext; private Resources mRes; private TextPaint mPaint = new TextPaint(); private Rect mBounds = new Rect(); private Canvas mCanvas = new Canvas(); private Bitmap mDefaultIcon; public TodayIcon(Context context) { mContext = context; mRes = mContext.getResources(); } private int date() { return Calendar.getInstance(Locale.getDefault()).get( Calendar.DAY_OF_MONTH); } public static TodayIcon get(Context context) { return new TodayIcon(context); } public Drawable getIcon() { mPaint.setTypeface(Typeface.create("sans-serif", Typeface.BOLD)); mPaint.setColor(Color.WHITE); mPaint.setTextAlign(Align.CENTER); mPaint.setAntiAlias(true); mPaint.setTextSize(mRes.getDimension(R.dimen.text_size_xxsmall)); mDefaultIcon = BitmapFactory.decodeResource(mRes, R.drawable.ic_action_goto_today); Bitmap bmp = generate(mDefaultIcon.getWidth(), mDefaultIcon.getHeight()); return new BitmapDrawable(mRes, bmp); } private Bitmap generate(int width, int height) { final String date = (date() < 10) ? "0" + date() + "" : date() + ""; final Canvas c = mCanvas; final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); c.setBitmap(bitmap); c.drawBitmap(mDefaultIcon, 0, 0, null); c.drawColor(Color.TRANSPARENT); mPaint.getTextBounds(date, 0, 2, mBounds); c.drawText(date, 0, 2, width / 2, 5 + height / 2 + (mBounds.bottom - mBounds.top) / 2, mPaint); return bitmap; } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/CRMDetail.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 3:40 PM */ package com.odoo.addons.crm; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.odoo.App; import com.odoo.addons.crm.models.CRMCaseStage; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.customers.Customers; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.base.addons.res.ResCompany; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.sync.SyncUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import com.odoo.R; import java.util.ArrayList; import java.util.List; import odoo.controls.OForm; public class CRMDetail extends ActionBarActivity { public static final String TAG = CRMDetail.class.getSimpleName(); public static final int REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD = 1223; public static final int REQUEST_CONVERT_TO_QUOTATION_WIZARD = 1224; private Bundle extra; private OForm mForm; private ODataRow record; private CRMLead crmLead; private ActionBar actionBar; private Menu menu; private String wonLost = "won"; private String type = "lead"; private TextView currency_symbol; private int stage_id = -1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crm_detail); OActionBarUtils.setActionBar(this, true); actionBar = getSupportActionBar(); crmLead = new CRMLead(this, null); extra = getIntent().getExtras(); init(); } private void init() { mForm = (OForm) findViewById(R.id.crmLeadForm); currency_symbol = (TextView) findViewById(R.id.currency_symbol); if (!extra.containsKey(OColumn.ROW_ID)) { if (extra.getString("type").equals(Customers.Type.Opportunities.toString())) { type = "opportunity"; findViewById(R.id.opportunity_controls).setVisibility(View.VISIBLE); } if (extra.containsKey("stage_id")) { stage_id = extra.getInt("stage_id"); } mForm.initForm(null); actionBar.setTitle(R.string.label_tag_new); actionBar.setHomeAsUpIndicator(R.drawable.ic_action_navigation_close); ODataRow currency = ResCompany.getCurrency(this); if (currency != null) { currency_symbol.setText(currency.getString("symbol")); } } else { initFormValues(); } mForm.setEditable(true); } private void initFormValues() { record = crmLead.browse(extra.getInt(OColumn.ROW_ID)); if (record == null) { finish(); } ODataRow currency = record.getM2ORecord("company_currency").browse(); if (currency != null) { currency_symbol.setText(currency.getString("symbol")); } if (!record.getString("type").equals("lead")) { actionBar.setTitle(R.string.label_opportunity); type = "opportunity"; findViewById(R.id.opportunity_controls).setVisibility(View.VISIBLE); } else { actionBar.setTitle(R.string.label_lead); } mForm.initForm(record); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_lead_detail, menu); this.menu = menu; toggleMenu(); return true; } private void toggleMenu() { if (!extra.containsKey(OColumn.ROW_ID)) { menu.findItem(R.id.menu_lead_detail_more).setVisible(false); } else { initFormValues(); menu.findItem(R.id.menu_lead_detail_more).setVisible(true); if (record.getString("type").equals(CRMLead.KEY_LEAD)) { menu.findItem(R.id.menu_lead_convert_to_quotation).setVisible(false); menu.findItem(R.id.menu_mark_won).setVisible(false); } else if (record.getString("type").equals(crmLead.KEY_OPPORTUNITY)) { menu.findItem(R.id.menu_lead_convert_to_opportunity).setVisible(false); } } menu.findItem(R.id.menu_lead_save).setVisible(true); actionBar.setHomeAsUpIndicator(R.drawable.ic_action_navigation_close); } @Override public boolean onOptionsItemSelected(MenuItem item) { App app = (App) getApplicationContext(); switch (item.getItemId()) { case android.R.id.home: finish(); break; case R.id.menu_lead_save: OValues values = mForm.getValues(); if (stage_id != OModel.INVALID_ROW_ID) { values.put("stage_id", stage_id); } if (values != null) { values.put("type", type); int row_id; if (record != null) { crmLead.update(record.getInt(OColumn.ROW_ID), values); row_id = record.getInt(OColumn.ROW_ID); } else { values.put("company_id", ResCompany.myId(this)); values.put("company_currency", ResCompany.myCurrency(this)); values.put("create_date", ODateUtils.getUTCDate()); values.put("user_id", ResUsers.myId(this)); CRMCaseStage stages = new CRMCaseStage(this, null); ODataRow row; if (!values.contains("stage_id")) { row = stages.browse(new String[]{"name"}, "name = ?", new String[]{"New"}); } else { row = stages.browse(stage_id); } if (row != null) { values.put("stage_id", row.getInt(OColumn.ROW_ID)); values.put("stage_name", row.getString("name")); } values.put("display_name", values.getString("partner_name")); values.put("assignee_name", crmLead.getUser().getName()); row_id = crmLead.insert(values); } crmLead.setReminder(row_id); finish(); } break; case R.id.menu_lead_convert_to_opportunity: if (record.getInt("id") == 0) { OAlert.showWarning(this, OResource.string(this, R.string.label_sync_warning)); } else { if (app.inNetwork()) { int count = crmLead.count("id != ? and partner_id = ? and " + OColumn.ROW_ID + " != ?" , new String[]{ "0", record.getInt("partner_id") + "", record.getString(OColumn.ROW_ID) }); if (count > 0) { Intent intent = new Intent(this, ConvertToOpportunityWizard.class); intent.putExtras(record.getPrimaryBundleData()); startActivityForResult(intent, REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD); } else { crmLead.convertToOpportunity(record, new ArrayList(), convertDoneListener); } } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } } break; case R.id.menu_mark_won: if (app.inNetwork()) { crmLead.markWonLost(wonLost, record, markDoneListener); } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_mark_lost: wonLost = "lost"; if (app.inNetwork()) { crmLead.markWonLost(wonLost, record, markDoneListener); } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_convert_to_quotation: if (app.inNetwork()) { Intent intent = new Intent(this, ConvertToQuotation.class); intent.putExtras(record.getPrimaryBundleData()); startActivityForResult(intent, REQUEST_CONVERT_TO_QUOTATION_WIZARD); } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; } return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD && resultCode == RESULT_OK) { List ids = data.getIntegerArrayListExtra(ConvertToOpportunityWizard.KEY_LEADS_IDS); crmLead.convertToOpportunity(record, ids, convertDoneListener); } if (requestCode == REQUEST_CONVERT_TO_QUOTATION_WIZARD && resultCode == Activity.RESULT_OK) { crmLead.createQuotation(record, data.getStringExtra("partner_id"), data.getBooleanExtra("mark_won", false), createQuotationListener); } } CRMLead.OnOperationSuccessListener createQuotationListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(CRMDetail.this, OResource.string(CRMDetail.this, R.string.label_quotation_created) + " " + record.getString("name"), Toast.LENGTH_LONG).show(); SyncUtils sync = new SyncUtils(CRMDetail.this, crmLead.getUser()); sync.requestSync(SaleOrder.AUTHORITY); } @Override public void OnCancelled() { } }; CRMLead.OnOperationSuccessListener markDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(CRMDetail.this, StringUtils.capitalizeString(record.getString("type")) + " marked " + wonLost, Toast.LENGTH_LONG).show(); finish(); } @Override public void OnCancelled() { } }; CRMLead.OnOperationSuccessListener convertDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(CRMDetail.this, R.string.label_convert_to_opportunity, Toast.LENGTH_LONG).show(); Intent intent = new Intent(CRMDetail.this, CRMDetail.class); intent.putExtra(OColumn.ROW_ID, record.getInt(OColumn.ROW_ID)); startActivity(intent); finish(); } @Override public void OnCancelled() { } }; } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/CRMLeads.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:24 AM */ package com.odoo.addons.crm; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.customers.Customers; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import com.odoo.core.utils.sys.IOnActivityResultListener; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.R; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import java.util.ArrayList; import java.util.List; public class CRMLeads extends BaseFragment implements OCursorListAdapter.OnViewBindListener, LoaderManager.LoaderCallbacks, SwipeRefreshLayout.OnRefreshListener, ISyncStatusObserverListener, OCursorListAdapter.BeforeBindUpdateData, IOnSearchViewChangeListener, View.OnClickListener, IOnItemClickListener, BottomSheetListeners.OnSheetItemClickListener, BottomSheetListeners.OnSheetActionClickListener, IOnBackPressListener, IOnActivityResultListener { public static final String TAG = CRMLeads.class.getSimpleName(); public static final String KEY_MENU = "key_menu_item"; public static final int REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD = 223; public static final int REQUEST_CONVERT_TO_QUOTATION_WIZARD = 224; public static final String KEY_IS_LEAD = "key_is_lead"; private View mView; private int mLocal_id = 0; private ListView mList; private OCursorListAdapter mAdapter; private BottomSheet mSheet = null; private String mFilter = null; private String wonLost = "won"; private boolean syncRequested = false; // Customer's data filter private boolean filter_customer_data = false; private int customer_id = -1; private ODataRow convertRequestRecord = null; private Bundle syncBundle = new Bundle(); public enum Type { Leads, Opportunities } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setHasOptionsMenu(true); return inflater.inflate(R.layout.common_listview, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; parent().setOnBackPressListener(this); parent().setOnActivityResultListener(this); Bundle extra = getArguments(); if (extra != null && extra.containsKey(Customers.KEY_FILTER_REQUEST)) { filter_customer_data = true; customer_id = extra.getInt(Customers.KEY_CUSTOMER_ID); mView.findViewById(R.id.customer_filterContainer).setVisibility(View.VISIBLE); OControls.setText(mView, R.id.customer_name, extra.getString("name")); mView.findViewById(R.id.cancel_filter).setOnClickListener(this); } setHasSyncStatusObserver(TAG, this, db()); initAdapter(); } private void initAdapter() { mList = (ListView) mView.findViewById(R.id.listview); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.crm_item); mAdapter.setOnViewBindListener(this); mList.setAdapter(mAdapter); setHasFloatingButton(mView, R.id.fabButton, mList, this); mAdapter.handleItemClickListener(mList, this); getLoaderManager().initLoader(0, null, this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.cancel_filter: getActivity().getSupportFragmentManager().popBackStack(); break; case R.id.fabButton: Bundle type = new Bundle(); type.putString("type", Type.Leads.toString()); IntentUtils.startActivity(getActivity(), CRMDetail.class, type); break; } } @Override public ODataRow updateDataRow(Cursor cr) { return db().browse(new String[]{"stage_id"}, cr.getInt(cr.getColumnIndex(OColumn.ROW_ID))); } @Override public Loader onCreateLoader(int id, Bundle data) { String where = " type = ?"; String[] whereArgs; List args = new ArrayList<>(); args.add("lead"); if (mFilter != null) { where += " and (name like ? or description like ? or display_name like ? " + "or stage_name like ? or title_action like ?)"; args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); } if (filter_customer_data) { where += " and partner_id = ?"; args.add(customer_id + ""); } whereArgs = args.toArray(new String[args.size()]); return new CursorLoader(getActivity(), db().uri(), null, where, whereArgs, "create_date DESC"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); if (data.getCount() > 0) { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setVisible(mView, R.id.swipe_container); OControls.setGone(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.swipe_container, CRMLeads.this); } }, 500); } else { if (db().isEmptyTable() && !syncRequested) { syncRequested = true; onRefresh(); } new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setGone(mView, R.id.swipe_container); OControls.setVisible(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.customer_no_items, CRMLeads.this); OControls.setImage(mView, R.id.icon, R.drawable.ic_action_leads ); OControls.setText(mView, R.id.title, "No Leads Found"); OControls.setText(mView, R.id.subTitle, ""); } }, 500); } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { OControls.setText(view, R.id.name, row.getString("name")); OControls.setText(view, R.id.stage, row.getString("stage_name")); OControls.setText(view, R.id.display_name, row.getString("display_name")); OControls.setText(view, R.id.assignee_name, row.getString("assignee_name")); String date = ODateUtils.convertToDefault(row.getString("create_date"), ODateUtils.DEFAULT_FORMAT, "MMMM, dd"); OControls.setText(view, R.id.create_date, date); syncBundle.putBoolean(KEY_IS_LEAD, true); view.findViewById(R.id.opportunity_controls).setVisibility(View.GONE); } @Override public List drawerMenus(Context context) { List menu = new ArrayList<>(); menu.add(new ODrawerItem(TAG) .setTitle(OResource.string(context, R.string.label_leads)) .setInstance(new CRMLeads()) .setIcon(R.drawable.ic_action_leads) .setExtra(data(Type.Leads))); menu.add(new ODrawerItem(TAG) .setTitle(OResource.string(context, R.string.label_opportunities)) .setInstance(new CRMOpportunitiesPager()) .setIcon(R.drawable.ic_action_opportunities) .setExtra(data(Type.Opportunities))); return menu; } private Bundle data(Type type) { Bundle extra = new Bundle(); extra.putString(KEY_MENU, type.toString()); return extra; } @Override public Class database() { return CRMLead.class; } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(CRMLead.AUTHORITY, syncBundle); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } @Override public void onStatusChange(Boolean refreshing) { getLoaderManager().restartLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); inflater.inflate(R.menu.menu_leads, menu); setHasSearchView(this, menu, R.id.menu_lead_search); } @Override public boolean onSearchViewTextChange(String newFilter) { mFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { // Nothing to do } @Override public void onItemDoubleClick(View view, int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); IntentUtils.startActivity(getActivity(), CRMDetail.class, row.getPrimaryBundleData()); } @Override public void onItemClick(View view, int position) { showSheet((Cursor) mAdapter.getItem(position)); } private void showSheet(Cursor data) { BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.body_text_2)); builder.setTextColor(_c(R.color.body_text_2)); builder.setData(data); builder.actionListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); builder.menu(R.menu.menu_lead_list_sheet); mSheet = builder.create(); mSheet.show(); } @Override public void onSheetActionClick(BottomSheet sheet, Object extras) { mSheet.dismiss(); ODataRow row = OCursorUtils.toDatarow((Cursor) extras); IntentUtils.startActivity(getActivity(), CRMDetail.class, row.getPrimaryBundleData()); } @Override public void onItemClick(BottomSheet sheet, MenuItem menu, Object extras) { ODataRow row = OCursorUtils.toDatarow((Cursor) extras); mLocal_id = row.getInt(OColumn.ROW_ID); mSheet.dismiss(); convertRequestRecord = row; CRMLead crmLead = (CRMLead) db(); ResPartner partner = new ResPartner(getActivity(), null); switch (menu.getItemId()) { case R.id.menu_lead_convert_to_opportunity: if (inNetwork()) { if (row.getInt("id") == 0) { OAlert.showWarning(getActivity(), OResource.string(getActivity(), R.string.label_sync_warning)); } else { int count = crmLead.count("id != ? and partner_id = ? and " + OColumn.ROW_ID + " != ?" , new String[]{ "0", row.getInt("partner_id") + "", row.getString(OColumn.ROW_ID) }); if (count > 0) { Intent intent = new Intent(getActivity(), ConvertToOpportunityWizard.class); intent.putExtras(row.getPrimaryBundleData()); parent().startActivityForResult(intent, REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD); } else { crmLead.convertToOpportunity(row, new ArrayList(), convertDoneListener); } } } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_call_customer: String contact = (row.getString("phone").equals("false")) ? (row.getString("mobile").equals("false")) ? "false" : row.getString("mobile") : row.getString("phone"); if (contact.equals("false")) { if (!row.getString("partner_id").equals("false")) { contact = partner.getContact(getActivity(), row.getInt(OColumn.ROW_ID)); if (!contact.equals("false")) { IntentUtils.requestCall(getActivity(), contact); } else { Toast.makeText(getActivity(), "No contact found !", Toast.LENGTH_LONG).show(); } } else { Toast.makeText(getActivity(), "No contact found !", Toast.LENGTH_LONG).show(); } } else { IntentUtils.requestCall(getActivity(), contact); } break; case R.id.menu_lead_customer_location: if (!row.getString("partner_id").equals("false")) { String address = partner.getAddress(partner.browse(row.getInt("partner_id"))); if (!address.equals("false") && !TextUtils.isEmpty(address)) { IntentUtils.redirectToMap(getActivity(), address); } else { Toast.makeText(getActivity(), "No location found !", Toast.LENGTH_LONG).show(); } } else { Toast.makeText(getActivity(), "No partner found !", Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_lost: wonLost = "lost"; if (inNetwork()) { crmLead.markWonLost(wonLost, row, markDoneListener); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; } } @Override public void onOdooActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD && resultCode == Activity.RESULT_OK) { CRMLead crmLead = (CRMLead) db(); List ids = data.getIntegerArrayListExtra(ConvertToOpportunityWizard.KEY_LEADS_IDS); crmLead.convertToOpportunity(convertRequestRecord, ids, convertDoneListener); } } CRMLead.OnOperationSuccessListener markDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), StringUtils.capitalizeString(convertRequestRecord.getString("type")) + " marked " + wonLost, Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; CRMLead.OnOperationSuccessListener convertDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), R.string.label_convert_to_opportunity, Toast.LENGTH_LONG).show(); Intent intent = new Intent(getActivity(), CRMDetail.class); intent.putExtra(OColumn.ROW_ID, mLocal_id); startActivity(intent); } @Override public void OnCancelled() { } }; @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/CRMOpportunities.java ================================================ package com.odoo.addons.crm; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.odoo.addons.calendar.EventDetail; import com.odoo.addons.crm.models.CRMCaseStage; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.customers.Customers; import com.odoo.addons.phonecall.PhoneCallDetail; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import com.odoo.core.utils.dialog.OChoiceDialog; import com.odoo.core.utils.sys.IOnActivityResultListener; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.R; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import java.util.ArrayList; import java.util.List; public class CRMOpportunities extends BaseFragment implements OCursorListAdapter.OnViewBindListener, LoaderManager.LoaderCallbacks, SwipeRefreshLayout.OnRefreshListener, ISyncStatusObserverListener, OCursorListAdapter.BeforeBindUpdateData, IOnSearchViewChangeListener, View.OnClickListener, IOnItemClickListener, BottomSheetListeners.OnSheetItemClickListener, BottomSheetListeners.OnSheetActionClickListener, IOnBackPressListener, IOnActivityResultListener { public static final String TAG = CRMOpportunities.class.getSimpleName(); public static final String KEY_MENU = "key_menu_item"; public static final int REQUEST_CONVERT_TO_OPPORTUNITY_WIZARD = 223; public static final int REQUEST_CONVERT_TO_QUOTATION_WIZARD = 224; public static final String KEY_IS_LEAD = "key_is_lead"; // private Type mType = Type.Opportunities; private View mView; private ListView mList; private OCursorListAdapter mAdapter; private BottomSheet mSheet = null; private String mFilter = null; private String wonLost = "won"; private int stage_id = -1; private boolean syncRequested = false; // Customer's data filter private boolean filter_customer_data = false; private int customer_id = -1; private ODataRow convertRequestRecord = null; private Bundle syncBundle = new Bundle(); @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setHasOptionsMenu(true); return inflater.inflate(R.layout.common_listview, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; parent().setHasActionBarSpinner(true); parent().setOnActivityResultListener(this); Bundle extra = getArguments(); if (extra != null) { if (extra.containsKey(CRMOpportunitiesPager.KEY_STAGE_ID)) { stage_id = extra.getInt(CRMOpportunitiesPager.KEY_STAGE_ID); } if (extra.containsKey(Customers.KEY_FILTER_REQUEST)) { filter_customer_data = true; customer_id = extra.getInt(Customers.KEY_CUSTOMER_ID); mView.findViewById(R.id.customer_filterContainer).setVisibility(View.VISIBLE); OControls.setText(mView, R.id.customer_name, extra.getString("name")); mView.findViewById(R.id.cancel_filter).setOnClickListener(this); } } setHasSyncStatusObserver(TAG, this, db()); initAdapter(); } private void initAdapter() { if (getActivity() != null) { mList = (ListView) mView.findViewById(R.id.listview); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.crm_item); mAdapter.setOnViewBindListener(this); mList.setAdapter(mAdapter); setHasFloatingButton(mView, R.id.fabButton, mList, this); mAdapter.handleItemClickListener(mList, this); getLoaderManager().initLoader(0, null, this); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.cancel_filter: getActivity().getSupportFragmentManager().popBackStack(); break; case R.id.fabButton: Bundle types = new Bundle(); types.putString("type", CRMLeads.Type.Opportunities.toString()); types.putInt("stage_id", stage_id); IntentUtils.startActivity(getActivity(), CRMDetail.class, types); break; case R.id.stage_move: final ODataRow row = (ODataRow) v.getTag(); final List stageNames = new ArrayList<>(); CRMCaseStage crmStage = new CRMCaseStage(getActivity(), null); final List stages = crmStage.select(null, "type != ?", new String[]{"lead"}, "sequence"); int defaultSelected = -1; for (ODataRow stage : stages) { stageNames.add(stage.getString("name")); if (stage_id == stage.getInt(OColumn.ROW_ID)) { defaultSelected = stageNames.indexOf(stage.getString("name")); } } OChoiceDialog.get(getActivity()).withTitle("Move to").withOptions(stageNames, defaultSelected) .show(new OChoiceDialog.OnChoiceSelectListener() { @Override public void choiceSelected(int position, String value) { OValues stageValue = new OValues(); stageValue.put("stage_name", stages.get(position). getString("name")); stageValue.put("stage_id", stages.get(position) .getInt(OColumn.ROW_ID)); stageValue.put("probability", stages.get(position).getFloat("probability")); new CRMLead(getActivity(), null).update(OColumn.ROW_ID + "=?", new String[]{row.getString(OColumn.ROW_ID)}, stageValue); Toast.makeText(getActivity(), row.getString("name") + " moved to stage " + stages.get(position). getString("name"), Toast.LENGTH_SHORT).show(); } }); break; } } @Override public ODataRow updateDataRow(Cursor cr) { return db().browse(new String[]{"stage_id"}, cr.getInt(cr.getColumnIndex(OColumn.ROW_ID))); } @Override public Loader onCreateLoader(int id, Bundle data) { String where = " type = ? and stage_id = ?"; String[] whereArgs; List args = new ArrayList<>(); args.add("opportunity"); args.add(stage_id + ""); if (mFilter != null) { where += " and (name like ? or description like ? or display_name like ? " + "or stage_name like ? or title_action like ?)"; args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); } if (filter_customer_data) { where += " and partner_id = ?"; args.add(customer_id + ""); } whereArgs = args.toArray(new String[args.size()]); return new CursorLoader(getActivity(), db().uri(), null, where, whereArgs, "date_action DESC"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); if (data.getCount() > 0) { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setVisible(mView, R.id.swipe_container); OControls.setGone(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.swipe_container, CRMOpportunities.this); } }, 500); } else { if (db().isEmptyTable() && !syncRequested) { syncRequested = true; onRefresh(); } new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setGone(mView, R.id.swipe_container); OControls.setVisible(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.customer_no_items, CRMOpportunities.this); OControls.setImage(mView, R.id.icon, R.drawable.ic_action_opportunities); if (getActivity() != null) OControls.setText(mView, R.id.title, OResource.string(getActivity(), R.string.label_no_opportunity_found)); OControls.setText(mView, R.id.subTitle, ""); } }, 500); } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { OControls.setText(view, R.id.name, row.getString("name")); OControls.setGone(view, R.id.stage); OControls.setVisible(view, R.id.stage_move); //OControls.setText(view, R.id.stage, row.getString("stage_name")); OControls.setText(view, R.id.display_name, row.getString("display_name")); OControls.setText(view, R.id.assignee_name, row.getString("assignee_name")); String date = ODateUtils.convertToDefault(row.getString("create_date"), ODateUtils.DEFAULT_FORMAT, "MMMM, dd"); OControls.setText(view, R.id.create_date, date); // Controls for opportunity syncBundle.putBoolean(KEY_IS_LEAD, false); view.findViewById(R.id.opportunity_controls).setVisibility(View.VISIBLE); if (!row.getString("date_action").equals("false")) { OControls.setVisible(view, R.id.date_action); String date_action = ODateUtils.convertToDefault(row.getString("date_action") , ODateUtils.DEFAULT_DATE_FORMAT, "MMMM, dd"); OControls.setText(view, R.id.date_action, date_action + " : "); } else { OControls.setGone(view, R.id.date_action); } if (!row.getString("title_action").equals("false")) { OControls.setVisible(view, R.id.title_action); OControls.setText(view, R.id.title_action, row.getString("title_action")); } else { OControls.setGone(view, R.id.title_action); } // TextView tv = (TextView) view.findViewById(R.id.stage); // tv.setOnClickListener(this); // tv.setTag(row); view.findViewById(R.id.stage_move).setTag(row); view.findViewById(R.id.stage_move).setOnClickListener(this); } @Override public List drawerMenus(Context context) { return new ArrayList<>(); } @Override public Class database() { return CRMLead.class; } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(CRMLead.AUTHORITY, syncBundle); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } @Override public void onStatusChange(Boolean refreshing) { getLoaderManager().restartLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); inflater.inflate(R.menu.menu_leads, menu); setHasSearchView(this, menu, R.id.menu_lead_search); } @Override public boolean onSearchViewTextChange(String newFilter) { mFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { // Nothing to do } @Override public void onItemDoubleClick(View view, int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); IntentUtils.startActivity(getActivity(), CRMDetail.class, row.getPrimaryBundleData()); } @Override public void onItemClick(View view, int position) { showSheet((Cursor) mAdapter.getItem(position)); } private void showSheet(Cursor data) { BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.body_text_2)); builder.setTextColor(_c(R.color.body_text_2)); builder.setData(data); builder.actionListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); builder.menu(R.menu.menu_opp_list_sheet); mSheet = builder.create(); mSheet.show(); } @Override public void onSheetActionClick(BottomSheet sheet, Object extras) { mSheet.dismiss(); ODataRow row = OCursorUtils.toDatarow((Cursor) extras); IntentUtils.startActivity(getActivity(), CRMDetail.class, row.getPrimaryBundleData()); } @Override public void onItemClick(BottomSheet sheet, MenuItem menu, Object extras) { final ODataRow row = OCursorUtils.toDatarow((Cursor) extras); mSheet.dismiss(); CRMLead crmLead = (CRMLead) db(); ResPartner partner = new ResPartner(getActivity(), null); switch (menu.getItemId()) { case R.id.menu_lead_convert_to_quotation: if (inNetwork()) { Intent intent = new Intent(getActivity(), ConvertToQuotation.class); intent.putExtras(row.getPrimaryBundleData()); parent().startActivityForResult(intent, REQUEST_CONVERT_TO_QUOTATION_WIZARD); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_call_customer: String contact = (row.getString("phone").equals("false")) ? (row.getString("mobile").equals("false")) ? "false" : row.getString("mobile") : row.getString("phone"); if (contact.equals("false")) { if (!row.getString("partner_id").equals("false")) { contact = partner.getContact(getActivity(), row.getInt(OColumn.ROW_ID)); if (!contact.equals("false")) { IntentUtils.requestCall(getActivity(), contact); } else { Toast.makeText(getActivity(), "No contact found !", Toast.LENGTH_LONG).show(); } } else { Toast.makeText(getActivity(), "No contact found !", Toast.LENGTH_LONG). show(); } } else { IntentUtils.requestCall(getActivity(), contact); } break; case R.id.menu_lead_customer_location: if (!row.getString("partner_id").equals("false")) { String address = partner.getAddress(partner.browse(row.getInt("partner_id"))); if (!address.equals("false") && !TextUtils.isEmpty(address)) { IntentUtils.redirectToMap(getActivity(), address); } else { Toast.makeText(getActivity(), _s(R.string.label_no_location_found), Toast.LENGTH_LONG). show(); } } else { Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_reschedule: List choices = new ArrayList<>(); choices.add(OResource.string(getActivity(), R.string.label_opt_schedule_log_call)); choices.add(OResource.string(getActivity(), R.string.label_opt_schedule_meeting)); OChoiceDialog.get(getActivity()).withOptions(choices, -1) .show(new OChoiceDialog.OnChoiceSelectListener() { @Override public void choiceSelected(int position, String value) { int opp_id = row.getInt(OColumn.ROW_ID); switch (position) { case 0: Bundle extra = new Bundle(); extra.putInt("opp_id", opp_id); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); break; case 1: // Schedule meeting Bundle data = new Bundle(); // data.putString(KEY_DATE, mFilterDate); data.putInt("opp_id", opp_id); IntentUtils.startActivity(getActivity(), EventDetail.class, data); break; } } }); break; case R.id.menu_lead_won: wonLost = "won"; if (inNetwork()) { crmLead.markWonLost(wonLost, row, markDoneListener); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_lead_lost: wonLost = "lost"; if (inNetwork()) { crmLead.markWonLost(wonLost, row, markDoneListener); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; } } @Override public void onOdooActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CONVERT_TO_QUOTATION_WIZARD && resultCode == Activity.RESULT_OK) { CRMLead crmLead = (CRMLead) db(); convertRequestRecord = crmLead.browse(data.getIntExtra(OColumn.ROW_ID, 0)); crmLead.createQuotation(convertRequestRecord, data.getStringExtra("partner_id"), data.getBooleanExtra("mark_won", false), createQuotationListener); } } CRMLead.OnOperationSuccessListener createQuotationListener = new CRMLead. OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), OResource.string(getActivity(), R.string.label_quotation_created) + " " + convertRequestRecord.getString("name"), Toast.LENGTH_LONG).show(); parent().sync().requestSync(SaleOrder.AUTHORITY); } @Override public void OnCancelled() { } }; CRMLead.OnOperationSuccessListener markDoneListener = new CRMLead.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), StringUtils.capitalizeString(convertRequestRecord.getString("type")) + " marked " + wonLost, Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } @Override protected void onNavSpinnerDestroy() { } @Override public void onDestroy() { super.onDestroy(); getLoaderManager().destroyLoader(0); } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/CRMOpportunitiesPager.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/2/15 11:56 AM */ package com.odoo.addons.crm; import android.annotation.SuppressLint; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.view.PagerTabStrip; import android.support.v4.view.ViewPager; import android.support.v4.widget.SwipeRefreshLayout; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.odoo.R; import com.odoo.addons.crm.models.CRMCaseStage; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.customers.Customers; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.core.utils.sys.IOnBackPressListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import odoo.controls.OControlHelper; public class CRMOpportunitiesPager extends BaseFragment implements ViewPager.OnPageChangeListener, AdapterView.OnItemSelectedListener, IOnBackPressListener, SwipeRefreshLayout.OnRefreshListener { public static final String TAG = CRMOpportunitiesPager.class.getSimpleName(); public static final String KEY_MENU = "key_menu_item"; private CRMLeads.Type mType = CRMLeads.Type.Opportunities; private Context mContext; private Handler handler; private DataObserver observer; private Cursor cursor = null; private String[] projection = new String[]{"name"}; private CRMCaseStage crmStage; public static final String KEY_STAGE_ID = "stage_id"; public static final String KEY_FILTER = "key_filter"; private ViewPager mPager; private PagerTabStrip mTabStrip; private HashMap mFragments = new HashMap<>(); private StagePagerAdapter mAdapter; private Boolean filterCustomerOpp = false; private int customer_id = -1; private Spinner mNavSpinner = null; private OListAdapter mNavSpinnerAdapter = null; private List spinnerItems = new ArrayList<>(); private int selectedPagerPosition = 0; private View mView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.crm_opportunity_pagger, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; mContext = getActivity(); parent().setOnBackPressListener(this); Bundle extra = getArguments(); if (extra.containsKey(Customers.KEY_FILTER_REQUEST)) { filterCustomerOpp = true; customer_id = extra.getInt(Customers.KEY_CUSTOMER_ID); } crmStage = new CRMCaseStage(getActivity(), null); handler = new Handler(); observer = new DataObserver(handler); parent().setHasActionBarSpinner(true); mNavSpinner = parent().getActionBarSpinner(); initPager(view); initSpinner(); } private void initSpinner() { if (getActivity() == null) { return; } spinnerItems.clear(); spinnerItems.addAll(crmStage.select(null, "type!=?", new String[]{"lead"}, "sequence")); if (spinnerItems.isEmpty()) { parent().setHasActionBarSpinner(false); mPager.setVisibility(View.GONE); setHasSwipeRefreshView(mView, R.id.no_items_found, this); OControls.setVisible(mView, R.id.no_items_found); OControls.setVisible(mView, R.id.dashboard_no_item_view); OControls.setText(mView, R.id.title, OResource.string(getActivity(), R.string.label_no_opportunity_found)); OControls.setText(mView, R.id.subTitle, ""); OControls.setImage(mView, R.id.icon, R.drawable.ic_action_opportunities); return; } else { mPager.setVisibility(View.VISIBLE); OControls.setGone(mView, R.id.no_items_found); OControls.setGone(mView, R.id.dashboard_no_item_view); } mNavSpinnerAdapter = new OListAdapter(getActivity(), R.layout.base_simple_list_item_1, spinnerItems) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate(R.layout.base_simple_list_item_1_selected , parent, false); } return getSpinnerView(getItem(position), position, convertView, parent); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate(getResource(), parent, false); } return getSpinnerView(getItem(position), position, convertView, parent); } }; mNavSpinner.setAdapter(mNavSpinnerAdapter); mNavSpinner.setOnItemSelectedListener(this); } private View getSpinnerView(Object row, int pos, View view, ViewGroup parent) { ODataRow r = (ODataRow) row; OControls.setText(view, android.R.id.text1, r.getString("name")); return view; } private void initPager(View view) { getActivity().getContentResolver().registerContentObserver( crmStage.uri(), true, observer); initCR(); mPager = (ViewPager) view.findViewById(R.id.pager); mPager.setOnPageChangeListener(this); mTabStrip = (PagerTabStrip) view.findViewById(R.id.pager_title_strip); mTabStrip.setTabIndicatorColor(Color.WHITE); mPager.setOffscreenPageLimit(2); mAdapter = new StagePagerAdapter(cursor, getChildFragmentManager()); mPager.setAdapter(mAdapter); for (int i = 0; i < mTabStrip.getChildCount(); ++i) { View nextChild = mTabStrip.getChildAt(i); if (nextChild instanceof TextView) { TextView textViewToConvert = (TextView) nextChild; textViewToConvert.setAllCaps(true); textViewToConvert.setTextColor(Color.WHITE); textViewToConvert.setTypeface(OControlHelper.boldFont()); } } } private void initCR() { cursor = mContext.getContentResolver().query(crmStage.uri(), projection, "type != ?", new String[]{"lead"}, "sequence"); } @Override public void onPageScrolled(int i, float v, int i2) { } @Override public void onPageSelected(int position) { mNavSpinner.setSelection(position); selectedPagerPosition = position; } @Override public void onPageScrollStateChanged(int i) { } @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { mPager.setCurrentItem(position, true); } @Override public void onNothingSelected(AdapterView parent) { } @Override public boolean onBackPressed() { if (mFragments.size() > 0) { return ((IOnBackPressListener) mFragments.get("index_" + mNavSpinner.getSelectedItemPosition()) ).onBackPressed(); } return true; } @Override public void onRefresh() { if (inNetwork()) { Bundle syncBundle = new Bundle(); syncBundle.putBoolean(CRMOpportunities.KEY_IS_LEAD, false); parent().sync().requestSync(CRMLead.AUTHORITY, syncBundle); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } private class StagePagerAdapter extends FragmentStatePagerAdapter { private String key_filter; public StagePagerAdapter(Cursor cursor, FragmentManager fm) { super(fm); key_filter = "opportunity"; } @Override public CharSequence getPageTitle(int position) { cursor.moveToPosition(position); String name = cursor.getString(cursor.getColumnIndex("name")); int row_id = cursor.getInt(cursor.getColumnIndex(OColumn.ROW_ID)); String where = "stage_id = ? and type != ?"; List args = new ArrayList<>(); args.add(row_id + ""); args.add("lead"); if (filterCustomerOpp) { where += " and partner_id = ?"; args.add(customer_id + ""); } int count = db().count(where, args.toArray(new String[args.size()])); if (count > 0) name += " (" + count + ")"; return name; } @Override public Fragment getItem(int index) { CRMOpportunities crm = new CRMOpportunities(); cursor.moveToPosition(index); int stage_id = cursor.getInt(cursor.getColumnIndex(OColumn.ROW_ID)); Bundle bundle = new Bundle(); bundle.putInt(KEY_STAGE_ID, stage_id); bundle.putString(KEY_FILTER, key_filter); bundle.putInt("index", index); if (filterCustomerOpp) { bundle.putBoolean(Customers.KEY_FILTER_REQUEST, true); bundle.putInt(Customers.KEY_CUSTOMER_ID, customer_id); bundle.putString("name", getArguments().getString("name")); } crm.setArguments(bundle); mFragments.put("index_" + index, crm); return crm; } @Override public void restoreState(Parcelable state, ClassLoader loader) { super.restoreState(null, loader); } @Override public int getCount() { return cursor.getCount(); } } @Override public List drawerMenus(Context context) { return new ArrayList<>(); } @Override public Class database() { return CRMLead.class; } private class DataObserver extends ContentObserver { public DataObserver(Handler handler) { super(handler); } @SuppressLint("NewApi") @Override public void onChange(boolean selfChange) { super.onChange(selfChange); } @SuppressLint("NewApi") @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); updatePager(); } } public void updatePager() { initCR(); initSpinner(); mAdapter.notifyDataSetChanged(); mPager.setCurrentItem(selectedPagerPosition); } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/ConvertToOpportunityWizard.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 27/1/15 3:07 PM */ package com.odoo.addons.crm; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.odoo.addons.crm.models.CRMLead; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.StringUtils; import com.odoo.core.utils.controls.ExpandableHeightGridView; import com.odoo.R; import java.util.ArrayList; import java.util.List; import odoo.controls.OField; import odoo.controls.OForm; public class ConvertToOpportunityWizard extends ActionBarActivity implements View.OnClickListener, OField.IOnFieldValueChangeListener { public static final String TAG = ConvertToOpportunityWizard.class.getSimpleName(); public static final String KEY_LEADS_IDS = "key_leads_ids"; private Bundle extra; private OForm convert_form; private CRMLead crmLead = null; private OListAdapter mAdapter; private List items = new ArrayList<>(); private ExpandableHeightGridView mOpportunityList; private OField conversation_action; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crm_convert_to_opportunity); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); getSupportActionBar().hide(); setResult(RESULT_CANCELED); extra = getIntent().getExtras(); convert_form = (OForm) findViewById(R.id.convert_form); convert_form.setEditable(true); convert_form.initForm(null); findViewById(R.id.create_opportunity).setOnClickListener(this); findViewById(R.id.cancel).setOnClickListener(this); conversation_action = (OField) findViewById(R.id.conversation_action); conversation_action.setOnValueChangeListener(this); init(); } private void init() { items.clear(); mOpportunityList = (ExpandableHeightGridView) findViewById(R.id.opportunities); mOpportunityList.setExpanded(true); crmLead = new CRMLead(this, null); ODataRow lead = crmLead.browse(extra.getInt(OColumn.ROW_ID)); items.addAll(crmLead.select(null, "partner_id = ? and id != ? and " + OColumn.ROW_ID + " != ?", new String[]{ lead.getString("partner_id"), "0", lead.getString(OColumn.ROW_ID) })); mAdapter = new OListAdapter(this, R.layout.crm_convert_to_opportunity_item, items) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(ConvertToOpportunityWizard.this) .inflate(getResource(), parent, false); } ODataRow row = (ODataRow) getItem(position); OControls.setText(convertView, R.id.name, row.getString("name")); OControls.setText(convertView, R.id.stage, row.getString("stage_name")); OControls.setText(convertView, R.id.type, StringUtils.capitalizeString(row.getString("type"))); String date = ODateUtils.convertToDefault(row.getString("create_date"), ODateUtils.DEFAULT_FORMAT, "MMMM, dd"); OControls.setText(convertView, R.id.create_date, date); convertView.findViewById(R.id.remove_lead).setTag(position); convertView.findViewById(R.id.remove_lead).setOnClickListener(ConvertToOpportunityWizard.this); return convertView; } }; mOpportunityList.setAdapter(mAdapter); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.create_opportunity: ArrayList ids = new ArrayList<>(); for (Object data : items) { ODataRow row = (ODataRow) data; ids.add(row.getInt("id")); } Intent result = new Intent(); result.putIntegerArrayListExtra(KEY_LEADS_IDS, ids); setResult(RESULT_OK, result); finish(); break; case R.id.remove_lead: int pos = (int) v.getTag(); items.remove(pos); mAdapter.notifiyDataChange(items); if (items.size() == 0) { conversation_action.setValue(1); } break; case R.id.cancel: finish(); break; } } @Override public void onFieldValueChange(OField field, Object value) { ODataRow record = (ODataRow) value; int index = record.getInt(OColumn.ROW_ID); switch (index) { case -1: case 0: findViewById(R.id.opportunity_container).setVisibility(View.GONE); break; case 1: findViewById(R.id.opportunity_container).setVisibility(View.VISIBLE); init(); break; } } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/ConvertToQuotation.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 28/1/15 10:57 AM */ package com.odoo.addons.crm; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.view.ViewGroup; import com.odoo.addons.crm.models.CRMLead; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.R; import odoo.controls.OField; import odoo.controls.OForm; public class ConvertToQuotation extends ActionBarActivity implements View.OnClickListener { public static final String TAG = ConvertToQuotation.class.getSimpleName(); private Bundle extra; private OForm convert_form; private CRMLead crmLead = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crm_convert_to_quotation); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getSupportActionBar().hide(); setResult(RESULT_CANCELED); extra = getIntent().getExtras(); crmLead = new CRMLead(this, null); ODataRow lead = crmLead.browse(extra.getInt(OColumn.ROW_ID)); ODataRow customer = new ODataRow(); customer.put("partner_id", lead.getInt("partner_id")); convert_form = (OForm) findViewById(R.id.convert_form); convert_form.setEditable(true); convert_form.initForm(customer); findViewById(R.id.create_quotation).setOnClickListener(this); findViewById(R.id.cancel).setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.create_quotation: if (convert_form.getValues().getInt("partner_id") <= 0) { OField partner = (OField) convert_form.findViewById(R.id.partner); partner.setError("Select Partner"); partner.requestFocus(); } else { boolean mark_won = convert_form.getValues().getBoolean("mark_won"); String partner_id = convert_form.getValues().getString("partner_id"); Intent data = new Intent(); data.putExtra(OColumn.ROW_ID, extra.getInt(OColumn.ROW_ID)); data.putExtra("partner_id", partner_id); data.putExtra("mark_won", mark_won); setResult(RESULT_OK, data); finish(); } break; case R.id.cancel: finish(); break; } } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/models/CRMCaseCateg.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:15 AM */ package com.odoo.addons.crm.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class CRMCaseCateg extends OModel { public static final String TAG = CRMCaseCateg.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); public CRMCaseCateg(Context context, OUser user) { super(context, "crm.case.categ", user); if (getOdooVersion() != null) { int version = getOdooVersion().getVersion_number(); String serie = getOdooVersion().getServer_serie(); if (version >= 9 || serie.equals("8.saas~6")) { setModelName("crm.lead.tag"); } } } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/models/CRMCaseStage.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:12 AM */ package com.odoo.addons.crm.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class CRMCaseStage extends OModel { public static final String TAG = CRMCaseStage.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); OColumn sequence = new OColumn("Sequence", OInteger.class); OColumn probability = new OColumn("Probability (%)", OFloat.class).setSize(20); OColumn case_default = new OColumn("Default to New Sales Team", OBoolean.class); OColumn type = new OColumn("Type", OVarchar.class); public CRMCaseStage(Context context, OUser user) { super(context, "crm.case.stage", user); if (getOdooVersion() != null) { int version = getOdooVersion().getVersion_number(); String serieVersion = getOdooVersion().getServer_serie(); if (serieVersion.equals("8.saas~6") || version >= 9) { setModelName("crm.stage"); } } } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/models/CRMLead.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:07 AM */ package com.odoo.addons.crm.models; import android.app.ProgressDialog; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; import com.odoo.R; import com.odoo.base.addons.res.ResCompany; import com.odoo.base.addons.res.ResCountry; import com.odoo.base.addons.res.ResCurrency; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.account.BaseSettings; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODate; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.reminder.ReminderUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.Date; import java.util.List; import odoo.OArguments; import odoo.ODomain; public class CRMLead extends OModel { public static final String TAG = CRMLead.class.getSimpleName(); public static final String AUTHORITY = "com.odoo.core.crm.provider.content.sync.crm_lead"; public static final String KEY_LEAD = "lead"; public static final String KEY_OPPORTUNITY = "opportunity"; private Context mContext; @Odoo.onChange(method = "partnerIdOnChange") OColumn partner_id = new OColumn("Customer", ResPartner.class, OColumn.RelationType.ManyToOne).addDomain("customer", "=", "true"); OColumn name = new OColumn("Name", OVarchar.class).setSize(64) .setRequired(); OColumn email_from = new OColumn("Email", OVarchar.class).setSize(128); OColumn street = new OColumn("Street", OText.class); OColumn street2 = new OColumn("Street2", OText.class); OColumn city = new OColumn("City", OVarchar.class).setSize(100); OColumn zip = new OColumn("Zip", OVarchar.class).setSize(20); OColumn mobile = new OColumn("Mobile", OVarchar.class).setSize(20); OColumn phone = new OColumn("Phone", OVarchar.class).setSize(20); OColumn create_date = new OColumn("Creation Date", ODateTime.class); OColumn description = new OColumn("Internal Notes", OText.class); @Odoo.api.v7 @Odoo.api.v8 OColumn categ_ids = new OColumn("Tags", CRMCaseCateg.class, OColumn.RelationType.ManyToMany); @Odoo.api.v9alpha OColumn tag_ids = new OColumn("Tags", CRMCaseCateg.class, OColumn.RelationType.ManyToMany); OColumn contact_name = new OColumn("Contact Name", OVarchar.class); OColumn partner_name = new OColumn("Company Name", OVarchar.class); OColumn opt_out = new OColumn("Opt-Out", OBoolean.class); OColumn type = new OColumn("Type", OVarchar.class).setDefaultValue("lead"); OColumn priority = new OColumn("Priority", OVarchar.class).setSize(10); OColumn date_open = new OColumn("Assigned", ODateTime.class); OColumn date_closed = new OColumn("Closed", ODateTime.class); OColumn stage_id = new OColumn("Stage", CRMCaseStage.class, OColumn.RelationType.ManyToOne); OColumn user_id = new OColumn("Salesperson", ResUsers.class, OColumn.RelationType.ManyToOne); OColumn referred = new OColumn("Referred By", OVarchar.class); OColumn company_id = new OColumn("Company", ResCompany.class, OColumn.RelationType.ManyToOne); OColumn country_id = new OColumn("Country", ResCountry.class, OColumn.RelationType.ManyToOne); OColumn company_currency = new OColumn("Company Currency", ResCurrency.class, OColumn.RelationType.ManyToOne); /** * Only used for type opportunity */ OColumn probability = new OColumn("Success Rate (%)", OFloat.class).setSize(20).setDefaultValue("0.0"); OColumn planned_revenue = new OColumn("Expected Revenue", OFloat.class).setSize(20).setDefaultValue("0.0"); OColumn ref = new OColumn("Reference", OVarchar.class); OColumn ref2 = new OColumn("Reference 2", OVarchar.class); OColumn date_deadline = new OColumn("Expected Closing", ODate.class); OColumn date_action = new OColumn("Next Action", ODate.class); OColumn title_action = new OColumn("Next Action", OVarchar.class); OColumn planned_cost = new OColumn("Planned Cost", OFloat.class).setSize(20); /** * Extra functional fields */ @Odoo.Functional(method = "getDisplayName", store = true, depends = { "partner_id", "contact_name", "partner_name"}) OColumn display_name = new OColumn("Display Name", OVarchar.class) .setLocalColumn(); @Odoo.Functional(method = "storeAssigneeName", store = true, depends = {"user_id"}) OColumn assignee_name = new OColumn("Assignee", OVarchar.class).setSize(100) .setLocalColumn(); @Odoo.Functional(method = "storeStageName", store = true, depends = {"stage_id"}) OColumn stage_name = new OColumn("Stage name", OVarchar.class).setLocalColumn(); OColumn data_type = new OColumn("Data type", OVarchar.class).setSize(34) .setLocalColumn().setDefaultValue("opportunity"); OColumn is_done = new OColumn("Mark as Done", OInteger.class) .setLocalColumn().setDefaultValue("0"); OColumn color_index = new OColumn("Color index", OInteger.class).setSize(5) .setLocalColumn().setDefaultValue(7); public CRMLead(Context context, OUser user) { super(context, "crm.lead", user); mContext = context; setHasMailChatter(true); String serie = getOdooVersion().getServer_serie(); if (serie.equals("8.saas~6")) { categ_ids.setName("tag_ids"); } } @Override public Uri uri() { return buildURI(AUTHORITY); } public ODataRow partnerIdOnChange(ODataRow row) { ODataRow rec = new ODataRow(); String display_name = ""; String contact_name = ""; ResCountry country = new ResCountry(mContext, null); try { rec.put("partner_name", row.getString("name")); rec.put("partner_name", rec.getString("partner_name")); if (!row.getString("parent_id").equals("false")) { if (row.get("parent_id") instanceof JSONArray) { JSONArray parent_id = new JSONArray( row.getString("parent_id")); rec.put("partner_name", parent_id.get(1)); display_name = parent_id.getString(1); contact_name = parent_id.getString(1); } else { ODataRow parent_id = row.getM2ORecord("parent_id").browse(); if (parent_id != null) { rec.put("partner_name", parent_id.getString("name")); display_name = parent_id.getString("name"); contact_name = parent_id.getString("name"); } } if (!TextUtils.isEmpty(display_name)) { display_name += " (" + row.getString("name") + ")"; contact_name = row.getString("name"); } else { display_name += row.getString("name"); } } else { display_name = row.getString("name"); } Integer country_id = 0; if (!row.getString("country_id").equals("false")) { if (row.get("country_id") instanceof JSONArray) { JSONArray country_data = new JSONArray( row.getString("country_id")); country_id = country.selectRowId(country_data.getInt(0)); if (country_id == null) { country_id = 0; } } else { ODataRow country_data = row.getM2ORecord("country_id") .browse(); if (country_data != null) { country_id = country_data.getInt(OColumn.ROW_ID); } } if (country_id != 0) rec.put("country_id", country_id); } rec.put("display_name", display_name); rec.put("contact_name", contact_name); rec.put("street", row.getString("street")); rec.put("street2", row.getString("street2")); rec.put("city", row.getString("city")); rec.put("zip", row.getString("zip")); rec.put("email_from", row.getString("email")); rec.put("phone", row.getString("phone")); } catch (Exception e) { e.printStackTrace(); } return rec; } public String getDisplayName(OValues row) { String name = ""; try { if (!row.getString("partner_id").equals("false")) { JSONArray partner_id = new JSONArray( row.getString("partner_id")); name = partner_id.getString(1); } else if (!row.getString("partner_name").equals("false")) { name = row.getString("partner_name"); } if (!row.getString("contact_name").equals("false")) { name += (TextUtils.isEmpty(name)) ? row .getString("contact_name") : " (" + row.getString("contact_name") + ")"; } if (TextUtils.isEmpty(name)) { name = "No Partner"; } } catch (Exception e) { e.printStackTrace(); } return name; } public String storeAssigneeName(OValues vals) { try { if (!vals.getString("user_id").equals("false")) { JSONArray user_id = new JSONArray(vals.getString("user_id")); return user_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return "Unassigned"; } public String storeStageName(OValues values) { try { JSONArray stage_id = new JSONArray(values.getString("stage_id")); return stage_id.getString(1); } catch (Exception e) { } return "false"; } public void convertToOpportunity(final ODataRow lead, final List other_lead_ids, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { try { odoo.Odoo odoo = getServerDataHelper().getOdoo(); // Creating wizard record JSONObject values = new JSONObject(); values.put("name", (other_lead_ids.size() > 0) ? "merge" : "convert"); Object partner_id = false; ODataRow partner = null; if (!lead.getString("partner_id").equals("false")) { ResPartner resPartner = new ResPartner(mContext, getUser()); partner = resPartner.browse(lead.getInt("partner_id")); partner_id = partner.getInt("id"); } values.put("action", (partner == null) ? "create" : "exist"); values.put("partner_id", partner_id); JSONObject context = new JSONObject(); context.put("stage_type", "lead"); context.put("active_id", lead.getInt("id")); other_lead_ids.add(lead.getInt("id")); context.put("active_ids", JSONUtils.toArray(other_lead_ids)); context.put("active_model", "crm.lead"); odoo.updateContext(context); JSONObject result = odoo.createNew("crm.lead2opportunity.partner", values); int lead_to_opp_partner_id = result.getInt("result"); // Converting lead to opportunity OArguments arg = new OArguments(); arg.add(lead_to_opp_partner_id); arg.add(context); odoo.call_kw("crm.lead2opportunity.partner", "action_apply", arg.get()); OValues val = new OValues(); val.put("type", "opportunity"); for (int id : other_lead_ids) { update(selectRowId(id), val); } } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } private void _markWonLost(String type, ODataRow record) { OArguments oArguments = new OArguments(); oArguments.add(new JSONArray().put(record.getInt("id"))); getServerDataHelper().callMethod("case_mark_" + type, oArguments, new JSONObject()); CRMCaseStage stage = new CRMCaseStage(mContext, getUser()); String key = (type.equals("won")) ? "Won" : (record.getString("type").equals("lead")) ? "Dead" : "Lost"; ODataRow row = stage.browse(null, "name = ?", new String[]{key}); if (row != null) { OValues values = new OValues(); values.put("stage_id", row.getInt(OColumn.ROW_ID)); values.put("stage_name", row.getString("name")); values.put("probability", row.getFloat("probability")); update(record.getInt(OColumn.ROW_ID), values); } } /** * Setting reminder for lead/opportunity * * @param row_id */ public void setReminder(int row_id) { ODataRow row = browse(row_id); String time = " " + BaseSettings.getDayStartTime(mContext); Date now = new Date(); Bundle extra = row.getPrimaryBundleData(); extra.putString(ReminderUtils.KEY_REMINDER_TYPE, "opportunity"); if (!row.getString("date_deadline").equals("false")) { row.put("date_deadline", row.getString("date_deadline") + time); Date date_deadline = ODateUtils.createDateObject(row.getString("date_deadline"), ODateUtils.DEFAULT_FORMAT, false); if (now.compareTo(date_deadline) < 0) { extra.putBoolean("expiry_date", true); if (ReminderUtils.get(mContext).resetReminder(date_deadline, extra)) { // Nothing to do. Reminder set for expiry date } } } if (!row.getString("date_action").equals("false")) { row.put("date_action", row.getString("date_action") + time); Date date_action = ODateUtils.createDateObject(row.getString("date_action"), ODateUtils.DEFAULT_FORMAT, false); if (now.compareTo(date_action) < 0) { extra.putBoolean("expiry_date", false); if (ReminderUtils.get(mContext).resetReminder(date_action, extra)) { // Nothing to do. Reminder set for next date action } } } } public void markWonLost(final String type, final ODataRow record, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { _markWonLost(type, record); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } public void createQuotation(final ODataRow lead, final String partnerId, final boolean close, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { try { odoo.Odoo odoo = getServerDataHelper().getOdoo(); // Creating wizard record JSONObject values = new JSONObject(); ResPartner resPartner = new ResPartner(mContext, getUser()); ODataRow partner = resPartner.browse(new String[]{}, Integer.parseInt(partnerId)); values.put("partner_id", partner.getInt("id")); values.put("close", close); JSONObject context = new JSONObject(); context.put("stage_type", lead.getString("type")); context.put("active_id", lead.getInt("id")); context.put("active_ids", new JSONArray().put(lead.getInt("id"))); context.put("active_model", "crm.lead"); odoo.updateContext(context); JSONObject result = odoo.createNew("crm.make.sale", values); int quotation_wizard_id = result.getInt("result"); // Creating quotation OArguments arg = new OArguments(); arg.add(quotation_wizard_id); arg.add(context); odoo.call_kw("crm.make.sale", "makeOrder", arg.get()); Thread.sleep(500); // if close = true if (close) _markWonLost("won", lead); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } @Override public ODomain defaultDomain() { ODomain domain = new ODomain(); domain.add("|"); domain.add("user_id", "=", getUser().getUser_id()); domain.add("user_id", "=", false); return domain; } public static interface OnOperationSuccessListener { public void OnSuccess(); public void OnCancelled(); } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/providers/CRMLeadProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:28 AM */ package com.odoo.addons.crm.providers; import android.net.Uri; import com.odoo.addons.crm.models.CRMLead; import com.odoo.core.orm.provider.BaseModelProvider; public class CRMLeadProvider extends BaseModelProvider { public static final String TAG = CRMLeadProvider.class.getSimpleName(); @Override public void setModel(Uri uri) { super.setModel(uri); mModel = new CRMLead(getContext(), getUser(uri)); } @Override public String authority() { return CRMLead.AUTHORITY; } } ================================================ FILE: app/src/main/java/com/odoo/addons/crm/services/CRMLeadSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:31 AM */ package com.odoo.addons.crm.services; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import android.util.Log; import com.odoo.addons.crm.CRMLeads; import com.odoo.addons.crm.models.CRMCaseStage; import com.odoo.addons.crm.models.CRMLead; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.service.ISyncFinishListener; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.service.OSyncService; import com.odoo.core.support.OUser; import odoo.ODomain; public class CRMLeadSyncService extends OSyncService implements ISyncFinishListener { public static final String TAG = CRMLeadSyncService.class.getSimpleName(); private Context mContext; private OSyncService service; @Override public OSyncAdapter getSyncAdapter(OSyncService service, Context context) { mContext = context; this.service = service; return new OSyncAdapter(context, CRMLead.class, service, true); } @Override public void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user) { if (adapter.getModel().getModelName().equals("crm.lead")) { ODomain domain = new ODomain(); if (extras.containsKey(CRMLeads.KEY_IS_LEAD)) { domain.add("type", "=", (extras.getBoolean(CRMLeads.KEY_IS_LEAD)) ? "lead" : "opportunity"); adapter.setDomain(domain); Log.d(TAG, "Setting lead filter type"); } adapter.onSyncFinish(this).syncDataLimit(50); } } @Override public OSyncAdapter performNextSync(OUser user, SyncResult syncResult) { CRMLead crmLead = new CRMLead(mContext, user); for (ODataRow row : crmLead.select(new String[]{})) { crmLead.setReminder(row.getInt(OColumn.ROW_ID)); } return new OSyncAdapter(mContext, CRMCaseStage.class, service, true); } } ================================================ FILE: app/src/main/java/com/odoo/addons/customers/CustomerDetails.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/1/15 5:47 PM */ package com.odoo.addons.customers; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.GradientDrawable; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.odoo.App; import com.odoo.addons.customers.utils.ShareUtil; import com.odoo.base.addons.ir.feature.OFileManager; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.support.OdooFields; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OStringColorUtil; import com.odoo.R; import com.odoo.widgets.parallax.ParallaxScrollView; import org.json.JSONObject; import odoo.ODomain; import odoo.Odoo; import odoo.controls.OField; import odoo.controls.OForm; public class CustomerDetails extends ActionBarActivity implements View.OnClickListener, OField.IOnFieldValueChangeListener { public static final String TAG = CustomerDetails.class.getSimpleName(); private final String KEY_MODE = "key_edit_mode"; private final String KEY_NEW_IMAGE = "key_new_image"; private ActionBar actionBar; private Bundle extras; private ResPartner resPartner; private ODataRow record = null; private ParallaxScrollView parallaxScrollView; private ImageView userImage = null, captureImage = null; private TextView mTitleView = null; private OForm mForm; private App app; private Boolean mEditMode = false; private Menu mMenu; private OFileManager fileManager; private String newImage = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.customer_detail); OActionBarUtils.setActionBar(this, false); fileManager = new OFileManager(this); actionBar = getSupportActionBar(); actionBar.setTitle(""); if (savedInstanceState != null) { mEditMode = savedInstanceState.getBoolean(KEY_MODE); newImage = savedInstanceState.getString(KEY_NEW_IMAGE); } app = (App) getApplicationContext(); parallaxScrollView = (ParallaxScrollView) findViewById(R.id.parallaxScrollView); parallaxScrollView.setActionBar(actionBar); userImage = (ImageView) findViewById(android.R.id.icon); mTitleView = (TextView) findViewById(android.R.id.title); resPartner = new ResPartner(this, null); extras = getIntent().getExtras(); if (extras == null) mEditMode = true; setupActionBar(); } private void setMode(Boolean edit) { if (mMenu != null) { mMenu.findItem(R.id.menu_customer_detail_more).setVisible(!edit); mMenu.findItem(R.id.menu_customer_edit).setVisible(!edit); mMenu.findItem(R.id.menu_customer_save).setVisible(edit); mMenu.findItem(R.id.menu_customer_cancel).setVisible(edit); } int color = Color.DKGRAY; if (record != null) { color = OStringColorUtil.getStringColor(this, record.getString("name")); } if (edit) { if (extras != null) actionBar.setTitle(R.string.label_edit); else actionBar.setTitle(R.string.label_new); actionBar.setBackgroundDrawable(new ColorDrawable(color)); mForm = (OForm) findViewById(R.id.customerFormEdit); captureImage = (ImageView) findViewById(R.id.captureImage); captureImage.setOnClickListener(this); userImage = (ImageView) findViewById(android.R.id.icon1); findViewById(R.id.parallaxScrollView).setVisibility(View.GONE); findViewById(R.id.customerScrollViewEdit).setVisibility(View.VISIBLE); OField is_company = (OField) findViewById(R.id.is_company_edit); is_company.setOnValueChangeListener(this); } else { actionBar.setBackgroundDrawable(getResources().getDrawable(R.drawable.action_bar_shade)); userImage = (ImageView) findViewById(android.R.id.icon); mForm = (OForm) findViewById(R.id.customerForm); findViewById(R.id.customerScrollViewEdit).setVisibility(View.GONE); findViewById(R.id.parallaxScrollView).setVisibility(View.VISIBLE); } setColor(color); } private void setupActionBar() { if (extras == null) { setMode(mEditMode); userImage.setColorFilter(Color.parseColor("#ffffff")); mForm.setEditable(mEditMode); mForm.initForm(null); } else { int rowId = extras.getInt(OColumn.ROW_ID); record = resPartner.browse(rowId); record.put("full_address", resPartner.getAddress(record)); checkControls(); setMode(mEditMode); mForm.setEditable(mEditMode); mForm.initForm(record); mTitleView.setText(record.getString("name")); setCustomerImage(); if (record.getInt("id") != 0 && record.getString("large_image").equals("false")) { BigImageLoader bigImageLoader = new BigImageLoader(); bigImageLoader.execute(record.getInt("id")); } } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.full_address: IntentUtils.redirectToMap(this, record.getString("full_address")); break; case R.id.website: IntentUtils.openURLInBrowser(this, record.getString("website")); break; case R.id.email: IntentUtils.requestMessage(this, record.getString("email")); break; case R.id.phone_number: IntentUtils.requestCall(this, record.getString("phone")); break; case R.id.mobile_number: IntentUtils.requestCall(this, record.getString("mobile")); break; case R.id.captureImage: fileManager.requestForFile(OFileManager.RequestType.IMAGE_OR_CAPTURE_IMAGE); break; } } private void checkControls() { findViewById(R.id.full_address).setOnClickListener(this); findViewById(R.id.website).setOnClickListener(this); findViewById(R.id.email).setOnClickListener(this); findViewById(R.id.phone_number).setOnClickListener(this); findViewById(R.id.mobile_number).setOnClickListener(this); } private void setCustomerImage() { if (!record.getString("image_small").equals("false")) { userImage.setScaleType(ImageView.ScaleType.CENTER_CROP); userImage.setColorFilter(null); String base64 = newImage; if (newImage == null) { if (!record.getString("large_image").equals("false")) { base64 = record.getString("large_image"); } else { base64 = record.getString("image_small"); } } userImage.setImageBitmap(BitmapUtils.getBitmapImage(this, base64)); } else { userImage.setColorFilter(Color.parseColor("#ffffff")); } } private void setColor(int color) { FrameLayout frameLayout = (FrameLayout) findViewById(R.id.parallax_view); frameLayout.setBackgroundColor(color); parallaxScrollView.setParallaxOverLayColor(color); parallaxScrollView.setBackgroundColor(color); mForm.setIconTintColor(color); findViewById(R.id.parallax_view).setBackgroundColor(color); findViewById(R.id.parallax_view_edit).setBackgroundColor(color); findViewById(R.id.customerScrollViewEdit).setBackgroundColor(color); if (captureImage != null) { GradientDrawable shapeDrawable = (GradientDrawable) getResources().getDrawable(R.drawable.circle_mask_primary); shapeDrawable.setColor(color); captureImage.setBackgroundDrawable(shapeDrawable); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_customer_save: OValues values = mForm.getValues(); if (values != null) { if (newImage != null) { values.put("image_small", newImage); values.put("large_image", newImage); } if (record != null) { resPartner.update(record.getInt(OColumn.ROW_ID), values); Toast.makeText(this, R.string.toast_information_saved, Toast.LENGTH_LONG).show(); mEditMode = !mEditMode; setupActionBar(); } else { final int row_id = resPartner.insert(values); if (row_id != OModel.INVALID_ROW_ID) { finish(); } } } break; case R.id.menu_customer_cancel: if (record == null) { finish(); return true; } case R.id.menu_customer_edit: mEditMode = !mEditMode; setMode(mEditMode); mForm.setEditable(mEditMode); mForm.initForm(record); setCustomerImage(); break; case R.id.menu_customer_share: ShareUtil.shareContact(this, record, true); break; case R.id.menu_customer_import: ShareUtil.shareContact(this, record, false); break; } return super.onOptionsItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_customer_detail, menu); mMenu = menu; setMode(mEditMode); return true; } @Override public void onFieldValueChange(OField field, Object value) { if (field.getFieldName().equals("is_company")) { Boolean checked = Boolean.parseBoolean(value.toString()); int view = (checked) ? View.GONE : View.VISIBLE; findViewById(R.id.parent_id).setVisibility(view); } } private class BigImageLoader extends AsyncTask { @Override protected String doInBackground(Integer... params) { String image = null; try { Thread.sleep(300); Odoo odoo = app.getOdoo(resPartner.getUser()); if (odoo == null) { odoo = OSyncAdapter.createOdooInstance(CustomerDetails.this, resPartner.getUser()); } ODomain domain = new ODomain(); domain.add("id", "=", params[0]); JSONObject result = odoo.search_read(resPartner.getModelName(), new OdooFields(new String[]{"image_medium"}).get(), domain.get()); JSONObject records = result.getJSONArray("records") .getJSONObject(0); if (!records.getString("image_medium").equals("false")) { image = records.getString("image_medium"); } } catch (Exception e) { e.printStackTrace(); } return image; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); if (result != null) { if (!result.equals("false")) { OValues values = new OValues(); values.put("large_image", result); resPartner.update(record.getInt(OColumn.ROW_ID), values); record.put("large_image", result); setCustomerImage(); } } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(KEY_MODE, mEditMode); outState.putString(KEY_NEW_IMAGE, newImage); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); OValues values = fileManager.handleResult(requestCode, resultCode, data); if (values != null && !values.contains("size_limit_exceed")) { newImage = values.getString("datas"); userImage.setScaleType(ImageView.ScaleType.CENTER_CROP); userImage.setColorFilter(null); userImage.setImageBitmap(BitmapUtils.getBitmapImage(this, newImage)); } else if (values != null) { Toast.makeText(this, R.string.toast_image_size_too_large, Toast.LENGTH_LONG).show(); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/customers/Customers.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:28 PM */ package com.odoo.addons.customers; import android.app.ProgressDialog; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.odoo.addons.crm.CRMLeads; import com.odoo.addons.crm.CRMOpportunitiesPager; import com.odoo.addons.phonecall.PhoneCallDetail; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.R; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import java.util.ArrayList; import java.util.List; public class Customers extends BaseFragment implements ISyncStatusObserverListener, LoaderManager.LoaderCallbacks, SwipeRefreshLayout.OnRefreshListener, OCursorListAdapter.OnViewBindListener, IOnSearchViewChangeListener, View.OnClickListener, BottomSheetListeners.OnSheetItemClickListener, BottomSheetListeners.OnSheetActionClickListener, IOnBackPressListener, IOnItemClickListener, BottomSheetListeners.OnSheetMenuCreateListener { public static final String KEY = Customers.class.getSimpleName(); public static final String KEY_FILTER_REQUEST = "key_filter_request"; public static final String KEY_CUSTOMER_ID = "key_customer_id"; public static final String KEY_FILTER_TYPE = CRMLeads.KEY_MENU; private View mView; private String mCurFilter = null; private ListView mPartnersList = null; private OCursorListAdapter mAdapter = null; private BottomSheet mSheet = null; private boolean syncRequested = false; public enum Type { Leads, Opportunities } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setHasOptionsMenu(true); setHasSyncStatusObserver(KEY, this, db()); return inflater.inflate(R.layout.common_listview, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setHasSwipeRefreshView(view, R.id.swipe_container, this); parent().setOnBackPressListener(this); mView = view; mPartnersList = (ListView) view.findViewById(R.id.listview); mPartnersList.setFastScrollEnabled(true); mPartnersList.setFastScrollAlwaysVisible(true); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.customer_row_item); mAdapter.setHasSectionIndexers(true, "name"); mAdapter.setOnViewBindListener(this); mPartnersList.setAdapter(mAdapter); mAdapter.handleItemClickListener(mPartnersList, this); setHasFloatingButton(view, R.id.fabButton, mPartnersList, this); getLoaderManager().initLoader(0, null, this); } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { Bitmap img; if (row.getString("image_small").equals("false")) { img = BitmapUtils.getAlphabetImage(getActivity(), row.getString("name")); } else { img = BitmapUtils.getBitmapImage(getActivity(), row.getString("image_small")); } OControls.setImage(view, R.id.image_small, img); OControls.setText(view, R.id.name, row.getString("name")); OControls.setText(view, R.id.company_name, (row.getString("company_name").equals("false")) ? "" : row.getString("company_name")); OControls.setText(view, R.id.email, (row.getString("email").equals("false") ? " " : row.getString("email"))); } @Override public Loader onCreateLoader(int id, Bundle data) { String where = ""; List args = new ArrayList<>(); if (mCurFilter != null) { where = " name like ? "; args.add(mCurFilter + "%"); } String selection = (args.size() > 0) ? where : null; String[] selectionArgs = (args.size() > 0) ? args.toArray(new String[args.size()]) : null; return new CursorLoader(getActivity(), ((ResPartner) db()).liveSearchURI(), null, selection, selectionArgs, "name"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); if (data.getCount() > 0) { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setVisible(mView, R.id.swipe_container); OControls.setGone(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.swipe_container, Customers.this); } }, 500); } else { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setGone(mView, R.id.swipe_container); OControls.setVisible(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.customer_no_items, Customers.this); OControls.setImage(mView, R.id.icon, R.drawable.ic_action_customers); OControls.setText(mView, R.id.title, _s(R.string.label_no_customer_found)); OControls.setText(mView, R.id.subTitle, ""); } }, 500); if (db().isEmptyTable() && !syncRequested) { syncRequested = true; onRefresh(); } } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public void onItemClick(View view, final int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); if (row.getInt(OColumn.ROW_ID) == 0) { CustomerQuickCreater customerQuickCreater = new CustomerQuickCreater(new OnLiveSearchRecordCreateListener() { @Override public void recordCreated(ODataRow row) { Cursor cr = getActivity().getContentResolver() .query(db().uri(), null, "id = ?", new String[]{row.getString("id")} , null); cr.moveToFirst(); showSheet(cr); } }); customerQuickCreater.execute(row); } else showSheet((Cursor) mAdapter.getItem(position)); } private void showSheet(Cursor data) { if (mSheet != null) { mSheet.dismiss(); } BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.body_text_2)); builder.setTextColor(_c(R.color.body_text_2)); builder.setData(data); builder.actionListener(this); builder.setOnSheetMenuCreateListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); builder.menu(R.menu.menu_sheet_customer); mSheet = builder.create(); mSheet.show(); } @Override public void onSheetMenuCreate(Menu menu, Object o) { ODataRow row = OCursorUtils.toDatarow((Cursor) o); String address = ((ResPartner) db()).getAddress(row); if (address.equals("false") || TextUtils.isEmpty(address)) { menu.findItem(R.id.menu_customer_location).setVisible(false); } String contact = ResPartner.getContact(getActivity(), row.getInt(OColumn.ROW_ID)); if (contact.equals("false")) { menu.findItem(R.id.menu_customer_call).setVisible(false); } if (row.getString("email").equals("false")) { menu.findItem(R.id.menu_customer_send_message).setVisible(false); } } @Override public Class database() { return ResPartner.class; } @Override public List drawerMenus(Context context) { List items = new ArrayList(); items.add(new ODrawerItem(KEY).setTitle("Customers") .setIcon(R.drawable.ic_action_customers) .setInstance(new Customers())); return items; } @Override public void onStatusChange(Boolean refreshing) { // Sync Status getLoaderManager().restartLoader(0, null, this); } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(ResPartner.AUTHORITY); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); inflater.inflate(R.menu.menu_partners, menu); setHasSearchView(this, menu, R.id.menu_partner_search); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { } return super.onOptionsItemSelected(item); } @Override public boolean onSearchViewTextChange(String newFilter) { mCurFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { // nothing to do } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fabButton: loadActivity(null); break; } } @Override public void onItemClick(BottomSheet sheet, final MenuItem menu, final Object extras) { sheet.dismiss(); ODataRow row = OCursorUtils.toDatarow((Cursor) extras); switch (menu.getItemId()) { case R.id.menu_customer_opportunity: requestOpportunity(row.getInt(OColumn.ROW_ID), row.getString("name")); break; case R.id.menu_customer_leads: requestLeads(Type.Leads, row.getInt(OColumn.ROW_ID), row.getString("name")); break; case R.id.menu_customer_location: String address = ((ResPartner) db()).getAddress(OCursorUtils.toDatarow((Cursor) extras)); if (!address.equals("false") && !TextUtils.isEmpty(address)) IntentUtils.redirectToMap(getActivity(), address); else Toast.makeText(getActivity(), _s(R.string.label_no_location_found), Toast.LENGTH_LONG).show(); break; case R.id.menu_customer_call: String contact = ResPartner.getContact(getActivity(), row.getInt(OColumn.ROW_ID)); if (!contact.equals("false")) IntentUtils.requestCall(getActivity(), contact); else Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); break; case R.id.menu_customer_send_message: if (!row.getString("email").equals("false")) IntentUtils.requestMessage(getActivity(), row.getString("email")); else Toast.makeText(getActivity(), _s(R.string.label_no_email_found), Toast.LENGTH_LONG).show(); break; case R.id.menu_customer_schedule_call: Bundle extra = row.getPrimaryBundleData(); extra.putInt(PhoneCallDetail.KEY_OPPORTUNITY_ID, -1); extra.putBoolean(PhoneCallDetail.KEY_LOG_CALL_REQUEST, true); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); break; } } private void requestOpportunity(int row_id, String name) { Bundle extra = new Bundle(); extra.putBoolean(KEY_FILTER_REQUEST, true); extra.putInt(KEY_CUSTOMER_ID, row_id); extra.putString("name", name); startFragment(new CRMOpportunitiesPager(), true, extra); } private void requestLeads(Type type, int row_id, String name) { Bundle extra = new Bundle(); extra.putBoolean(KEY_FILTER_REQUEST, true); extra.putInt(KEY_CUSTOMER_ID, row_id); extra.putString(KEY_FILTER_TYPE, type.toString()); extra.putString("name", name); startFragment(new CRMLeads(), true, extra); } @Override public void onItemDoubleClick(View view, int position) { final ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); if (row.getInt(OColumn.ROW_ID) == 0) { CustomerQuickCreater customerQuickCreater = new CustomerQuickCreater(new OnLiveSearchRecordCreateListener() { @Override public void recordCreated(ODataRow row) { loadActivity(row); } }); customerQuickCreater.execute(row); } else loadActivity(row); } @Override public void onSheetActionClick(BottomSheet sheet, Object extras) { if (extras instanceof Cursor) { loadActivity(OCursorUtils.toDatarow((Cursor) extras)); } } private void loadActivity(ODataRow row) { Bundle data = null; if (row != null) { data = row.getPrimaryBundleData(); } IntentUtils.startActivity(getActivity(), CustomerDetails.class, data); } @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } private class CustomerQuickCreater extends AsyncTask { private ProgressDialog progressDialog; private OnLiveSearchRecordCreateListener mOnLiveSearchRecordCreateListener; public CustomerQuickCreater(OnLiveSearchRecordCreateListener listener) { mOnLiveSearchRecordCreateListener = listener; } @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(getActivity()); progressDialog.setTitle(R.string.title_working); progressDialog.setMessage(_s(R.string.title_please_wait)); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected ODataRow doInBackground(ODataRow... params) { try { Thread.sleep(500); return db().quickCreateRecord(params[0]); } catch (Exception e) { } return null; } @Override protected void onPostExecute(ODataRow row) { super.onPostExecute(row); progressDialog.dismiss(); getLoaderManager().restartLoader(0, null, Customers.this); if (mOnLiveSearchRecordCreateListener != null && row != null) { mOnLiveSearchRecordCreateListener.recordCreated(row); } } } public interface OnLiveSearchRecordCreateListener { public void recordCreated(ODataRow row); } } ================================================ FILE: app/src/main/java/com/odoo/addons/customers/providers/CustomersSyncProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 2/1/15 2:25 PM */ package com.odoo.addons.customers.providers; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.provider.BaseModelProvider; import com.odoo.core.support.OdooFields; import java.util.ArrayList; import java.util.List; import java.util.Locale; import odoo.ODomain; public class CustomersSyncProvider extends BaseModelProvider { public static final String TAG = CustomersSyncProvider.class.getSimpleName(); public static final int LIVE_SEARCHABLE_CUSTOMER = 116; @Override public boolean onCreate() { String path = new ResPartner(getContext(), null).getModelName().toLowerCase(Locale.getDefault()); matcher.addURI(authority(), path + "/live_searchable_customer", LIVE_SEARCHABLE_CUSTOMER); return super.onCreate(); } @Override public void setModel(Uri uri) { super.setModel(uri); mModel = new ResPartner(getContext(), getUser(uri)); } @Override public Cursor query(Uri uri, String[] base_projection, String selection, String[] selectionArgs, String sortOrder) { int match = matcher.match(uri); if (match != LIVE_SEARCHABLE_CUSTOMER) { return super.query(uri, base_projection, selection, selectionArgs, sortOrder); } ResPartner partner = new ResPartner(getContext(), null); Cursor cr = super.query(partner.uri(), base_projection, selection, selectionArgs, sortOrder); if (cr.getCount() <= 0) { String searchName = null; if (selectionArgs != null && selectionArgs.length > 0) { searchName = selectionArgs[selectionArgs.length - 1]; } if (searchName != null) { List records = getRecords(searchName, partner); if (records.size() > 0) { List keys = new ArrayList<>(); keys.addAll(records.get(0).keys()); keys.add(OColumn.ROW_ID); MatrixCursor cursor = new MatrixCursor(keys.toArray(new String[keys.size()])); for (ODataRow row : records) { List values = row.values(); values.add(0); cursor.addRow(values); } return cursor; } } } return cr; } @Override public String authority() { return ResPartner.AUTHORITY; } public List getRecords(String searchName, OModel model) { List items = new ArrayList<>(); try { OdooFields fields = new OdooFields(new String[]{"name", "image_small", "email"}); ODomain domain = new ODomain(); domain.add("name", "=ilike", "%" + searchName); List records = model.getServerDataHelper().searchRecords(fields, domain, 10); items.addAll(records); } catch (Exception e) { e.printStackTrace(); } return items; } } ================================================ FILE: app/src/main/java/com/odoo/addons/customers/services/CustomerSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 2/1/15 11:07 AM */ package com.odoo.addons.customers.services; import android.content.Context; import android.os.Bundle; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.service.OSyncService; import com.odoo.core.support.OUser; import odoo.ODomain; public class CustomerSyncService extends OSyncService { public static final String TAG = CustomerSyncService.class.getSimpleName(); @Override public OSyncAdapter getSyncAdapter(OSyncService service, Context context) { return new OSyncAdapter(context, ResPartner.class, service, true); } @Override public void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user) { if (adapter.getModel().getModelName().equals("res.partner")) { ODomain domain = new ODomain(); domain.add("|"); domain.add("|"); domain.add("opportunity_ids.user_id", "=", user.getUser_id()); domain.add("sale_order_ids.user_id", "=", user.getUser_id()); domain.add("id", "in", adapter.getModel().getServerIds()); adapter.setDomain(domain); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/customers/utils/ShareUtil.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 15/1/15 4:52 PM */ package com.odoo.addons.customers.utils; import android.content.Context; import android.content.Intent; import android.net.Uri; import com.odoo.core.orm.ODataRow; import com.odoo.core.utils.OStorageUtils; import java.io.File; import java.io.FileWriter; public class ShareUtil { public static final String TAG = ShareUtil.class.getSimpleName(); public static void shareContact(Context context, ODataRow row, Boolean view) { try { File vcfFile = new File(OStorageUtils.getDirectoryPath("file"), row.getString("name") + ".vcf"); FileWriter fw = new FileWriter(vcfFile); fw.write("BEGIN:VCARD\r\n"); fw.write("VERSION:3.0\r\n"); fw.write("N:" + row.getString("name") + ";\r\n"); fw.write("FN:" + row.getString("name") + "\r\n"); if (row.get("parent_id") instanceof Integer) { fw.write("ORG:" + row.getM2ORecord("parent_id").browse().getString("name") + "\r\n"); } if (!row.getString("phone").equals("false")) fw.write("TEL;TYPE=WORK,VOICE:" + row.getString("phone") + "\r\n"); if (!row.getString("mobile").equals("false")) fw.write("TEL;TYPE=HOME,VOICE:" + row.getString("mobile") + "\r\n"); String country = ""; if (row.get("country_id") instanceof Integer) { country = row.getM2ORecord("country_id").browse().getString("name"); } // if (!row.getString("street").equals("false") && !row.getString("street").equals("")) { // fw.write("ADR;TYPE=WORK:;;" + row.getString("street")); fw.write("ADR;TYPE=WORK:;;" + row.getString("street") + " " + row.getString("street2") + ";" + row.getString("city") + ";" + row.getString("zip") + ";" + country + "\r\n"); // } // if (!row.getString("street2").equals("false") && !row.getString("street2").equals("")) // fw.write("ADR;TYPE=WORK:;;" + " " + row.getString("street2")); // if (!row.getString("city").equals("false") && !row.getString("city").equals("")) // fw.write("ADR;TYPE=WORK:;;" + " " + row.getString("city")); // if (!row.getString("zip").equals("false") && !row.getString("zip").equals("")) // fw.write("ADR;TYPE=WORK:;;" + " " + row.getString("zip") + ";" + country); // if (!row.getString("email").equals("false") && !row.getString("email").equals("")) fw.write("EMAIL;TYPE=PREF,INTERNET:" + row.getString("email") + "\r\n"); fw.write("END:VCARD\r\n"); fw.close(); Intent i = new Intent(); if (view) { i.setAction(Intent.ACTION_SEND); i.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(vcfFile)); i.setType("text/x-vcard"); } else { i.setAction(Intent.ACTION_VIEW); i.setDataAndType(Uri.fromFile(vcfFile), "text/x-vcard"); } context.startActivity(i); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/PhoneCallDetail.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 4:55 PM */ package com.odoo.addons.phonecall; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.odoo.addons.calendar.utils.ReminderDialog; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.phonecall.features.receivers.PhoneStateReceiver; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.addons.phonecall.models.CRMPhoneCallsCategory; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; import com.odoo.core.utils.reminder.ReminderReceiver; import com.odoo.core.utils.reminder.ReminderUtils; import com.odoo.R; import java.util.Date; import odoo.controls.OField; import odoo.controls.OForm; public class PhoneCallDetail extends ActionBarActivity implements OField. IOnFieldValueChangeListener, ReminderDialog.OnReminderValueSelectListener, View.OnClickListener { public static final String TAG = PhoneCallDetail.class.getSimpleName(); public static final String KEY_LOG_CALL_REQUEST = "key_log_call_request"; public static final String KEY_PHONE_NUMBER = "key_phone_number"; public static final String KEY_OPPORTUNITY_ID = "key_opportunity_id"; private ActionBar actionBar; private Bundle extra; private OForm mForm; private ODataRow record; private CRMPhoneCalls crmPhoneCalls; private OField phoneCallDate, opportunity_id; private String logType = "done", type = null; private Boolean updateOpportunity = false; private CRMLead crmLead = null; private OForm opportunity_action_form; private ReminderDialog.ReminderItem mReminder; OValues values = null; public static final String KEY_RESCHEDULE = "key_reschedule"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crm_phonecall_detail); crmPhoneCalls = new CRMPhoneCalls(this, null); OActionBarUtils.setActionBar(this, true); actionBar = getSupportActionBar(); actionBar.setTitle(R.string.label_log_call); extra = getIntent().getExtras(); crmLead = new CRMLead(this, null); mReminder = ReminderDialog.getDefault(this, false); init(); } private void init() { mForm = (OForm) findViewById(R.id.phoneLogForm); opportunity_action_form = (OForm) findViewById(R.id.opportunity_action_form); phoneCallDate = (OField) findViewById(R.id.phoneCallDate); opportunity_id = (OField) findViewById(R.id.opportunity_id); opportunity_id.setOnValueChangeListener(this); phoneCallDate.setOnValueChangeListener(this); findViewById(R.id.reminderForPhoneCall).setOnClickListener(this); mForm.setEditable(true); if (extra != null) { String action = getIntent().getAction(); if (!extra.containsKey(KEY_LOG_CALL_REQUEST)) { if (extra.containsKey("opp_id")) { ODataRow opp_rec = new ODataRow(); opp_rec.put("opportunity_id", extra.getInt("opp_id")); boolean partner_edit = true; if (!crmLead.browse(extra.getInt("opp_id")).getString("partner_id"). equals("false")) { opp_rec.put("partner_id", crmLead.browse(extra.getInt("opp_id")). getInt("partner_id")); partner_edit = false; } mForm.initForm(opp_rec); ((OField) mForm.findViewById(R.id.partner_id)).setEditable(partner_edit); ((OField) mForm.findViewById(R.id.opportunity_id)).setEditable(false); return; } if (action != null) { if (action.equals(ReminderReceiver.ACTION_PHONE_CALL_REMINDER_CALLBACK)) { String contact = extra.getString("contact"); if (!contact.equals("false")) { IntentUtils.requestCall(this, contact); } else { Toast.makeText(this, R.string.label_no_contact_found, Toast.LENGTH_LONG).show(); } finish(); } if (action.equals(ReminderReceiver.ACTION_PHONE_CALL_REMINDER_DONE)) { OValues values = new OValues(); values.put("is_done", "1"); values.put("state", "done"); crmPhoneCalls.update(extra.getInt(OColumn.ROW_ID), values); crmPhoneCalls.setReminder(extra.getInt(OColumn.ROW_ID)); Toast.makeText(this, R.string.toast_phone_call_marked_done, Toast.LENGTH_LONG).show(); } ONotificationBuilder.cancelNotification(this, extra.getInt(OColumn.ROW_ID)); } // Record request record = crmPhoneCalls.browse(extra.getInt(OColumn.ROW_ID)); mForm.initForm(record); } else { // Logging new call if (action != null) { ONotificationBuilder.cancelNotification(this, extra.getInt("notification_id")); if (action.equals(PhoneStateReceiver.ACTION_CALL_BACK)) { String contactNumber = extra.getString(KEY_PHONE_NUMBER); IntentUtils.requestCall(this, contactNumber); finish(); } } ODataRow data_record = new ODataRow(); data_record.put("partner_id", extra.getInt(OColumn.ROW_ID)); data_record.put("partner_phone", extra.getString(KEY_PHONE_NUMBER)); int opp_id = extra.getInt(KEY_OPPORTUNITY_ID); data_record.put("opportunity_id", opp_id); data_record.put("date", ODateUtils.getCurrentDateWithHour(1)); if (extra.containsKey(PhoneStateReceiver.KEY_DURATION_START)) { long start_time = Long.parseLong(extra.getString( PhoneStateReceiver.KEY_DURATION_START)); long end_time = Long.parseLong(extra.getString( PhoneStateReceiver.KEY_DURATION_END)); long duration = (end_time - start_time); data_record.put("duration", ODateUtils.durationToFloat(duration)); } CRMPhoneCallsCategory.Type bound = CRMPhoneCallsCategory.Type.Inbound; if (!extra.getBoolean("in_bound", false)) { bound = CRMPhoneCallsCategory.Type.OutBound; } data_record.put("categ_id", CRMPhoneCallsCategory.getId(this, bound)); mForm.initForm(data_record); } } else { mForm.initForm(null); } String action = getIntent().getAction(); if (action != null && (action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_DONE) || action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_RE_SCHEDULE))) { ONotificationBuilder.cancelNotification(this, getIntent().getExtras(). getInt(OColumn.ROW_ID)); if (action.equals(ReminderReceiver.ACTION_EVENT_REMINDER_DONE)) { int row_id = getIntent().getExtras().getInt(OColumn.ROW_ID); OValues values = new OValues(); values.put("is_done", 1); crmPhoneCalls.update(row_id, values); Toast.makeText(this, R.string.toast_event_marked_done, Toast.LENGTH_LONG).show(); extra.remove(KEY_RESCHEDULE); } } if (extra != null && extra.containsKey(KEY_RESCHEDULE)) { new Handler().postDelayed(new Runnable() { @Override public void run() { onClick(findViewById(R.id.reminderForEvent)); } }, 500); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_phonecall_detail, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; case R.id.menu_phonecall_save: values = mForm.getValues(); if (values != null) { values.put("user_id", ResUsers.myId(this)); ResPartner partner = new ResPartner(this, null); ODataRow row = partner.browse(values.getInt("partner_id")); values.put("customer_name", row.getString("name")); ODataRow lead = crmLead.browse(values.getInt("opportunity_id")); values.put("lead_name", ""); if (lead != null) { values.put("lead_name", lead.getString("name")); } if (updateOpportunity) { OValues opp_values = opportunity_action_form.getValues(); if (opp_values != null) { crmLead.update(lead.getInt(OColumn.ROW_ID), opp_values); crmLead.setReminder(lead.getInt(OColumn.ROW_ID)); } } values.put("call_type", "false"); CRMPhoneCallsCategory category = new CRMPhoneCallsCategory(this, null); ODataRow categ_id = category.browse(values.getInt("categ_id")); if (categ_id != null) { values.put("call_type", categ_id.getString("name")); } values.put("state", logType); if (extra == null || extra.containsKey("opp_id") || extra.containsKey(KEY_LOG_CALL_REQUEST) || extra.containsKey("call_id")) { int row_id = crmPhoneCalls.insert(values); extra = new Bundle(); extra.putInt(OColumn.ROW_ID, row_id); Toast.makeText(this, type + " " + values.getString("name"), Toast.LENGTH_LONG).show(); } else { crmPhoneCalls.update(extra.getInt(OColumn.ROW_ID), values); crmPhoneCalls.setReminder(extra.getInt(OColumn.ROW_ID)); Toast.makeText(this, "Updated " + type + " " + values.getString("name"), Toast.LENGTH_LONG).show(); } if (type.equals(OResource.string(this, R.string.label_scheduled_call))) { setTimer(); } finish(); } break; } return super.onOptionsItemSelected(item); } private void setTimer() { Date now = new Date(); String format = ODateUtils.DEFAULT_FORMAT; Date date_start = ODateUtils.createDateObject(values.getString("date"), format, false); Date reminderDate = null; int row_id = extra.getInt(OColumn.ROW_ID); if (now.compareTo(date_start) < 0) { values.put("has_reminder", "true"); reminderDate = ReminderDialog.getReminderDateTime(values.getString("date"), false, mReminder); if (reminderDate != null) { values.put("reminder_datetime", ODateUtils.getDate(reminderDate, ODateUtils.DEFAULT_FORMAT)); } } Bundle extra = new Bundle(); extra.putInt(OColumn.ROW_ID, row_id); extra.putString(ReminderUtils.KEY_REMINDER_TYPE, "phonecall"); if (reminderDate != null) { if (ReminderUtils.get(getApplicationContext()).resetReminder(reminderDate, extra)) { Log.i(TAG, "Reminder added."); } } } @Override public void onFieldValueChange(OField field, Object value) { if (field.getFieldName().equals("opportunity_id")) { ODataRow lead = (ODataRow) value; updateOpportunity = false; if (!lead.getString("type").equals("lead")) { updateOpportunity = true; opportunity_action_form.loadChatter(false); opportunity_action_form.setEditable(true); opportunity_action_form.initForm(lead); } findViewById(R.id.opportunity_action_container).setVisibility( (updateOpportunity) ? View.VISIBLE : View.GONE); } else { if (!value.toString().equals("now()")) { Date selectedDate = ODateUtils.createDateObject(value.toString(), ODateUtils.DEFAULT_FORMAT, false); Date now = new Date(); if (now.compareTo(selectedDate) >= 0) { actionBar.setTitle(R.string.label_log_call); type = OResource.string(this, R.string.label_logged_call); logType = "done"; findViewById(R.id.reminderForPhoneCall).setVisibility(View.GONE); } else { findViewById(R.id.reminderForPhoneCall).setVisibility(View.VISIBLE); logType = "open"; type = OResource.string(this, R.string.label_scheduled_call); actionBar.setTitle(R.string.label_schedule_call); } } } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.reminderForPhoneCall: ReminderDialog dialog = new ReminderDialog(this, ReminderDialog.ReminderType.TimeBasedEvent); dialog.setOnReminderValueSelectListener(this); dialog.show(); break; } } @Override public void onReminderItemSelect(ReminderDialog.ReminderItem value) { ((TextView) findViewById(R.id.reminderTypeName)).setText(value.getTitle()); mReminder = value; } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/PhoneCalls.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:19 AM */ package com.odoo.addons.phonecall; import android.content.Context; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.odoo.R; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.dialog.OChoiceDialog; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import com.odoo.widgets.snackbar.SnackBar; import com.odoo.widgets.snackbar.SnackbarBuilder; import com.odoo.widgets.snackbar.listeners.EventListener; import java.util.ArrayList; import java.util.List; public class PhoneCalls extends BaseFragment implements OCursorListAdapter.OnViewBindListener, LoaderManager.LoaderCallbacks, SwipeRefreshLayout.OnRefreshListener, IOnSearchViewChangeListener, View.OnClickListener, ISyncStatusObserverListener, BottomSheetListeners.OnSheetItemClickListener, BottomSheetListeners. OnSheetActionClickListener, BottomSheetListeners.OnSheetMenuCreateListener, IOnItemClickListener, IOnBackPressListener, EventListener { public static final String TAG = PhoneCalls.class.getSimpleName(); private View mView; private ListView mList; private OCursorListAdapter mAdapter; private String mFilter = null; private boolean syncRequested = false; private BottomSheet mSheet = null; public enum Type { Logged, Scheduled } private Type mType = Type.Logged; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setHasOptionsMenu(true); return inflater.inflate(R.layout.common_listview, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; mType = Type.valueOf(getArguments().getString("type")); setHasSwipeRefreshView(mView, R.id.swipe_container, this); initAdapter(); } private void initAdapter() { mList = (ListView) mView.findViewById(R.id.listview); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.phonecall_item); mAdapter.setOnViewBindListener(this); mList.setAdapter(mAdapter); parent().setOnBackPressListener(this); setHasFloatingButton(mView, R.id.fabButton, mList, this); setHasSyncStatusObserver(TAG, this, db()); mAdapter.handleItemClickListener(mList, this); getLoaderManager().initLoader(0, null, this); } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { OControls.setText(view, R.id.name, row.getString("name")); String date = ODateUtils.convertToDefault(row.getString("date"), ODateUtils.DEFAULT_FORMAT, "MMM, dd hh:mm a"); OControls.setText(view, R.id.date, date); OControls.setText(view, R.id.state, db().getLabel("state", row.getString("state"))); if (!row.getString("description").equals("false")) { OControls.setVisible(view, R.id.description); OControls.setText(view, R.id.description, row.getString("description")); } else { OControls.setGone(view, R.id.description); } if (!row.getString("customer_name").equals("")) { OControls.setVisible(view, R.id.customer_name); OControls.setText(view, R.id.customer_name, row.getString("customer_name")); } else { OControls.setGone(view, R.id.customer_name); } if (row.getString("call_type").equals("false")) { OControls.setGone(view, R.id.call_type_icon); } else { OControls.setVisible(view, R.id.call_type_icon); if (row.getString("call_type").equals("Inbound")) { OControls.setImage(view, R.id.call_type_icon, R.drawable.ic_action_call_inbound); } else { OControls.setImage(view, R.id.call_type_icon, R.drawable.ic_action_call_outbound); } } if (row.getString("lead_name").equals("")) { OControls.setGone(view, R.id.lead_name); } else { OControls.setVisible(view, R.id.lead_name); OControls.setText(view, R.id.lead_name, row.getString("lead_name")); } } @Override public Loader onCreateLoader(int id, Bundle data) { String where; List args = new ArrayList<>(); if (mType == Type.Logged) { where = " state = ?"; args.add("done"); } else { where = " state != ?"; args.add("done"); } if (mFilter != null) { where += " and name like ? or lead_name like ? or customer_name like ?"; args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); } return new CursorLoader(getActivity(), db().uri(), null, where, (args.size() > 0) ? args.toArray(new String[args.size()]) : null, "date"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); if (data.getCount() > 0) { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setVisible(mView, R.id.swipe_container); OControls.setGone(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.swipe_container, PhoneCalls.this); } }, 500); } else { if (db().isEmptyTable() && !syncRequested) { syncRequested = true; onRefresh(); } new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setGone(mView, R.id.swipe_container); OControls.setVisible(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.customer_no_items, PhoneCalls.this); OControls.setImage(mView, R.id.icon, R.drawable.ic_action_customers); if (mType == Type.Logged) { OControls.setText(mView, R.id.title, _s(R.string.label_no_logged_calls_found)); OControls.setImage(mView, R.id.icon,R.drawable.ic_action_call_logs); } else { OControls.setText(mView, R.id.title, _s(R.string.label_no_scheduled_calls_found)); OControls.setImage(mView, R.id.icon,R.drawable.ic_action_schedule_call); } OControls.setText(mView, R.id.subTitle, ""); } }, 500); } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public List drawerMenus(Context context) { List menu = new ArrayList<>(); menu.add(new ODrawerItem(TAG) .setTitle(OResource.string(context, R.string.label_phone_calls)) .setGroupTitle()); menu.add(new ODrawerItem(TAG) .setTitle(OResource.string(context, R.string.label_logged_calls)) .setIcon(R.drawable.ic_action_call_logs) .setExtra(extra(Type.Logged)) .setInstance(new PhoneCalls())); menu.add(new ODrawerItem(TAG) .setTitle(OResource.string(context, R.string.label_scheduled_calls)) .setIcon(R.drawable.ic_action_schedule_call) .setExtra(extra(Type.Scheduled)) .setInstance(new PhoneCalls())); return menu; } private Bundle extra(Type type) { Bundle extra = new Bundle(); extra.putString("type", type.toString()); return extra; } @Override public Class database() { return CRMPhoneCalls.class; } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(CRMPhoneCalls.AUTHORITY); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); inflater.inflate(R.menu.menu_phonecalls, menu); setHasSearchView(this, menu, R.id.menu_phonecall_search); } @Override public boolean onSearchViewTextChange(String newFilter) { mFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { //Nothing to do } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fabButton: IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, null); break; } } @Override public void onStatusChange(Boolean refreshing) { getLoaderManager().restartLoader(0, null, this); } // @Override // public void onItemClick(AdapterView parent, View view, int position, long id) { // ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); // IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, row.getPrimaryBundleData()); // } @Override public void onItemClick(BottomSheet sheet, MenuItem menu, Object extras) { dismissSheet(sheet); actionEvent(menu, (Cursor) extras); } @Override public void onItemDoubleClick(View view, int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, row.getPrimaryBundleData()); } @Override public void onItemClick(View view, int position) { Cursor cr = (Cursor) mAdapter.getItem(position); showSheet(cr); } private void showSheet(Cursor data) { if (mSheet != null) { mSheet.dismiss(); } BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.body_text_2)); builder.setTextColor(_c(R.color.body_text_1)); builder.setData(data); builder.actionListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); builder.setOnSheetMenuCreateListener(this); builder.menu(R.menu.menu_dashboard_phonecalls); mSheet = builder.create(); mSheet.show(); } private void actionEvent(MenuItem menu, Cursor cr) { String is_done = cr.getString(cr.getColumnIndex("is_done")); final OValues values = new OValues(); values.put("_is_dirty", "false"); // to ignore update on server final int row_id = cr.getInt(cr.getColumnIndex(OColumn.ROW_ID)); values.put("is_done", (is_done.equals("0")) ? 1 : 0); String done_label = (is_done.equals("0")) ? "done" : "undone"; final ODataRow row = OCursorUtils.toDatarow(cr); Bundle data = row.getPrimaryBundleData(); switch (menu.getItemId()) { case R.id.menu_phonecall_call: int partner_id = cr.getInt(cr.getColumnIndex("partner_id")); if (partner_id != 0) { String contact = ResPartner.getContact(getActivity(), partner_id); if (contact != null && !contact.equals("false")) { IntentUtils.requestCall(getActivity(), contact); } else { Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); } } else { Toast.makeText(getActivity(), _s(R.string.label_no_contact_found), Toast.LENGTH_LONG).show(); } break; case R.id.menu_phonecall_reschedule: List choices = new ArrayList<>(); choices = new ArrayList<>(); choices.add("Re-Schedule call"); choices.add("Schedule other call"); OChoiceDialog.get(getActivity()).withOptions(choices, -1) .show(new OChoiceDialog.OnChoiceSelectListener() { @Override public void choiceSelected(int position, String value) { switch (position) { case 0: // Re-Schedule IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, row.getPrimaryBundleData()); break; case 1: // Schedule other call Bundle extra = row.getPrimaryBundleData(); extra.putInt("call_id", row.getInt(OColumn.ROW_ID)); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); break; } } }); break; // All done menu case R.id.menu_phonecall_all_done: final CRMPhoneCalls phone_call = new CRMPhoneCalls(getActivity(), null); values.put("state", "done"); phone_call.update(row_id, values); getLoaderManager().restartLoader(0, null, this); SnackBar.get(getActivity()).text(_s(R.string.toast_phone_call_marked_done)) .duration(SnackbarBuilder.SnackbarDuration.LENGTH_LONG) .withEventListener(this).show(); break; } } private void dismissSheet(final BottomSheet sheet) { new Handler().postDelayed(new Runnable() { @Override public void run() { sheet.dismiss(); } }, 100); } @Override public void onShow(int i) { hideFab(); } @Override public void onDismiss(int i) { showFab(); } @Override public void onSheetActionClick(BottomSheet sheet, final Object extras) { sheet.dismiss(); new Handler().postDelayed(new Runnable() { @Override public void run() { Cursor cr = (Cursor) extras; String data_type = cr.getString(cr.getColumnIndex("data_type")); int record_id = cr.getInt(cr.getColumnIndex(OColumn.ROW_ID)); Bundle extra = new Bundle(); extra.putInt(OColumn.ROW_ID, record_id); IntentUtils.startActivity(getActivity(), PhoneCallDetail.class, extra); } }, 250); } @Override public void onSheetMenuCreate(Menu menu, Object o) { if (mType == Type.Logged) menu.findItem(R.id.menu_phonecall_all_done).setVisible(false); } @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/features/CallerWindow.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 6:11 PM */ package com.odoo.addons.phonecall.features; import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.os.Handler; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import com.odoo.core.orm.ODataRow; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OPreferenceManager; import com.odoo.R; public class CallerWindow { public static final String TAG = CallerWindow.class.getSimpleName(); public static final String KEY_CALLER_WINDOW = "key_caller_window"; private WindowManager windowManager; private Context context; private OPreferenceManager mPref; private KeyguardManager keyguardManager; private View callerView = null; public CallerWindow(Context context) { this.context = context; mPref = new OPreferenceManager(context); windowManager = (WindowManager) context.getSystemService(Activity.WINDOW_SERVICE); keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); mPref.setBoolean(KEY_CALLER_WINDOW, false); } private WindowManager.LayoutParams getWindowParams() { WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP; return params; } private View buildView() { View view = LayoutInflater.from(context).inflate(R.layout.crm_caller_window_layout, null); return view; } public boolean inIdleMode() { return keyguardManager.inKeyguardRestrictedInputMode(); } public boolean isLollipop() { return (android.os.Build.VERSION.SDK_INT > 19); } private void bindView(ODataRow row) { OControls.setText(callerView, R.id.partner_name, row.getString("name")); OControls.setText(callerView, R.id.company_name, row.getString("company_name")); Bitmap bmp; if (row.getString("image_small").equals("false")) { bmp = BitmapUtils.getAlphabetImage(context, row.getString("name")); } else { String base64; if (row.getString("large_image").equals("false")) { base64 = row.getString("image_small"); } else { base64 = row.getString("large_image"); } bmp = BitmapUtils.getBitmapImage(context, base64); } if (row.getString("lead_name").equals("false")) { row.put("lead_name", "No any lead found"); } if (row.getString("probability").equals("false")) row.put("probability", ""); OControls.setImage(callerView, R.id.customerImage, bmp); OControls.setText(callerView, R.id.leadName, row.getString("lead_name")); OControls.setText(callerView, R.id.oppProbability, row.getString("probability")); OControls.setText(callerView, R.id.partner_contact, row.getString("caller_contact")); } public void show(final Boolean dialed, final ODataRow row) { if (!mPref.getBoolean(KEY_CALLER_WINDOW, false)) { Log.i(TAG, "Showing caller window"); mPref.setBoolean(KEY_CALLER_WINDOW, true); new Handler().postDelayed(new Runnable() { @Override public void run() { callerView = buildView(); bindView(row); WindowManager.LayoutParams params = getWindowParams(); if (!dialed && !inIdleMode() && isLollipop()) { params.gravity = Gravity.BOTTOM; } windowManager.addView(callerView, params); } }, 1000); } } public void dismiss() { if (mPref.getBoolean(KEY_CALLER_WINDOW, false)) { Log.i(TAG, "Removing caller window"); mPref.setBoolean(KEY_CALLER_WINDOW, false); try { if (callerView != null) windowManager.removeViewImmediate(callerView); } catch (Exception e) { e.printStackTrace(); } } } public boolean isShowing() { return mPref.getBoolean(KEY_CALLER_WINDOW, false); } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/features/CustomerFinder.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 6:33 PM */ package com.odoo.addons.phonecall.features; import android.content.Context; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; import com.odoo.addons.crm.models.CRMLead; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.ServerDataHelper; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OdooFields; import java.util.List; import odoo.ODomain; public class CustomerFinder { public static final String TAG = CustomerFinder.class.getSimpleName(); private Context mContext; private IOnCustomerFindListener customerFindListener; private ResPartner resPartner; private CustomerFinderTask customerFinderTask; private Boolean dialed = false; public CustomerFinder(Context context) { mContext = context; resPartner = new ResPartner(context, null); } public void findCustomer(Boolean isDialed, String callerNumber) { Log.i(TAG, "Finding customer for " + callerNumber); dialed = isDialed; customerFinderTask = new CustomerFinderTask(); customerFinderTask.execute(callerNumber); } public void setOnCustomerFindListener(IOnCustomerFindListener listener) { customerFindListener = listener; } private class CustomerFinderTask extends AsyncTask { private String mContactLast2Chars = ""; private String mContactLast3Chars = ""; @Override protected ODataRow doInBackground(String... params) { String number = params[0].trim().replaceAll(" ", "").replace("+", ""); Log.d(TAG, "Checking for number " + number + " in database"); mContactLast2Chars = number.substring(number.length() - 2); mContactLast3Chars = number.substring(number.length() - 3); String where = "phone like ? or phone like ? or mobile like ? or mobile like ?"; String[] args = new String[]{"%" + mContactLast2Chars, "%" + mContactLast3Chars, "%" + mContactLast2Chars, "%" + mContactLast3Chars}; ODataRow partner = null; for (ODataRow row : resPartner.select(null, where, args)) { String partnerPhone = row.getString("phone").trim().replaceAll(" ", "") .replace("+", ""); String partnerMobile = row.getString("mobile").trim().replaceAll(" ", "") .replace("+", ""); if (!partnerPhone.equals("false") && (partnerPhone.contains(number) || number.contains(partnerPhone))) { partner = row; break; } if (!partnerMobile.equals("false") && (partnerMobile.contains(number) || number.contains(partnerMobile))) { partner = row; break; } } if (partner == null) { ServerDataHelper helper = resPartner.getServerDataHelper(); try { ODomain domain = new ODomain(); domain.add("|"); domain.add("|"); domain.add("phone", "=like", "%" + mContactLast2Chars); domain.add("phone", "=like", "%" + mContactLast3Chars); domain.add("|"); domain.add("mobile", "=like", "%" + mContactLast2Chars); domain.add("mobile", "=like", "%" + mContactLast3Chars); OdooFields fields = new OdooFields(resPartner.getColumns()); List partners = helper.searchRecords(fields, domain, 10); if (partners.size() > 0) { for (ODataRow row : partners) { String phone = row.getString("phone").trim(); String mobile = row.getString("mobile").trim(); String contact = ((phone.equals("false") || TextUtils .isEmpty(phone)) ? mobile : phone).trim().replaceAll(" ", "") .replace("+", ""); if (number.contains(contact) || contact.contains(number)) { return resPartner.quickCreateRecord(row); } } } else { Log.i(TAG, "No Customer found on server with number " + number); } } catch (Exception e) { e.printStackTrace(); } } else { // Finding Leads or Opportunity for customer in local CRMLead crmLead = new CRMLead(mContext, null); String projection[] = {"name", "company_currency", "probability", "planned_revenue", "type"}; List records = crmLead.select(projection, "partner_id = ?", new String[]{partner.getString(OColumn.ROW_ID)}, "type DESC"); if (records.size() > 0) { ODataRow record = records.get(0); String more = ""; if (records.size() > 1) { more = " and " + (records.size() - 1) + " more..."; } partner.put("lead_name", record.getString("name") + " " + more); partner.put("opportunity_id", record.getInt(OColumn.ROW_ID)); if (!record.getString("type").equals("lead")) { partner.put("probability", record.getString("planned_revenue") + " " + record.getM2ORecord("company_currency").browse().getString("symbol") + " at " + record.getString("probability") + "%"); } else { partner.put("probability", ""); } } } return partner; } @Override protected void onPostExecute(ODataRow row) { super.onPostExecute(row); if (row != null) { customerFindListener.onCustomerFind(dialed, row); } } } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/features/IOnCustomerFindListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 6:34 PM */ package com.odoo.addons.phonecall.features; import com.odoo.core.orm.ODataRow; public interface IOnCustomerFindListener { public void onCustomerFind(Boolean dialed, ODataRow row); } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/features/receivers/PhoneStateReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 6:12 PM */ package com.odoo.addons.phonecall.features.receivers; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import com.odoo.addons.phonecall.PhoneCallDetail; import com.odoo.addons.phonecall.features.CallerWindow; import com.odoo.addons.phonecall.features.CustomerFinder; import com.odoo.addons.phonecall.features.IOnCustomerFindListener; import com.odoo.core.orm.ODataRow; import com.odoo.core.support.OUser; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; import com.odoo.R; import java.util.Date; public class PhoneStateReceiver extends BroadcastReceiver implements IOnCustomerFindListener { public static final String TAG = PhoneStateReceiver.class.getSimpleName(); public static final String ACTION_CALL_BACK = "action_call_back"; public static final String ACTION_CALL_SCHEDULE = "action_call_schedule"; public static final Integer REQUEST_CALL_BACK = 5567; public static final Integer REQUEST_CALL_SCHEDULE = 5568; public static final String KEY_RECEIVED = "phone_state_received"; public static final String KEY_RINGING = "phone_state_ringing"; public static final String KEY_OFFHOOK = "phone_state_offhook"; public static final String KEY_DURATION_START = "key_duration_start"; public static final String KEY_DURATION_END = "key_duration_end"; public static final String KEY_ACTIVITY_STARTED = "key_activity_started"; private String callerNumber = null; private TelephonyManager telephonyManager; private OPreferenceManager mPref; private static CallerWindow callerWindow; private CustomerFinder customerFinder; private Context mContext; private Bundle extra = null; @Override public void onReceive(Context context, Intent intent) { mContext = context; if (OUser.current(mContext) != null) { Log.v(TAG, "Phone state received."); mPref = new OPreferenceManager(context); if (callerWindow == null) callerWindow = new CallerWindow(context); customerFinder = new CustomerFinder(context); customerFinder.setOnCustomerFindListener(this); if (mPref.getBoolean(KEY_RECEIVED, true) && !callerWindow.isShowing()) { mPref.setBoolean(KEY_RECEIVED, false); mPref.setBoolean(KEY_RINGING, false); } if (!mPref.getBoolean(KEY_RECEIVED, false)) { telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); Bundle bundle = intent.getExtras(); callerNumber = bundle.getString(TelephonyManager.EXTRA_INCOMING_NUMBER); if (callerNumber != null) { telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } mPref.setBoolean(KEY_RECEIVED, true); } } } PhoneStateListener phoneStateListener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { super.onCallStateChanged(state, incomingNumber); switch (state) { case TelephonyManager.CALL_STATE_IDLE: Log.i(TAG, callerNumber + " CALL_STATE_IDLE"); mPref.setBoolean(KEY_RECEIVED, false); mPref.setBoolean(KEY_RINGING, false); if (extra != null) { extra.putBoolean("in_bound", mPref.getBoolean("in_bound", false)); extra.putString(KEY_DURATION_END, new Date().getTime() + ""); } if (!mPref.getBoolean(KEY_OFFHOOK, false)) { showMissCallNotification(extra); } else { startLogCallActivity(extra); } if (callerWindow != null) callerWindow.dismiss(); callerWindow = null; customerFinder = null; extra = null; break; case TelephonyManager.CALL_STATE_OFFHOOK: Log.i(TAG, callerNumber + " CALL_STATE_OFFHOOK"); mPref.setBoolean(KEY_OFFHOOK, true); // Call Started (received or dialed) callStarted(); if (!mPref.getBoolean(KEY_RINGING, false)) { mPref.setBoolean("in_bound", false); if (customerFinder != null) customerFinder.findCustomer(true, callerNumber); } break; case TelephonyManager.CALL_STATE_RINGING: Log.i(TAG, callerNumber + " CALL_STATE_RINGING"); mPref.setBoolean(KEY_RINGING, true); mPref.setBoolean(KEY_OFFHOOK, false); mPref.setBoolean("notified", false); mPref.setBoolean("in_bound", true); if (customerFinder != null) customerFinder.findCustomer(false, callerNumber); break; } } }; private void showMissCallNotification(Bundle data) { mPref.setBoolean(KEY_OFFHOOK, false); if (data != null && !mPref.getBoolean("notified", false)) { mPref.setBoolean("notified", true); int notification_id = 55568; ONotificationBuilder builder = new ONotificationBuilder(mContext, notification_id); data.putInt("notification_id", notification_id); builder.setTitle(_s(R.string.label_missed_call_from_customer)); builder.setIcon(R.drawable.ic_action_user); builder.setText(data.getString("name")); ONotificationBuilder.NotificationAction callBack = new ONotificationBuilder.NotificationAction(R.drawable.ic_action_phone, "Call back", REQUEST_CALL_BACK, ACTION_CALL_BACK, PhoneCallDetail.class, data); builder.addAction(callBack); data.putBoolean(PhoneCallDetail.KEY_LOG_CALL_REQUEST, true); data.putString(PhoneCallDetail.KEY_PHONE_NUMBER, callerNumber); data.putInt(PhoneCallDetail.KEY_OPPORTUNITY_ID, data.getInt("opportunity_id")); ONotificationBuilder.NotificationAction scheduleCall = new ONotificationBuilder.NotificationAction(R.drawable.ic_action_reschedule, "Schedule", REQUEST_CALL_SCHEDULE, ACTION_CALL_SCHEDULE, PhoneCallDetail.class, data); builder.addAction(scheduleCall); builder.allowVibrate(false); builder.build().show(); } } public String _s(int res_id) { return OResource.string(mContext, res_id); } private void startLogCallActivity(Bundle data) { if (data != null && !mPref.getBoolean(KEY_ACTIVITY_STARTED, false)) { mPref.setBoolean(KEY_ACTIVITY_STARTED, true); Intent intent = new Intent(mContext, PhoneCallDetail.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); data.putBoolean(PhoneCallDetail.KEY_LOG_CALL_REQUEST, true); data.putString(PhoneCallDetail.KEY_PHONE_NUMBER, callerNumber); data.putInt(PhoneCallDetail.KEY_OPPORTUNITY_ID, data.getInt("opportunity_id")); intent.putExtras(data); mContext.startActivity(intent); } } private void callStarted() { if (extra != null && !extra.containsKey(KEY_DURATION_START)) { extra.putString(KEY_DURATION_START, new Date().getTime() + ""); } } @Override public void onCustomerFind(Boolean dialed, ODataRow row) { if (row != null) { extra = new Bundle(); extra = row.getPrimaryBundleData(); callStarted(); extra.putString("name", row.getString("name")); int row_id = (row.getString("opportunity_id").equals("false")) ? -1 : row.getInt("opportunity_id"); extra.putInt("opportunity_id", row_id); row.put("caller_contact", callerNumber); if(callerWindow!=null) { callerWindow.show(dialed, row); mPref.setBoolean(KEY_ACTIVITY_STARTED, false); } } } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/models/CRMPhoneCalls.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:20 AM */ package com.odoo.addons.phonecall.models; import android.content.Context; import android.net.Uri; import android.os.Bundle; import com.odoo.addons.crm.models.CRMLead; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OSelection; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.reminder.ReminderUtils; import org.json.JSONArray; import java.util.Date; import odoo.ODomain; public class CRMPhoneCalls extends OModel { public static final String TAG = CRMPhoneCalls.class.getSimpleName(); public static final String AUTHORITY = "com.odoo.core.crm.provider.content.sync.crm_phonecall"; private Context mContext; OColumn user_id = new OColumn("Responsible", ResUsers.class, OColumn.RelationType.ManyToOne).setRequired(); OColumn partner_id = new OColumn("Contact", ResPartner.class, OColumn.RelationType.ManyToOne).setRequired(); OColumn description = new OColumn("Description", OText.class); OColumn state = new OColumn("status", OSelection.class) .addSelection("open", "Confirmed") .addSelection("cancel", "Cancelled") .addSelection("pending", "Pending") .addSelection("done", "Held"); OColumn name = new OColumn("Call summary", OVarchar.class).setRequired(); OColumn duration = new OColumn("Duration", OFloat.class); OColumn categ_id = new OColumn("Category", CRMPhoneCallsCategory.class, OColumn.RelationType.ManyToOne); OColumn date = new OColumn("Date", ODateTime.class); OColumn opportunity_id = new OColumn("Lead/Opportunity", CRMLead.class, OColumn.RelationType.ManyToOne); OColumn call_audio_file = new OColumn("recorded audio file", OVarchar.class).setSize(200).setLocalColumn(); OColumn data_type = new OColumn("Data type", OVarchar.class).setSize(34) .setLocalColumn().setDefaultValue("phone_call"); OColumn is_done = new OColumn("Mark as Done", OInteger.class) .setLocalColumn().setDefaultValue("0"); OColumn partner_phone = new OColumn("Partner Phone", OVarchar.class).setSize(20); @Odoo.Functional(depends = {"opportunity_id"}, store = true, method = "storeLeadName") OColumn lead_name = new OColumn("Lead", OVarchar.class).setSize(100) .setLocalColumn(); @Odoo.Functional(depends = {"user_id"}, store = true, method = "storeUserName") OColumn user_name = new OColumn("Username", OVarchar.class).setSize(100) .setLocalColumn(); @Odoo.Functional(depends = {"partner_id"}, store = true, method = "storeCustomerName") OColumn customer_name = new OColumn("Username", OVarchar.class).setSize(100) .setLocalColumn(); @Odoo.Functional(depends = {"categ_id"}, store = true, method = "storeCallType") OColumn call_type = new OColumn("Call Type", OVarchar.class).setSize(100) .setLocalColumn(); OColumn has_reminder = new OColumn("Has reminder", OBoolean.class).setLocalColumn() .setDefaultValue("false"); OColumn reminder_datetime = new OColumn("Reminder type", ODateTime.class) .setDefaultValue("false").setLocalColumn(); OColumn color_index = new OColumn("Color index", OInteger.class).setSize(5) .setLocalColumn().setDefaultValue(6); public CRMPhoneCalls(Context context, OUser user) { super(context, "crm.phonecall", user); mContext = context; } @Override public Uri uri() { return buildURI(AUTHORITY); } @Override public ODomain defaultDomain() { ODomain domain = new ODomain(); domain.add("user_id", "=", getUser().getUser_id()); return domain; } public String storeLeadName(OValues values) { try { if (!values.getString("opportunity_id").equals("false")) { JSONArray opportunity_id = new JSONArray(values.getString("opportunity_id")); return opportunity_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return ""; } public String storeUserName(OValues values) { try { if (!values.getString("user_id").equals("false")) { JSONArray user_id = new JSONArray(values.getString("user_id")); return user_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return ""; } public String storeCustomerName(OValues values) { try { if (!values.getString("partner_id").equals("false")) { JSONArray partner_id = new JSONArray(values.getString("partner_id")); return partner_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return ""; } public String storeCallType(OValues values) { try { if (!values.getString("categ_id").equals("false")) { JSONArray categ_id = new JSONArray(values.getString("categ_id")); return categ_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return "false"; } public void setReminder(int row_id) { ODataRow row = browse(row_id); Date start_date = ODateUtils.createDateObject(row.getString("date"), ODateUtils.DEFAULT_FORMAT, false); Date now = new Date(); if (now.compareTo(start_date) < 0) { Bundle extra = row.getPrimaryBundleData(); extra.putString(ReminderUtils.KEY_REMINDER_TYPE, "phonecall"); if (ReminderUtils.get(mContext).resetReminder(start_date, extra)) { OValues values = new OValues(); values.put("_is_dirty", "false"); values.put("has_reminder", "true"); update(row_id, values); } } } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/models/CRMPhoneCallsCategory.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 4:27 PM */ package com.odoo.addons.phonecall.models; import android.content.Context; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import odoo.ODomain; public class CRMPhoneCallsCategory extends OModel { public static final String TAG = CRMPhoneCallsCategory.class.getSimpleName(); public enum Type { Inbound, OutBound } OColumn name = new OColumn("Name", OVarchar.class); public CRMPhoneCallsCategory(Context context, OUser user) { super(context, "crm.phonecall.category", user); String serie = getOdooVersion().getServer_serie(); if (getOdooVersion().getVersion_number() < 9 && !serie.equals("8.saas~6")) { setModelName("crm.case.categ"); } } @Override public ODomain defaultDomain() { ODomain domain = new ODomain(); domain.add("object_id.model", "=", getModelName()); return domain; } public static int getId(Context context, Type type) { int id = 0; CRMPhoneCallsCategory category = new CRMPhoneCallsCategory(context, null); if (category.count(null, null) > 0) { ODataRow row = category.browse(new String[]{}, "name = ?", new String[]{ (type == Type.Inbound) ? "Inbound" : "Outbound" }); if (row != null) { id = row.getInt(OColumn.ROW_ID); } } return id; } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/providers/PhoneCallProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 4:58 PM */ package com.odoo.addons.phonecall.providers; import android.net.Uri; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.core.orm.provider.BaseModelProvider; public class PhoneCallProvider extends BaseModelProvider { public static final String TAG = PhoneCallProvider.class.getSimpleName(); @Override public void setModel(Uri uri) { super.setModel(uri); mModel = new CRMPhoneCalls(getContext(), getUser(uri)); } @Override public String authority() { return CRMPhoneCalls.AUTHORITY; } } ================================================ FILE: app/src/main/java/com/odoo/addons/phonecall/services/PhoneCallSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 4:59 PM */ package com.odoo.addons.phonecall.services; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.service.ISyncFinishListener; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.service.OSyncService; import com.odoo.core.support.OUser; import java.util.List; import odoo.ODomain; public class PhoneCallSyncService extends OSyncService implements ISyncFinishListener { public static final String TAG = PhoneCallSyncService.class.getSimpleName(); private Context mContext; @Override public OSyncAdapter getSyncAdapter(OSyncService service, Context context) { mContext = context; return new OSyncAdapter(context, CRMPhoneCalls.class, service, true); } @Override public void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user) { if (adapter.getModel().getModelName().equals("crm.phonecall")) { ODomain domain = new ODomain(); domain.add("user_id", "=", user.getUser_id()); adapter.setDomain(domain).syncDataLimit(10); adapter.onSyncFinish(this).syncDataLimit(50); } } @Override public OSyncAdapter performNextSync(OUser user, SyncResult syncResult) { CRMPhoneCalls crmPhoneCalls = new CRMPhoneCalls(mContext, user); List rows = crmPhoneCalls.select(new String[]{}); for (ODataRow row : rows) { crmPhoneCalls.setReminder(row.getInt(OColumn.ROW_ID)); } return null; } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/AddProductLineWizard.java ================================================ package com.odoo.addons.sale; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import com.odoo.R; import com.odoo.addons.sale.models.ProductProduct; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.ServerDataHelper; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OdooFields; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import odoo.ODomain; import odoo.controls.IOnQuickRecordCreateListener; public class AddProductLineWizard extends ActionBarActivity implements AdapterView.OnItemClickListener, TextWatcher, View.OnClickListener, OListAdapter.OnSearchChange, IOnQuickRecordCreateListener, AdapterView.OnItemLongClickListener { private ProductProduct productProduct; private EditText edt_searchable_input; private ListView mList = null; private OListAdapter mAdapter; private List objects = new ArrayList<>(); private List localItems = new ArrayList<>(); private int selected_position = -1; private LiveSearch mLiveDataLoader = null; private OColumn mCol = null; private HashMap lineValues = new HashMap<>(); private Boolean mLongClicked = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sale_add_item); setResult(RESULT_CANCELED); productProduct = new ProductProduct(this, null); edt_searchable_input = (EditText) findViewById(R.id.edt_searchable_input); edt_searchable_input.addTextChangedListener(this); findViewById(R.id.done).setOnClickListener(this); Bundle extra = getIntent().getExtras(); if (extra != null) { mList = (ListView) findViewById(R.id.searchable_items); mList.setOnItemClickListener(this); mList.setOnItemLongClickListener(this); for (String key : extra.keySet()) { lineValues.put(key, extra.getFloat(key)); } for (Object local : productProduct.select()) { ODataRow product = (ODataRow) local; if (lineValues.containsKey(product.getString("id") + "")) { localItems.add(0, product); } else { localItems.add(product); } } objects.addAll(localItems); mAdapter = new OListAdapter(this, R.layout.sale_product_line_item, objects) { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) v = getLayoutInflater().inflate(getResource(), parent, false); generateView(v, position); return v; } }; mAdapter.setOnSearchChange(this); mList.setAdapter(mAdapter); } else { finish(); } } private void generateView(View v, int position) { final ODataRow row = (ODataRow) objects.get(position); Float qty = (lineValues.containsKey(row.getString("id")) && lineValues.get(row.getString("id")) > 0) ? lineValues.get(row.getString("id")) : 0; if (qty <= 0) { OControls.setGone(v, R.id.productQty); OControls.setGone(v, R.id.remove_qty); } else { OControls.setVisible(v, R.id.remove_qty); v.findViewById(R.id.remove_qty).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Float lineQty = lineValues.get(row.getString("id")); lineValues.put(row.getString("id"), lineQty - 1); mAdapter.notifiyDataChange(objects); } }); OControls.setVisible(v, R.id.productQty); OControls.setText(v, R.id.productQty, qty + " "); } OControls.setText(v, R.id.productName, row.getString(productProduct.getDefaultNameColumn())); if (row.contains(OColumn.ROW_ID) && selected_position == row.getInt(OColumn.ROW_ID)) { v.setBackgroundColor(getResources().getColor( R.color.control_pressed)); } else { v.setBackgroundColor(Color.TRANSPARENT); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { ODataRow data = (ODataRow) objects.get(position); int row_id = productProduct.selectRowId(data.getInt("id")); if (row_id != -1) { data.put(OColumn.ROW_ID, row_id); } if (!data.contains(OColumn.ROW_ID)) { QuickCreateRecordProcess quickCreateRecordProcess = new QuickCreateRecordProcess(this); quickCreateRecordProcess.execute(data); } else { onRecordCreated(data); } } @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { ODataRow data = (ODataRow) objects.get(position); mLongClicked = true; int row_id = productProduct.selectRowId(data.getInt("id")); if (row_id != -1) { data.put(OColumn.ROW_ID, row_id); } if (!data.contains(OColumn.ROW_ID)) { QuickCreateRecordProcess quickCreateRecordProcess = new QuickCreateRecordProcess(this); quickCreateRecordProcess.execute(data); } else { onLongClicked(data); } return true; } private void onLongClicked(final ODataRow row) { mLongClicked = false; final Float count = ((lineValues.containsKey(row.getString("id"))) ? lineValues.get(row.getString("id")) : 0); OAlert.inputDialog(this, "Quantity", new OAlert.OnUserInputListener() { @Override public void onViewCreated(EditText inputView) { inputView.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); inputView.setText(count + ""); } @Override public void onUserInputted(Object value) { float userData = Float.parseFloat(value.toString()); lineValues.put(row.getString("id"), userData); mAdapter.notifiyDataChange(objects); } }); } @Override public void onRecordCreated(ODataRow row) { if (!mLongClicked) { Float count = ((lineValues.containsKey(row.getString("id"))) ? lineValues.get(row.getString("id")) : 0); lineValues.put(row.getString("id"), ++count); mAdapter.notifiyDataChange(objects); } else { onLongClicked(row); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { mAdapter.getFilter().filter(s); ImageView imgView = (ImageView) findViewById(R.id.search_icon); if (s.length() > 0) { imgView.setImageResource(R.drawable.ic_action_navigation_close); imgView.setOnClickListener(this); imgView.setClickable(true); } else { imgView.setClickable(false); imgView.setImageResource(R.drawable.ic_action_search); imgView.setOnClickListener(null); } } @Override public void afterTextChanged(Editable s) { } @Override public void onClick(View v) { switch (v.getId()) { case R.id.done: Bundle data = new Bundle(); for (String key : lineValues.keySet()) { data.putFloat(key, lineValues.get(key)); } Intent intent = new Intent(); intent.putExtras(data); setResult(RESULT_OK, intent); finish(); break; default: setResult(RESULT_CANCELED); finish(); } } @Override public void onSearchChange(List newRecords) { if (newRecords.size() <= 2) { if (mLiveDataLoader != null) mLiveDataLoader.cancel(true); if (edt_searchable_input.getText().length() >= 2) { mLiveDataLoader = new LiveSearch(); mLiveDataLoader.execute(edt_searchable_input.getText() .toString()); } } } private class LiveSearch extends AsyncTask> { @Override protected void onPreExecute() { super.onPreExecute(); findViewById(R.id.loading_progress).setVisibility(View.VISIBLE); mList.setVisibility(View.GONE); } @Override protected List doInBackground(String... params) { try { ServerDataHelper helper = productProduct.getServerDataHelper(); ODomain domain = new ODomain(); // domain.add(productProduct.getDefaultNameColumn(), "ilike", params[0]); domain.add("id", "not in", productProduct.getServerIds()); if (mCol != null) { for (String key : mCol.getDomains().keySet()) { // domain.add("sale_ok", "=", true); } } OdooFields fields = new OdooFields(productProduct.getColumns()); //return helper.searchRecords(fields, domain, 10); return helper.nameSearch(params[0], domain, 10); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(List result) { super.onPostExecute(result); findViewById(R.id.loading_progress).setVisibility(View.GONE); mList.setVisibility(View.VISIBLE); if (result != null && result.size() > 0) { objects.clear(); objects.addAll(localItems); objects.addAll(result); mAdapter.notifiyDataChange(objects); } } @Override protected void onCancelled() { super.onCancelled(); findViewById(R.id.loading_progress).setVisibility(View.GONE); mList.setVisibility(View.VISIBLE); } } private class QuickCreateRecordProcess extends AsyncTask { private ProgressDialog progressDialog; IOnQuickRecordCreateListener mOnQuickRecordCreateListener = null; public QuickCreateRecordProcess(IOnQuickRecordCreateListener listener) { mOnQuickRecordCreateListener = listener; } @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(AddProductLineWizard.this); progressDialog.setTitle(R.string.title_please_wait); progressDialog.setMessage(OResource.string(AddProductLineWizard.this, R.string.title_working)); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected ODataRow doInBackground(ODataRow... params) { try { Thread.sleep(700); return productProduct.quickCreateRecord(params[0]); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(ODataRow data) { super.onPostExecute(data); if (data != null && mOnQuickRecordCreateListener != null) { mOnQuickRecordCreateListener.onRecordCreated(data); } progressDialog.dismiss(); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/Sales.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:06 AM */ package com.odoo.addons.sale; import android.content.Context; import android.database.Cursor; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SwipeRefreshLayout; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.odoo.R; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.core.orm.ODataRow; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.addons.fragment.IOnSearchViewChangeListener; import com.odoo.core.support.addons.fragment.ISyncStatusObserverListener; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.IOnItemClickListener; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.sys.IOnBackPressListener; import com.odoo.widgets.bottomsheet.BottomSheet; import com.odoo.widgets.bottomsheet.BottomSheetListeners; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Sales extends BaseFragment implements OCursorListAdapter.OnViewBindListener, LoaderManager.LoaderCallbacks, SwipeRefreshLayout.OnRefreshListener, IOnSearchViewChangeListener, ISyncStatusObserverListener, IOnItemClickListener, BottomSheetListeners.OnSheetItemClickListener, BottomSheetListeners.OnSheetActionClickListener, IOnBackPressListener, View.OnClickListener { public static final String TAG = Sales.class.getSimpleName(); public static final String KEY_MENU = "key_sales_menu"; private View mView; private ListView mList; private OCursorListAdapter mAdapter; private String mFilter = null; private BottomSheet mSheet; private Type mType = Type.Quotation; private Boolean mSyncRequested = false; public enum Type { Quotation, SaleOrder } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setHasOptionsMenu(true); mType = Type.valueOf(getArguments().getString(KEY_MENU)); return inflater.inflate(R.layout.common_listview, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); parent().setOnBackPressListener(this); mView = view; initAdapter(); } private void initAdapter() { mList = (ListView) mView.findViewById(R.id.listview); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.sale_order_item); mAdapter.setOnViewBindListener(this); mList.setAdapter(mAdapter); mAdapter.handleItemClickListener(mList, this); setHasFloatingButton(mView, R.id.fabButton, mList, this); if (mType == Type.SaleOrder) mView.findViewById(R.id.fabButton).setVisibility(View.GONE); setHasSyncStatusObserver(TAG, this, db()); setHasSwipeRefreshView(mView, R.id.swipe_container, this); getLoaderManager().initLoader(0, null, this); } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { OControls.setText(view, R.id.name, row.getString("name")); String format = (db().getUser().getVersion_number() <= 7) ? ODateUtils.DEFAULT_DATE_FORMAT : ODateUtils.DEFAULT_FORMAT; String date = ODateUtils.convertToDefault(row.getString("date_order"), format, "MMMM, dd"); OControls.setText(view, R.id.date_order, date); OControls.setText(view, R.id.state, row.getString("state_title")); if (row.getString("partner_name").equals("false")) { OControls.setGone(view, (R.id.partner_name)); } else { OControls.setVisible(view, R.id.partner_name); OControls.setText(view, R.id.partner_name, row.getString("partner_name")); } OControls.setText(view, R.id.amount_total, row.getString("amount_total")); if (row.getString("currency_symbol").equals("false")) { OControls.setGone(view, (R.id.currency_symbol)); } else { OControls.setVisible(view, R.id.currency_symbol); OControls.setText(view, R.id.currency_symbol, row.getString("currency_symbol")); } OControls.setText(view, R.id.order_lines, row.getString("order_line_count")); } @Override public List drawerMenus(Context context) { List menu = new ArrayList<>(); menu.add(new ODrawerItem(TAG).setTitle(OResource.string(context, R.string.label_quotation)) .setIcon(R.drawable.ic_action_quotation) .setInstance(new Sales()) .setExtra(data(Type.Quotation))); menu.add(new ODrawerItem(TAG).setTitle(OResource.string(context, R.string.label_sale_orders)) .setIcon(R.drawable.ic_action_sale_order) .setInstance(new Sales()) .setExtra(data(Type.SaleOrder))); return menu; } @Override public Loader onCreateLoader(int id, Bundle data) { String where = null; String[] whereArgs = null; List args = new ArrayList<>(); switch (mType) { case Quotation: where = " (state = ? or state = ?)"; args.addAll(Arrays.asList(new String[]{"draft", "cancel"})); break; case SaleOrder: where = "(state = ? or state = ? or state = ?)"; args.addAll(Arrays.asList(new String[]{"manual", "progress", "done"})); break; } if (mFilter != null) { where += " and (name like ? or partner_name like ? or state_title like ?)"; args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); args.add("%" + mFilter + "%"); } whereArgs = args.toArray(new String[args.size()]); return new CursorLoader(getActivity(), db().uri(), null, where, whereArgs, "date_order DESC"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); if (data.getCount() > 0) { new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setVisible(mView, R.id.swipe_container); OControls.setGone(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.swipe_container, Sales.this); } }, 500); } else { if (db().isEmptyTable() && !mSyncRequested) { mSyncRequested = true; onRefresh(); } new Handler().postDelayed(new Runnable() { @Override public void run() { OControls.setGone(mView, R.id.loadingProgress); OControls.setGone(mView, R.id.swipe_container); OControls.setVisible(mView, R.id.customer_no_items); setHasSwipeRefreshView(mView, R.id.customer_no_items, Sales.this); OControls.setImage(mView, R.id.icon, (mType == Type.Quotation) ? R.drawable.ic_action_quotation : R.drawable.ic_action_sale_order); OControls.setText(mView, R.id.title, "No " + mType + " Found"); OControls.setText(mView, R.id.subTitle, ""); } }, 500); } } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } private Bundle data(Type type) { Bundle extra = new Bundle(); extra.putString(KEY_MENU, type.toString()); return extra; } @Override public Class database() { return SaleOrder.class; } @Override public void onRefresh() { if (inNetwork()) { parent().sync().requestSync(SaleOrder.AUTHORITY); setSwipeRefreshing(true); } else { hideRefreshingProgress(); Toast.makeText(getActivity(), _s(R.string.toast_network_required), Toast.LENGTH_LONG) .show(); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); inflater.inflate(R.menu.menu_sales_order, menu); setHasSearchView(this, menu, R.id.menu_sales_search); } @Override public boolean onSearchViewTextChange(String newFilter) { mFilter = newFilter; getLoaderManager().restartLoader(0, null, this); return true; } @Override public void onSearchViewClose() { //Nothing to do } @Override public void onStatusChange(Boolean refreshing) { getLoaderManager().restartLoader(0, null, this); } @Override public void onItemDoubleClick(View view, int position) { onDoubleClick(position); } private void onDoubleClick(int position) { ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); Bundle data = row.getPrimaryBundleData(); data.putString("type", mType.toString()); IntentUtils.startActivity(getActivity(), SalesDetail.class, data); } @Override public void onItemClick(View view, int position) { if (mType == Type.Quotation) showSheet((Cursor) mAdapter.getItem(position)); else onDoubleClick(position); } private void showSheet(Cursor data) { BottomSheet.Builder builder = new BottomSheet.Builder(getActivity()); builder.listener(this); builder.setIconColor(_c(R.color.theme_primary_dark)); builder.setTextColor(Color.parseColor("#414141")); builder.setData(data); builder.actionListener(this); builder.setActionIcon(R.drawable.ic_action_edit); builder.title(data.getString(data.getColumnIndex("name"))); if (data.getString(data.getColumnIndex("state")).equals("cancel")) builder.menu(R.menu.menu_quotation_cancel_sheet); else builder.menu(R.menu.menu_quotation_sheet); mSheet = builder.create(); mSheet.show(); } @Override public void onItemClick(BottomSheet sheet, MenuItem menu, Object extras) { mSheet.dismiss(); ODataRow row = OCursorUtils.toDatarow((Cursor) extras); switch (menu.getItemId()) { // case R.id.menu_so_send_by_email: // break; case R.id.menu_quotation_cancel: ((SaleOrder) db()).cancelOrder(mType, row, cancelOrder); break; case R.id.menu_quotation_new: if (inNetwork()) { ((SaleOrder) db()).newCopyQuotation(row, newCopyQuotation); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } break; case R.id.menu_so_confirm_sale: if (row.getFloat("amount_total") > 0) { if (inNetwork()) { ((SaleOrder) db()).confirmSale(row, confirmSale); } else { Toast.makeText(getActivity(), R.string.toast_network_required, Toast.LENGTH_LONG).show(); } } else { OAlert.showWarning(getActivity(), "You cannot a sales order which has no line"); } break; } } SaleOrder.OnOperationSuccessListener cancelOrder = new SaleOrder.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), mType + " cancelled", Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; SaleOrder.OnOperationSuccessListener confirmSale = new SaleOrder.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), "Quotation confirmed !", Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; SaleOrder.OnOperationSuccessListener newCopyQuotation = new SaleOrder.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(getActivity(), R.string.label_copy_quotation, Toast.LENGTH_LONG).show(); } @Override public void OnCancelled() { } }; @Override public void onSheetActionClick(BottomSheet sheet, Object extras) { mSheet.dismiss(); ODataRow row = OCursorUtils.toDatarow((Cursor) extras); Bundle data = row.getPrimaryBundleData(); data.putString("type", mType.toString()); IntentUtils.startActivity(getActivity(), SalesDetail.class, data); } @Override public boolean onBackPressed() { if (mSheet != null && mSheet.isShowing()) { mSheet.dismiss(); return false; } return true; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fabButton: Bundle bundle = new Bundle(); bundle.putString("type", Type.Quotation.toString()); IntentUtils.startActivity(getActivity(), SalesDetail.class, bundle); break; } } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/SalesDetail.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 5:09 PM */ package com.odoo.addons.sale; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.odoo.App; import com.odoo.R; import com.odoo.addons.sale.models.ProductProduct; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.addons.sale.models.SalesOrderLine; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.ServerDataHelper; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import odoo.OArguments; import odoo.controls.ExpandableListControl; import odoo.controls.OField; import odoo.controls.OForm; import static com.odoo.addons.sale.Sales.Type; public class SalesDetail extends ActionBarActivity implements View.OnClickListener { public static final String TAG = SalesDetail.class.getSimpleName(); public static final int REQUEST_ADD_ITEMS = 323; private Bundle extra; private OForm mForm; private ODataRow record; private SaleOrder sale; private ActionBar actionBar; private ExpandableListControl mList; private ExpandableListControl.ExpandableListAdapter mAdapter; private List objects = new ArrayList<>(); private HashMap lineValues = new HashMap<>(); private HashMap lineIds = new HashMap<>(); private TextView txvType, currency1, currency2, currency3, untaxedAmt, taxesAmt, total_amt; private ODataRow currencyObj; private ResPartner partner = null; private ProductProduct products = null; private String mSOType = ""; private LinearLayout layoutAddItem = null; private Type mType; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sale_detail); OActionBarUtils.setActionBar(this, true); actionBar = getSupportActionBar(); sale = new SaleOrder(this, null); extra = getIntent().getExtras(); mType = Type.valueOf(extra.getString("type")); currencyObj = sale.currency(); partner = new ResPartner(this, null); products = new ProductProduct(this, null); init(); initAdapter(); } private void init() { mForm = (OForm) findViewById(R.id.saleForm); mForm.setEditable(true); txvType = (TextView) findViewById(R.id.txvType); currency1 = (TextView) findViewById(R.id.currency1); currency2 = (TextView) findViewById(R.id.currency2); currency3 = (TextView) findViewById(R.id.currency3); String currencySymbol = currencyObj.getString("symbol"); untaxedAmt = (TextView) findViewById(R.id.untaxedTotal); taxesAmt = (TextView) findViewById(R.id.taxesTotal); total_amt = (TextView) findViewById(R.id.fTotal); untaxedAmt.setText("0.00"); taxesAmt.setText("0.00"); total_amt.setText("0.00"); layoutAddItem = (LinearLayout) findViewById(R.id.layoutAddItem); layoutAddItem.setOnClickListener(this); if (extra == null || !extra.containsKey(OColumn.ROW_ID)) { mForm.initForm(null); actionBar.setTitle(R.string.label_new); actionBar.setHomeAsUpIndicator(R.drawable.ic_action_navigation_close); txvType.setText(R.string.label_quotation); } else { record = sale.browse(extra.getInt(OColumn.ROW_ID)); if (record == null) { finish(); } if (!record.getString("partner_id").equals("false") && mType == Type.Quotation) { OnCustomerChangeUpdate onCustomerChangeUpdate = new OnCustomerChangeUpdate(); onCustomerChangeUpdate.execute(record.getM2ORecord("partner_id").browse()); } if (mType == Type.Quotation) { actionBar.setTitle(R.string.label_quotation); txvType.setText(R.string.label_quotation); if (record.getString("state").equals("cancel")) layoutAddItem.setVisibility(View.GONE); } else { layoutAddItem.setVisibility(View.GONE); actionBar.setTitle(R.string.label_sale_orders); txvType.setText(R.string.label_sale_orders); mForm.setEditable(false); } currencySymbol = record.getM2ORecord("currency_id").browse().getString("symbol"); untaxedAmt.setText(String.format("%.2f", record.getFloat("amount_untaxed"))); taxesAmt.setText(String.format("%.2f", record.getFloat("amount_tax"))); total_amt.setText(String.format("%.2f", record.getFloat("amount_total"))); mForm.initForm(record); } mSOType = txvType.getText().toString(); currency1.setText(currencySymbol); currency2.setText(currencySymbol); currency3.setText(currencySymbol); } private void initAdapter() { mList = (ExpandableListControl) findViewById(R.id.expListOrderLine); mList.setVisibility(View.VISIBLE); if (extra != null && record != null) { List lines = record.getO2MRecord("order_line").browseEach(); for (ODataRow line : lines) { int product_id = products.selectServerId(line.getInt("product_id")); if (product_id != 0) { lineValues.put(product_id + "", line.getFloat("product_uom_qty")); lineIds.put(product_id + "", line.getInt("id")); } } objects.addAll(lines); } mAdapter = mList.getAdapter(R.layout.sale_order_line_item, objects, new ExpandableListControl.ExpandableListAdapterGetViewListener() { @Override public View getView(int position, View mView, ViewGroup parent) { ODataRow row = (ODataRow) mAdapter.getItem(position); OControls.setText(mView, R.id.edtName, row.getString("name")); OControls.setText(mView, R.id.edtProductQty, row.getString("product_uom_qty")); OControls.setText(mView, R.id.edtProductPrice, String.format("%.2f", row.getFloat("price_unit"))); OControls.setText(mView, R.id.edtSubTotal, String.format("%.2f", row.getFloat("price_subtotal"))); return mView; } }); mAdapter.notifyDataSetChanged(objects); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_sale_detail, menu); OField name = (OField) mForm.findViewById(R.id.fname); name.setEditable(false); if (extra != null && !extra.getString("type").equals(Type.SaleOrder.toString())) { // Operation on Sale Order } else { menu.findItem(R.id.menu_sale_save).setVisible(false); menu.findItem(R.id.menu_sale_confirm_sale).setVisible(false); } if (extra != null && record != null && record.getString("state").equals("cancel")) { menu.findItem(R.id.menu_sale_save).setVisible(true).setTitle("Copy Quotation"); menu.findItem(R.id.menu_sale_detail_more).setVisible(false); mForm.setEditable(true); } else { menu.findItem(R.id.menu_sale_detail_more).setVisible(false); menu.findItem(R.id.menu_sale_new_copy_of_quotation).setVisible(false); } if (extra == null || !extra.containsKey(OColumn.ROW_ID)) { menu.findItem(R.id.menu_sale_save).setVisible(true); menu.findItem(R.id.menu_sale_detail_more).setVisible(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { OValues values = mForm.getValues(); App app = (App) getApplicationContext(); switch (item.getItemId()) { case android.R.id.home: finish(); break; case R.id.menu_sale_save: if (values != null) { if (app.inNetwork()) { values.put("partner_name", partner.getName(values.getInt("partner_id"))); SaleOrderOperation saleOrderOperation = new SaleOrderOperation(); saleOrderOperation.execute(values); } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } } break; case R.id.menu_sale_confirm_sale: if (record != null) { if (extra != null && record.getFloat("amount_total") > 0) { if (app.inNetwork()) { sale.confirmSale(record, confirmSale); } else { Toast.makeText(this, R.string.toast_network_required, Toast.LENGTH_LONG).show(); } } else { OAlert.showWarning(this, R.string.label_no_order_line + ""); } } break; } return super.onOptionsItemSelected(item); } private class SaleOrderOperation extends AsyncTask { private ProgressDialog mDialog; @Override protected void onPreExecute() { super.onPreExecute(); mDialog = new ProgressDialog(SalesDetail.this); mDialog.setTitle(R.string.title_working); mDialog.setMessage("Creating lines"); mDialog.setCancelable(false); mDialog.show(); } @Override protected Boolean doInBackground(OValues... params) { try { Thread.sleep(500); OValues values = params[0]; // Creating oneToMany order lines JSONArray order_line = new JSONArray(); for (Object line : objects) { JSONArray o_line = new JSONArray(); ODataRow row = (ODataRow) line; String product_id = row.getString("product_id"); o_line.put((lineIds.containsKey(product_id)) ? 1 : 0); o_line.put((lineIds.containsKey(product_id)) ? lineIds.get(product_id) : false); if (lineIds.containsKey(product_id)) { JSONObject line_data = new JSONObject(); line_data.put("product_uom_qty", row.get("product_uom_qty")); line_data.put("product_uos_qty", row.get("product_uos_qty")); o_line.put(line_data); } else o_line.put(JSONUtils.toJSONObject(row)); order_line.put(o_line); lineIds.remove(product_id); } if (lineIds.size() > 0) { for (String key : lineIds.keySet()) { JSONArray o_line = new JSONArray(); o_line.put(2); o_line.put(lineIds.get(key)); o_line.put(false); order_line.put(o_line); } } Thread.sleep(500); JSONObject data = new JSONObject(); data.put("name", values.getString("name")); data.put("partner_id", partner.selectServerId(values.getInt("partner_id"))); data.put("date_order", values.getString("date_order")); data.put("payment_term", values.get("payment_term")); data.put("order_line", order_line); if (record == null) { runOnUiThread(new Runnable() { @Override public void run() { mDialog.setMessage("Creating " + mSOType); } }); Thread.sleep(500); int new_id = sale.getServerDataHelper().createOnServer(data); values.put("id", new_id); ODataRow record = new ODataRow(); record.put("id", new_id); sale.quickCreateRecord(record); //sale.insert(values); } else { runOnUiThread(new Runnable() { @Override public void run() { mDialog.setMessage("Updating " + mSOType); } }); Thread.sleep(500); sale.getServerDataHelper().updateOnServer(data, record.getInt("id")); sale.quickCreateRecord(record); } return true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override protected void onPostExecute(Boolean success) { super.onPostExecute(success); mDialog.dismiss(); if (success) { Toast.makeText(SalesDetail.this, (record != null) ? mSOType + " updated" : mSOType + " created", Toast.LENGTH_LONG).show(); finish(); } } } SaleOrder.OnOperationSuccessListener cancelOrder = new SaleOrder.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(SalesDetail.this, StringUtils.capitalizeString(extra.getString("type")) + " cancelled", Toast.LENGTH_LONG).show(); finish(); } @Override public void OnCancelled() { } }; SaleOrder.OnOperationSuccessListener confirmSale = new SaleOrder.OnOperationSuccessListener() { @Override public void OnSuccess() { Toast.makeText(SalesDetail.this, R.string.label_quotation_confirm, Toast.LENGTH_LONG).show(); finish(); } @Override public void OnCancelled() { } }; @Override public void onClick(View v) { switch (v.getId()) { case R.id.layoutAddItem: if (mForm.getValues() != null) { Intent intent = new Intent(this, AddProductLineWizard.class); Bundle extra = new Bundle(); for (String key : lineValues.keySet()) { extra.putFloat(key, lineValues.get(key)); } intent.putExtras(extra); startActivityForResult(intent, REQUEST_ADD_ITEMS); } break; } } private class OnCustomerChangeUpdate extends AsyncTask { private ProgressDialog progressDialog; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(SalesDetail.this); progressDialog.setCancelable(false); progressDialog.setTitle(R.string.title_please_wait); progressDialog.setMessage(OResource.string(SalesDetail.this, R.string.title_working)); progressDialog.show(); } @Override protected Void doInBackground(ODataRow... params) { sale.onPartnerIdChange(params[0]); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); progressDialog.dismiss(); } } private class OnProductChange extends AsyncTask, Void, List> { private ProgressDialog progressDialog; private String warning = null; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(SalesDetail.this); progressDialog.setCancelable(false); progressDialog.setTitle(R.string.title_please_wait); progressDialog.setMessage(OResource.string(SalesDetail.this, R.string.title_working)); progressDialog.show(); } @Override protected List doInBackground(HashMap... params) { List items = new ArrayList<>(); try { ProductProduct productProduct = new ProductProduct(SalesDetail.this, sale.getUser()); SalesOrderLine saleLine = new SalesOrderLine(SalesDetail.this, sale.getUser()); ResPartner partner = new ResPartner(SalesDetail.this, sale.getUser()); ODataRow customer = partner.browse(mForm.getValues().getInt("partner_id")); ServerDataHelper helper = saleLine.getServerDataHelper(); boolean stockInstalled = saleLine.isInstalledOnServer("stock"); for (String key : params[0].keySet()) { ODataRow product = productProduct.browse(productProduct.selectRowId(Integer.parseInt(key))); Float qty = params[0].get(key); OArguments arguments = new OArguments(); arguments.add(new JSONArray()); int pricelist = customer.getInt("pricelist_id"); arguments.add(pricelist); // Price List for customer arguments.add(product.getInt("id")); // product id arguments.add(qty); // Quantity arguments.add(false); // UOM arguments.add(qty); // Qty_UOS arguments.add(false);// UOS arguments.add((product.getString("name").equals("false")) ? false : product.getString("name")); arguments.add(customer.getInt("id")); // Partner id arguments.add(false); // lang arguments.add(true); // update_tax arguments.add((customer.getString("date_order").equals("false")) ? false : customer.getString("date_order")); // date order arguments.add(false); // packaging Object fiscal_position = (customer.getString("fiscal_position").equals("false")) ? false : customer.getString("fiscal_position"); arguments.add(fiscal_position);// fiscal position arguments.add(false); // flag int version = saleLine.getOdooVersion().getVersion_number(); if (stockInstalled && version > 7) { arguments.add(false); } JSONObject context = new JSONObject(); context.put("partner_id", customer.getInt("id")); context.put("quantity", qty); context.put("pricelist", pricelist); // Fixed for Odoo 7.0 no product_id_change_with_wh available for v7 String method = (stockInstalled && version > 7) ? "product_id_change_with_wh" : "product_id_change"; JSONObject response = ((JSONObject) helper.callMethod(method, arguments, context)); JSONObject res = response.getJSONObject("value"); if (response.has("warning") && !response.getString("warning").equals("false")) { JSONObject warning_data = response.getJSONObject("warning"); if (warning_data.has("message")) warning = warning_data.getString("message"); } OValues values = new OValues(); values.put("product_id", product.getInt("id")); values.put("name", res.get("name")); values.put("product_uom_qty", res.get("product_uos_qty")); values.put("product_uom", res.get("product_uom")); values.put("price_unit", res.get("price_unit")); values.put("product_uos_qty", res.getDouble("product_uos_qty")); values.put("product_uos", false); values.put("price_subtotal", res.getDouble("price_unit") * res.getDouble("product_uos_qty")); JSONArray tax_id = new JSONArray(); tax_id.put(6); tax_id.put(false); tax_id.put(res.getJSONArray("tax_id")); values.put("tax_id", new JSONArray().put(tax_id)); values.put("th_weight", (res.has("th_weight")) ? res.get("th_weight") : 0); values.put("discount", (res.has("discount")) ? res.get("discount") : 0); if (stockInstalled) { values.put("route_id", (res.has("route_id")) ? res.get("route_id") : false); values.put("delay", res.get("delay")); } if (extra != null) values.put("order_id", extra.getInt(OColumn.ROW_ID)); items.add(values.toDataRow()); } } catch (Exception e) { e.printStackTrace(); } return items; } @Override protected void onPostExecute(List row) { super.onPostExecute(row); if (row != null) { objects.clear(); objects.addAll(row); mAdapter.notifyDataSetChanged(objects); float total = 0.0f; for (ODataRow rec : row) { total += rec.getFloat("price_subtotal"); } total_amt.setText(String.format("%.2f", total)); untaxedAmt.setText(total_amt.getText()); } progressDialog.dismiss(); if (warning != null) { OAlert.showWarning(SalesDetail.this, warning.trim()); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ADD_ITEMS && resultCode == Activity.RESULT_OK) { lineValues.clear(); for (String key : data.getExtras().keySet()) { if (data.getExtras().getFloat(key) > 0) lineValues.put(key, data.getExtras().getFloat(key)); } OnProductChange onProductChange = new OnProductChange(); onProductChange.execute(lineValues); } } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/models/AccountPaymentTerm.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 21/1/15 10:47 AM */ package com.odoo.addons.sale.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class AccountPaymentTerm extends OModel { public static final String TAG = AccountPaymentTerm.class.getSimpleName(); OColumn name = new OColumn("Payment Term", OVarchar.class); OColumn active = new OColumn("Active", OBoolean.class); public AccountPaymentTerm(Context context, OUser user) { super(context, "account.payment.term", user); } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/models/ProductProduct.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:11 AM */ package com.odoo.addons.sale.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class ProductProduct extends OModel { public static final String TAG = ProductProduct.class.getSimpleName(); OColumn name_template = new OColumn("Name", OVarchar.class).setSize(64); OColumn default_code = new OColumn("Internal Reference", OVarchar.class).setSize(64); OColumn lst_price = new OColumn("Public price", OInteger.class); OColumn sale_ok = new OColumn("Stock OK", OBoolean.class).setDefaultValue(false); public ProductProduct(Context context, OUser user) { super(context, "product.product", user); setDefaultNameColumn("name_template"); } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/models/SaleOrder.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:06 AM */ package com.odoo.addons.sale.models; import android.app.ProgressDialog; import android.content.Context; import android.net.Uri; import android.os.AsyncTask; import com.odoo.App; import com.odoo.R; import com.odoo.addons.sale.Sales; import com.odoo.base.addons.res.ResCompany; import com.odoo.base.addons.res.ResCurrency; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.ServerDataHelper; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.ODate; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import com.odoo.core.utils.OResource; import org.json.JSONArray; import org.json.JSONObject; import java.util.HashMap; import java.util.List; import odoo.OArguments; public class SaleOrder extends OModel { public static final String TAG = SaleOrder.class.getSimpleName(); public static final String AUTHORITY = "com.odoo.core.crm.provider.content.sync.sale_order"; private Context mContext; OColumn name = new OColumn("name", OVarchar.class); OColumn date_order = new OColumn("Date", ODateTime.class); @Odoo.onChange(method = "onPartnerIdChange", bg_process = true) OColumn partner_id = new OColumn("Customer", ResPartner.class, OColumn.RelationType.ManyToOne).setRequired(); OColumn user_id = new OColumn("Salesperson", ResUsers.class, OColumn.RelationType.ManyToOne); OColumn amount_total = new OColumn("Total", OFloat.class); OColumn payment_term = new OColumn("Payment Term", AccountPaymentTerm.class, OColumn.RelationType.ManyToOne); OColumn amount_untaxed = new OColumn("Untaxed", OInteger.class); OColumn amount_tax = new OColumn("Tax", OInteger.class); OColumn client_order_ref = new OColumn("Client Order Reference", OVarchar.class).setSize(100); OColumn state = new OColumn("status", OVarchar.class).setSize(10) .setDefaultValue("draft"); @Odoo.Functional(method = "getStateTitle", store = true, depends = {"state"}) OColumn state_title = new OColumn("State Title", OVarchar.class) .setLocalColumn(); @Odoo.Functional(method = "storePartnerName", store = true, depends = {"partner_id"}) OColumn partner_name = new OColumn("State Title", OVarchar.class) .setLocalColumn(); OColumn currency_id = new OColumn("currency", ResCurrency.class, OColumn.RelationType.ManyToOne); @Odoo.Functional(method = "storeCurrencySymbol", store = true, depends = {"currency_id"}) OColumn currency_symbol = new OColumn("State Title", OVarchar.class) .setLocalColumn(); OColumn order_line = new OColumn("Order Lines", SalesOrderLine.class, OColumn.RelationType.OneToMany).setRelatedColumn("order_id"); @Odoo.Functional(method = "countOrderLines", store = true, depends = {"order_line"}) OColumn order_line_count = new OColumn("Total Lines", OVarchar.class) .setLocalColumn(); OColumn partner_invoice_id = new OColumn("partner_invoice_id", OVarchar.class).setLocalColumn(); OColumn partner_shipping_id = new OColumn("partner_shipping_id", OVarchar.class).setLocalColumn(); OColumn pricelist_id = new OColumn("pricelist_id", OVarchar.class).setLocalColumn(); OColumn fiscal_position = new OColumn("fiscal_position", OVarchar.class).setLocalColumn(); public SaleOrder(Context context, OUser user) { super(context, "sale.order", user); mContext = context; setHasMailChatter(true); if (getUser().getVersion_number() == 7) { date_order.setType(ODate.class); } } @Override public Uri uri() { return buildURI(AUTHORITY); } public ODataRow onPartnerIdChange(ODataRow row) { ODataRow data = new ODataRow(); try { ResPartner partner = new ResPartner(mContext, getUser()); AccountPaymentTerm term = new AccountPaymentTerm(mContext, getUser()); ODataRow customer = partner.browse(row.getInt(OColumn.ROW_ID)); App app = (App) mContext.getApplicationContext(); if (app.inNetwork()) { ServerDataHelper helper = getServerDataHelper(); OArguments args = new OArguments(); args.add(new JSONArray()); args.add(customer.getInt("id")); JSONObject res = ((JSONObject) helper.callMethod("onchange_partner_id", args, new JSONObject())) .getJSONObject("value"); if (res.has("partner_invoice_id")) data.put("partner_invoice_id", res.get("partner_invoice_id")); if (res.has("partner_shipping_id")) data.put("partner_shipping_id", res.get("partner_shipping_id")); if (res.has("pricelist_id")) data.put("pricelist_id", res.get("pricelist_id")); if (res.has("payment_term") && !res.getString("payment_term").equals("false")) data.put("payment_term", term.selectRowId(res.getInt("payment_term"))); if (res.has("fiscal_position")) { data.put("fiscal_position", res.get("fiscal_position")); } partner.update(customer.getInt(OColumn.ROW_ID), data.toValues()); } else { data.put("partner_invoice_id", customer.get("partner_invoice_id")); data.put("partner_shipping_id", customer.get("partner_shipping_id")); data.put("pricelist_id", customer.get("pricelist_id")); data.put("payment_term", customer.get("payment_term")); data.put("fiscal_position", customer.get("fiscal_position")); } } catch (Exception e) { e.printStackTrace(); } return data; } public ODataRow currency() { ResCompany company = new ResCompany(mContext, getUser()); ODataRow row = company.browse(null, "id = ? ", new String[]{getUser().getCompany_id()}); if (row != null && !row.getString("currency_id").equals("false")) { return row.getM2ORecord("currency_id").browse(); } else { ResCurrency currency = new ResCurrency(mContext, getUser()); List list = currency.select(); if (list.size() > 0) { return list.get(0); } } return null; } public String getStateTitle(OValues row) { HashMap mStates = new HashMap(); mStates.put("draft", "Draft Quotation"); mStates.put("sent", "Quotation Sent"); mStates.put("cancel", "Cancelled"); mStates.put("waiting_date", "Waiting Schedule"); mStates.put("progress", "Sales Order"); mStates.put("manual", "Sale to Invoice"); mStates.put("shipping_except", "Shipping Exception"); mStates.put("invoice_except", "Invoice Exception"); mStates.put("done", "Done"); return mStates.get(row.getString("state")); } public String storeCurrencySymbol(OValues values) { try { if (!values.getString("currency_id").equals("false")) { JSONArray currency_id = new JSONArray(values.getString("currency_id")); return currency_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return "false"; } public String storePartnerName(OValues values) { try { if (!values.getString("partner_id").equals("false")) { JSONArray partner_id = new JSONArray(values.getString("partner_id")); return partner_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return "false"; } public String countOrderLines(OValues values) { try { JSONArray order_line = new JSONArray(values.getString("order_line")); if (order_line.length() > 0) { return " (" + order_line.length() + " lines)"; } } catch (Exception e) { e.printStackTrace(); } return " (No lines)"; } public void cancelOrder(final Sales.Type type, final ODataRow quotation, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { try { if (type == Sales.Type.SaleOrder) { OArguments args = new OArguments(); args.add(new JSONArray().put(quotation.getInt("id"))); args.add(new JSONObject()); getServerDataHelper().callMethod("action_cancel", args); } else { getServerDataHelper().executeWorkFlow(quotation.getInt("id"), "cancel"); } OValues values = new OValues(); values.put("state", "cancel"); values.put("state_title", getStateTitle(values)); values.put("_is_dirty", "false"); update(quotation.getInt(OColumn.ROW_ID), values); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } public void confirmSale(final ODataRow quotation, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { try { OArguments args = new OArguments(); args.add(new JSONArray().put(quotation.getInt("id"))); args.add(new JSONObject()); getServerDataHelper().callMethod("action_button_confirm", args); OValues values = new OValues(); values.put("state", "manual"); values.put("state_title", getStateTitle(values)); values.put("_is_dirty", "false"); update(quotation.getInt(OColumn.ROW_ID), values); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } public void newCopyQuotation(final ODataRow quotation, final OnOperationSuccessListener listener) { new AsyncTask() { private ProgressDialog dialog; @Override protected void onPreExecute() { super.onPreExecute(); dialog = new ProgressDialog(mContext); dialog.setTitle(R.string.title_please_wait); dialog.setMessage(OResource.string(mContext, R.string.title_working)); dialog.setCancelable(false); dialog.show(); } @Override protected Void doInBackground(Void... params) { try { OArguments args = new OArguments(); args.add(new JSONArray().put(quotation.getInt("id"))); args.add(new JSONObject()); getServerDataHelper().callMethod("copy_quotation", args); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); dialog.dismiss(); if (listener != null) { listener.OnSuccess(); } } @Override protected void onCancelled() { super.onCancelled(); dialog.dismiss(); if (listener != null) { listener.OnCancelled(); } } }.execute(); } public static interface OnOperationSuccessListener { public void OnSuccess(); public void OnCancelled(); } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/models/SalesOrderLine.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:08 AM */ package com.odoo.addons.sale.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.support.OUser; public class SalesOrderLine extends OModel { public static final String TAG = SalesOrderLine.class.getSimpleName(); OColumn product_id = new OColumn("Product", ProductProduct.class, OColumn.RelationType.ManyToOne); OColumn name = new OColumn("Description ", OText.class); OColumn product_uom_qty = new OColumn("Quantity", OInteger.class); OColumn price_unit = new OColumn("Unit Price", OFloat.class); OColumn price_subtotal = new OColumn("Sub Total", OFloat.class); OColumn order_id = new OColumn("ID", SaleOrder.class, OColumn.RelationType.ManyToOne); public SalesOrderLine(Context context, OUser user) { super(context, "sale.order.line", user); } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/providers/SaleOrderProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:40 AM */ package com.odoo.addons.sale.providers; import android.net.Uri; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.core.orm.provider.BaseModelProvider; public class SaleOrderProvider extends BaseModelProvider { public static final String TAG = SaleOrderProvider.class.getSimpleName(); @Override public void setModel(Uri uri) { super.setModel(uri); mModel = new SaleOrder(getContext(), getUser(uri)); } @Override public String authority() { return SaleOrder.AUTHORITY; } } ================================================ FILE: app/src/main/java/com/odoo/addons/sale/services/SaleOrderSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 11:40 AM */ package com.odoo.addons.sale.services; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import com.odoo.addons.sale.models.AccountPaymentTerm; import com.odoo.addons.sale.models.SaleOrder; import com.odoo.core.orm.ODataRow; import com.odoo.core.service.ISyncFinishListener; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.service.OSyncService; import com.odoo.core.support.OUser; import java.util.ArrayList; import java.util.List; import odoo.ODomain; public class SaleOrderSyncService extends OSyncService implements ISyncFinishListener { public static final String TAG = SaleOrderSyncService.class.getSimpleName(); public Boolean firstSync = false; @Override public OSyncAdapter getSyncAdapter(OSyncService service, Context context) { return new OSyncAdapter(context, SaleOrder.class, service, true); } @Override public void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user) { if (adapter.getModel().getModelName().equals("sale.order")) { ODomain domain = new ODomain(); SaleOrder saleOrder = new SaleOrder(getApplicationContext(), user); List newIds = new ArrayList<>(); for (ODataRow row : saleOrder.select(new String[]{}, "name = ? and id != ?", new String[]{"/", "0"})) { newIds.add(row.getInt("id")); } if (newIds.size() > 0) { domain.add("id", "in", newIds); } if (!firstSync) adapter.onSyncFinish(this); domain.add("user_id", "=", user.getUser_id()); adapter.setDomain(domain).syncDataLimit(50); } if (adapter.getModel().getModelName().equals("account.payment.term")) { adapter.onSyncFinish(syncFinishListener); } } @Override public OSyncAdapter performNextSync(OUser user, SyncResult syncResult) { return new OSyncAdapter(getApplicationContext(), AccountPaymentTerm.class, SaleOrderSyncService.this, true); } ISyncFinishListener syncFinishListener = new ISyncFinishListener() { @Override public OSyncAdapter performNextSync(OUser user, SyncResult syncResult) { firstSync = true; return new OSyncAdapter(getApplicationContext(), SaleOrder.class, SaleOrderSyncService.this, true); } }; } ================================================ FILE: app/src/main/java/com/odoo/base/addons/BaseModels.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 12:59 PM */ package com.odoo.base.addons; import android.content.Context; import com.odoo.base.addons.ir.IrAttachment; import com.odoo.base.addons.ir.IrModel; import com.odoo.base.addons.mail.MailMessage; import com.odoo.base.addons.res.ResCompany; import com.odoo.base.addons.res.ResPartner; import com.odoo.base.addons.res.ResUsers; import com.odoo.core.orm.OModel; import com.odoo.core.support.OUser; import com.odoo.news.models.OdooNews; import java.util.ArrayList; import java.util.List; public class BaseModels { public static final String TAG = BaseModels.class.getSimpleName(); public static List baseModels(Context context, OUser user) { List models = new ArrayList<>(); models.add(new OdooNews(context, user)); models.add(new IrModel(context, user)); models.add(new ResPartner(context, user)); models.add(new ResUsers(context, user)); models.add(new ResCompany(context, user)); models.add(new IrAttachment(context, user)); models.add(new MailMessage(context, user)); return models; } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/ir/IrAttachment.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 12:41 PM */ package com.odoo.base.addons.ir; import android.content.Context; import com.odoo.base.addons.res.ResCompany; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import org.json.JSONException; import org.json.JSONObject; import odoo.ODomain; public class IrAttachment extends OModel { public static final String TAG = IrAttachment.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); OColumn datas_fname = new OColumn("Data file name", OText.class); OColumn file_size = new OColumn("File Size", OInteger.class); OColumn res_model = new OColumn("Model", OVarchar.class).setSize(100); OColumn file_type = new OColumn("Content Type", OVarchar.class).setSize(100); OColumn company_id = new OColumn("Company", ResCompany.class, OColumn.RelationType.ManyToOne); OColumn res_id = new OColumn("Resource id", OInteger.class).setDefaultValue(0); OColumn scheme = new OColumn("File Scheme", OVarchar.class).setSize(100) .setLocalColumn(); // Local Column OColumn file_uri = new OColumn("File URI", OVarchar.class).setSize(150) .setLocalColumn().setDefaultValue(false); OColumn type = new OColumn("Type", OText.class).setLocalColumn(); public IrAttachment(Context context, OUser user) { super(context, "ir.attachment", user); } public boolean createAttachment(OValues value, String rel_model, int res_id) { OValues values = new OValues(); values.put("name", value.get("name")); values.put("datas_fname", value.getString("name")); values.put("file_size", value.get("file_size")); values.put("file_type", value.get("file_type")); values.put("company_id", getUser().getCompany_id()); values.put("res_id", res_id); values.put("res_model", rel_model); values.put("file_uri", value.getString("file_uri")); values.put("type", value.getString("file_type")); values.put("id", value.get("id")); insert(values); return true; } public static JSONObject valuesToData(OModel model, OValues value) { JSONObject data = new JSONObject(); try { data.put("name", value.get("name")); data.put("db_datas", value.getString("datas")); data.put("datas_fname", value.get("name")); data.put("file_size", value.get("file_size")); data.put("res_model", false); data.put("res_id", false); data.put("file_type", value.get("file_type")); data.put("company_id", model.getUser().getCompany_id()); return data; } catch (JSONException e) { e.printStackTrace(); } return null; } public String getDatasFromServer(Integer row_id) { try { ODomain domain = new ODomain(); domain.add("id", "=", selectServerId(row_id)); JSONObject fields = new JSONObject(); fields.accumulate("fields", "datas"); JSONObject result = getServerDataHelper().getOdoo().search_read(getModelName(), fields, domain.get()); if (result.getJSONArray("records").length() > 0) { JSONObject row = result.getJSONArray("records") .getJSONObject(0); return row.getString("datas"); } } catch (Exception e) { e.printStackTrace(); } return "false"; } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/ir/IrModel.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 2/1/15 3:18 PM */ package com.odoo.base.addons.ir; import android.content.Context; import android.net.Uri; import android.util.Log; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import com.odoo.core.utils.ODateUtils; import java.util.Calendar; import java.util.Date; public class IrModel extends OModel { public static final String TAG = IrModel.class.getSimpleName(); public static final String AUTHORITY = "com.odoo.crm.core.provider.content.sync.ir_model"; OColumn name = new OColumn("Model Description", OVarchar.class).setSize(100); OColumn model = new OColumn("Model", OVarchar.class).setSize(100); OColumn state = new OColumn("State", OVarchar.class).setSize(64); OColumn last_synced = new OColumn("Last Synced on ", ODateTime.class) .setLocalColumn(); public IrModel(Context context, OUser user) { super(context, "ir.model", user); } @Override public Uri uri() { return buildURI(AUTHORITY); } @Override public boolean checkForCreateDate() { return false; } @Override public boolean checkForWriteDate() { return false; } public void setLastSyncDateTimeToNow(OModel model) { Log.i(TAG, "Model Sync Update : " + model.getModelName()); OValues values = new OValues(); values.put("model", model.getModelName()); Date last_sync = ODateUtils.createDateObject(ODateUtils.getUTCDate(), ODateUtils.DEFAULT_FORMAT, true); Calendar cal = Calendar.getInstance(); cal.setTime(last_sync); /* Fixed for Postgres SQL It stores milliseconds so comparing date wrong. */ cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) + 2); last_sync = cal.getTime(); values.put("last_synced", ODateUtils.getDate(last_sync, ODateUtils.DEFAULT_FORMAT)); insertOrUpdate("model = ?", new String[]{model.getModelName()}, values); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/ir/feature/OFileManager.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 16/1/15 3:36 PM */ package com.odoo.base.addons.ir.feature; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.util.Base64; import android.webkit.MimeTypeMap; import android.widget.Toast; import com.odoo.App; import com.odoo.R; import com.odoo.base.addons.ir.IrAttachment; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OResource; import com.odoo.core.utils.OStorageUtils; import com.odoo.core.utils.notification.ONotificationBuilder; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.FileNameMap; import java.net.URLConnection; import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.makeText; public class OFileManager implements DialogInterface.OnClickListener { public static final String TAG = OFileManager.class.getSimpleName(); public static final int REQUEST_CAMERA = 111; public static final int REQUEST_IMAGE = 112; public static final int REQUEST_AUDIO = 113; public static final int REQUEST_FILE = 114; public static final int REQUEST_ALL_FILE = 115; private static final int SINGLE_ATTACHMENT_STREAM = 115; private static final long IMAGE_MAX_SIZE = 1000000; // 1 MB private Context mContext = null; private String[] mOptions = null; private RequestType requestType = null; private Uri newImageUri = null; private IrAttachment irAttachment = null; private App mApp; public enum RequestType { CAPTURE_IMAGE, IMAGE, IMAGE_OR_CAPTURE_IMAGE, AUDIO, FILE, ALL_FILE_TYPE } public OFileManager(Context context) { mContext = context; irAttachment = new IrAttachment(context, null); mApp = (App) mContext.getApplicationContext(); } public void downloadAttachment(int attachment_id) { ODataRow attachment = irAttachment.browse(attachment_id); if (attachment != null) { String uri = attachment.getString("file_uri"); if (uri.equals("false")) { // Downloading new file _download(attachment); } else { Uri file_uri = Uri.parse(uri); if (fileExists(file_uri)) { requestIntent(file_uri); } else if (atLeastKitKat()) { String file_path = getDocPath(file_uri); if (file_path != null) { file_uri = Uri.fromFile(new File(file_path)); requestIntent(file_uri); } else if (attachment.getInt("id") != 0) { // Downloading new file _download(attachment); } else { // Failed to get file OAlert.showAlert(mContext, "Unable to find file !"); } } else if (fileExists(file_uri)) { requestIntent(file_uri); } else { // Failed to get file OAlert.showAlert(mContext, "Unable to find file !"); } } } } private void _download(ODataRow attachment) { ONotificationBuilder builder = new ONotificationBuilder(mContext, attachment.getInt(OColumn.ROW_ID)); builder.setTitle("Downloading " + attachment.getString("name")); builder.setText("Download in progress"); builder.setOngoing(true); builder.setAutoCancel(true); DownloadManager downloader = new DownloadManager(builder); downloader.execute(attachment); } private class DownloadManager extends AsyncTask { ONotificationBuilder notificationBuilder; public DownloadManager(ONotificationBuilder builder) { notificationBuilder = builder; } @Override protected void onPreExecute() { super.onPreExecute(); notificationBuilder.allowVibrate(false); notificationBuilder.withRingTone(false); notificationBuilder.setProgress(0, 0, true); notificationBuilder.build().show(); } @Override protected ODataRow doInBackground(ODataRow... params) { if (mApp.inNetwork()) { try { Thread.sleep(500); ODataRow attachment = params[0]; String base64 = irAttachment.getDatasFromServer(attachment.getInt(OColumn.ROW_ID)); if (!base64.equals("false")) { String file = createFile(attachment.getString("name"), Base64.decode(base64, 0) , attachment.getString("file_type")); Uri uri = Uri.fromFile(new File(file)); OValues values = new OValues(); values.put("file_uri", uri.toString()); irAttachment.update(attachment.getInt(OColumn.ROW_ID), values); return irAttachment.browse(attachment.getInt(OColumn.ROW_ID)); } } catch (Exception e) { e.printStackTrace(); } } return null; } @Override protected void onPostExecute(ODataRow row) { super.onPostExecute(row); ONotificationBuilder.cancelNotification(mContext, row.getInt(OColumn.ROW_ID)); if (row != null) { ONotificationBuilder builder = new ONotificationBuilder(mContext, row.getInt(OColumn.ROW_ID)); builder.allowVibrate(true); builder.withRingTone(true); builder.setTitle(row.getString("name")); builder.setText("Download Complete"); builder.setBigText("Download Complete"); if (row.getString("file_type").contains("image")) { Bitmap bmp = getBitmapFromURI(Uri.parse(row.getString("file_uri"))); builder.setBigPicture(bmp); } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(row.getString("file_uri")), row.getString("file_type")); builder.setResultIntent(intent); builder.build().show(); } } } private String createFile(String name, byte[] fileAsBytes, String file_type) { InputStream is = new ByteArrayInputStream(fileAsBytes); String filename = name.replaceAll("[-+^:=, ]", "_"); String file_path = OStorageUtils.getDirectoryPath(file_type) + "/" + filename; try { FileOutputStream fos = new FileOutputStream(file_path); byte data[] = new byte[1024]; int count = 0; while ((count = is.read(data)) != -1) { fos.write(data, 0, count); } is.close(); fos.close(); } catch (Exception e) { e.printStackTrace(); } return file_path; } private void requestIntent(Uri uri) { Intent intent = new Intent(Intent.ACTION_VIEW); FileNameMap mime = URLConnection.getFileNameMap(); String mimeType = mime.getContentTypeFor(uri.getPath()); intent.setDataAndType(uri, mimeType); try { mContext.startActivity(intent); } catch (ActivityNotFoundException e) { Toast.makeText(mContext, OResource.string(mContext, R.string.toast_no_activity_found_to_handle_file), Toast.LENGTH_LONG).show(); } } private boolean fileExists(Uri uri) { return new File(uri.getPath()).exists(); } public Bitmap getBitmapFromURI(Uri uri) { Bitmap bitmap; if (!fileExists(uri) && atLeastKitKat()) { String path = getDocPath(uri); bitmap = BitmapUtils.getBitmapImage(mContext, BitmapUtils.uriToBase64(Uri.fromFile(new File(path)), mContext.getContentResolver())); } else { bitmap = BitmapUtils.getBitmapImage(mContext, BitmapUtils.uriToBase64(uri, mContext.getContentResolver())); } return bitmap; } @TargetApi(Build.VERSION_CODES.KITKAT) public String getDocPath(Uri uri) { String wholeID = DocumentsContract.getDocumentId(uri); String id = wholeID.split(":")[1]; String[] column = {MediaStore.Images.Media.DATA}; String sel = MediaStore.Images.Media._ID + "=?"; Cursor cursor = mContext.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, column, sel, new String[]{id}, null); String filePath = null; int columnIndex = cursor.getColumnIndex(column[0]); if (cursor.moveToFirst()) { filePath = cursor.getString(columnIndex); } cursor.close(); return filePath; } public boolean atLeastKitKat() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; } public void requestForFile(RequestType type) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_GET_CONTENT); switch (type) { case AUDIO: intent.setType("audio/*"); requestIntent(intent, REQUEST_AUDIO); break; case IMAGE: if (Build.VERSION.SDK_INT < 19) { intent = new Intent(); intent.setAction(Intent.ACTION_GET_CONTENT); } else { intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); } intent.setType("image/*"); requestIntent(intent, REQUEST_IMAGE); break; case CAPTURE_IMAGE: ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE, "Odoo Mobile Attachment"); values.put(MediaStore.Images.Media.DESCRIPTION, "Captured from Odoo Mobile App"); newImageUri = mContext.getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, newImageUri); requestIntent(intent, REQUEST_CAMERA); break; case IMAGE_OR_CAPTURE_IMAGE: requestDialog(type); break; case FILE: intent.setType("application/*"); requestIntent(intent, REQUEST_FILE); break; case ALL_FILE_TYPE: intent.setType("*/*"); requestIntent(intent, REQUEST_ALL_FILE); break; } } public OValues getURIDetails(Uri uri) { OValues values = new OValues(); ContentResolver mCR = mContext.getContentResolver(); if (uri.getScheme().equals("content")) { Cursor cr = mCR.query(uri, null, null, null, null); int nameIndex = cr.getColumnIndex(OpenableColumns.DISPLAY_NAME); int fileSize = cr.getColumnIndex(OpenableColumns.SIZE); if (cr.moveToFirst()) { values.put("name", cr.getString(nameIndex)); values.put("datas_fname", values.get("name")); values.put("file_size", Long.toString(cr.getLong(fileSize))); String path = getPath(uri); if (path != null) { values.put("file_size", new File(path).length() + ""); } } } if (uri.getScheme().equals("file")) { File file = new File(uri.toString()); values.put("name", file.getName()); values.put("datas_fname", values.get("name")); values.put("file_size", Long.toString(file.length())); } values.put("file_uri", uri.toString()); values.put("scheme", uri.getScheme()); MimeTypeMap mime = MimeTypeMap.getSingleton(); String type = mime.getMimeTypeFromExtension(mime .getExtensionFromMimeType(mCR.getType(uri))); values.put("file_type", (type == null) ? uri.getScheme() : type); values.put("type", type); return values; } public String getPath(Uri uri) { ContentResolver mCR = mContext.getContentResolver(); String[] projection = {MediaStore.Images.Media.DATA}; Cursor cursor = mCR.query(uri, projection, null, null, null); if (cursor == null) return null; int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); String s = cursor.getString(column_index); cursor.close(); return s; } public OValues handleResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { switch (requestCode) { case REQUEST_CAMERA: OValues values = getURIDetails(newImageUri); values.put("datas", BitmapUtils.uriToBase64(newImageUri, mContext.getContentResolver(), true)); return values; case REQUEST_IMAGE: values = getURIDetails(data.getData()); values.put("datas", BitmapUtils.uriToBase64(data.getData(), mContext.getContentResolver(), true)); return values; case REQUEST_ALL_FILE: default: return getURIDetails(data.getData()); } } return null; } private void requestIntent(Intent intent, int requestCode) { try { ((Activity) mContext).startActivityForResult(intent, requestCode); } catch (ActivityNotFoundException e) { makeText(mContext, "No Activity Found to handle request", LENGTH_SHORT).show(); } } private void requestDialog(RequestType type) { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); switch (type) { case IMAGE_OR_CAPTURE_IMAGE: requestType = type; mOptions = new String[]{"Select Image", "Capture Image"}; break; } builder.setSingleChoiceItems(mOptions, -1, this); builder.create().show(); } @Override public void onClick(DialogInterface dialog, int which) { switch (requestType) { case IMAGE_OR_CAPTURE_IMAGE: requestForFile((which == 0) ? RequestType.IMAGE : RequestType.CAPTURE_IMAGE); break; } dialog.dismiss(); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/ir/providers/IrModelProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 6/2/15 10:05 AM */ package com.odoo.base.addons.ir.providers; import android.net.Uri; import com.odoo.base.addons.ir.IrModel; import com.odoo.core.orm.provider.BaseModelProvider; public class IrModelProvider extends BaseModelProvider { public static final String TAG = IrModelProvider.class.getSimpleName(); @Override public void setModel(Uri uri) { mModel = new IrModel(getContext(), getUser(uri)); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/MailMessage.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 25/2/15 11:49 AM */ package com.odoo.base.addons.mail; import android.content.Context; import com.odoo.base.addons.ir.IrAttachment; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import org.json.JSONArray; import java.util.ArrayList; import java.util.List; public class MailMessage extends OModel { public static final String TAG = MailMessage.class.getSimpleName(); OColumn author_id = new OColumn("Author", ResPartner.class, OColumn.RelationType.ManyToOne); OColumn email_from = new OColumn("Email From", OVarchar.class).setDefaultValue("false"); OColumn subject = new OColumn("Subject", OVarchar.class).setSize(100); OColumn body = new OColumn("Body", OText.class); OColumn date = new OColumn("Date", ODateTime.class); OColumn record_name = new OColumn("Record Name", OVarchar.class).setSize(150); OColumn model = new OColumn("Model", OVarchar.class).setDefaultValue("false"); OColumn res_id = new OColumn("Resource Id", OInteger.class).setDefaultValue(0); OColumn attachment_ids = new OColumn("Attachments", IrAttachment.class, OColumn.RelationType.ManyToMany); OColumn type = new OColumn("Type", OVarchar.class); OColumn subtype_id = new OColumn("Subtype", MailMessageSubType.class, OColumn.RelationType.ManyToOne); @Odoo.Functional(method = "authorName", depends = {"author_id", "email_from"}, store = true) OColumn author_name = new OColumn("Author Name", OVarchar.class).setLocalColumn(); @Odoo.Functional(method = "hasAttachment", depends = {"attachment_ids"}, store = true) OColumn has_attachments = new OColumn("Has Attachments", OBoolean.class).setLocalColumn() .setDefaultValue(false); public MailMessage(Context context, OUser user) { super(context, "mail.message", user); } public boolean hasAttachment(OValues values) { try { JSONArray attachment_ids = (JSONArray) values.get("attachment_ids"); if (attachment_ids.length() > 0) { return true; } } catch (Exception e) { e.printStackTrace(); } return false; } public String authorName(OValues values) { try { if (!values.getString("author_id").equals("false")) { JSONArray author_id = new JSONArray(values.getString("author_id")); return author_id.getString(1); } else { return values.getString("email_from"); } } catch (Exception e) { } return ""; } public String getAuthorImage(int row_id) { ODataRow row = browse(new String[]{"author_id"}, row_id); if (row.getInt("author_id") != 0) { ODataRow author_id = row.getM2ORecord("author_id").browse(); return author_id.getString("image_small"); } return "false"; } public List getServerIds(String model, int res_server_id) { List ids = new ArrayList<>(); for (ODataRow row : select(new String[]{}, "model = ? and res_id = ?", new String[]{model, res_server_id + ""})) { ids.add(row.getInt("id")); } return ids; } @Override public boolean checkForWriteDate() { return false; } @Override public boolean allowCreateRecordOnServer() { return false; } @Override public boolean allowDeleteRecordOnServer() { return false; } @Override public boolean allowUpdateRecordOnServer() { return false; } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/MailMessageSubType.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 27/3/15 12:33 PM */ package com.odoo.base.addons.mail; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class MailMessageSubType extends OModel { public static final String TAG = MailMessageSubType.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); public MailMessageSubType(Context context, OUser user) { super(context, "mail.message.subtype", user); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/widget/MailChatterCompose.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 27/2/15 5:53 PM */ package com.odoo.base.addons.mail.widget; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.R; import com.odoo.base.addons.ir.IrAttachment; import com.odoo.base.addons.ir.feature.OFileManager; import com.odoo.base.addons.mail.MailMessage; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.core.utils.OStringColorUtil; import com.odoo.core.utils.logger.OLog; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import odoo.OArguments; public class MailChatterCompose extends ActionBarActivity implements View.OnClickListener { public static final String TAG = MailChatterCompose.class.getSimpleName(); private OModel mModel; private IrAttachment irAttachment; private int server_id = -1; private int partner_id = -1; private OFileManager fileManager; private LinearLayout horizontalScrollView; private List attachmentIds = new ArrayList<>(); public enum MessageType { Message, InternalNote } private MessageType mType = MessageType.Message; private View parent; private MailMessage mailMessage; private EditText edtSubject, edtBody; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_mail_chatter_message_compose); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getSupportActionBar().hide(); fileManager = new OFileManager(this); Bundle extra = getIntent().getExtras(); mType = MessageType.valueOf(extra.getString("type")); mModel = OModel.get(this, extra.getString("model"), null); irAttachment = new IrAttachment(this, null); mailMessage = new MailMessage(this, null); server_id = extra.getInt("server_id"); if (mModel.getModelName().equals("res.partner")) { partner_id = server_id; } else { ODataRow row = mModel.browse(mModel.selectRowId(server_id)); for (OColumn col : mModel.getColumns(false)) { if (col.getType().isAssignableFrom(ResPartner.class)) { if (col.getRelationType() != null && col.getRelationType() == OColumn.RelationType.ManyToOne) { ODataRow partner = null; if (!row.getString(col.getName()).equals("false")) { partner = row.getM2ORecord(col.getName()).browse(); } if (partner != null && partner.getInt("id") != 0) { partner_id = partner.getInt("id"); } } } } } findViewById(R.id.btnAttachment).setOnClickListener(this); findViewById(R.id.btnSend).setOnClickListener(this); findViewById(R.id.btnCancel).setOnClickListener(this); edtSubject = (EditText) findViewById(R.id.messageSubject); edtBody = (EditText) findViewById(R.id.messageBody); horizontalScrollView = (LinearLayout) findViewById(R.id.attachmentsList); init(); } private void init() { TextView recordName = (TextView) findViewById(R.id.recordName); parent = (View) recordName.getParent().getParent(); ODataRow record = mModel.browse(mModel.selectRowId(server_id)); String name = record.getString(mModel.getDefaultNameColumn()); findViewById(R.id.dialogHeader) .setBackgroundColor(OStringColorUtil.getStringColor(this, name)); if (mType == MessageType.Message) { edtSubject.setText("Re: " + name); recordName.setText(String.format(OResource.string(this, R.string.message_to), name)); } else { recordName.setText(R.string.add_internal_note); edtSubject.setVisibility(View.GONE); edtBody.setHint(R.string.internal_note_hint); OControls.setText(parent, R.id.btnSend, R.string.label_log_note); } edtBody.requestFocus(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnSend: sendMessage(); break; case R.id.btnCancel: finish(); break; case R.id.btnAttachment: fileManager.requestForFile(OFileManager.RequestType.ALL_FILE_TYPE); break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); OValues response = fileManager.handleResult(requestCode, resultCode, data); if (response != null) { addAttachment(response); } } private void addAttachment(OValues values) { View attachmentView = LayoutInflater.from(this) .inflate(R.layout.base_attachment_item, horizontalScrollView, false); String fileName = values.getString("name"); String type = values.getString("file_type"); ImageView imgPreview = (ImageView) attachmentView.findViewById(R.id.attachmentPreview); if (type.contains("image")) { OLog.log(values.getString("file_uri")); imgPreview.setImageURI(Uri.parse(values.getString("file_uri"))); } else if (type.contains("audio")) { imgPreview.setImageResource(R.drawable.audio); } else if (type.contains("video")) { imgPreview.setImageResource(R.drawable.video); } else { imgPreview.setImageResource(R.drawable.file); } OControls.setText(attachmentView, R.id.attachmentFileName, fileName); attachmentView.setTag(values); attachmentView.findViewById(R.id.btnRemoveAttachment) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { horizontalScrollView.removeView( (View) v.getParent() ); } }); horizontalScrollView.addView(attachmentView); } private void sendMessage() { edtSubject.setError(null); edtBody.setError(null); if (mType == MessageType.Message) { if (TextUtils.isEmpty(edtSubject.getText())) { edtSubject.setError("Subject required"); edtSubject.requestFocus(); return; } } if (TextUtils.isEmpty(edtBody.getText())) { edtBody.setError(((mType == MessageType.Message) ? "Message" : "Note") + " required"); edtBody.requestFocus(); return; } int attachments_count = horizontalScrollView.getChildCount(); if (attachments_count > 0) { // Has attachments List attachments = new ArrayList<>(); for (int i = 0; i < attachments_count; i++) { attachments.add((OValues) horizontalScrollView.getChildAt(i).getTag()); } CreateAttachments createAttachments = new CreateAttachments(); createAttachments.execute(attachments); } else { postMessage(); } } private void postMessage() { String subject = (mType == MessageType.Message) ? edtSubject.getText().toString() : "false"; MessagePost messagePost = new MessagePost(); messagePost.execute(subject, edtBody.getText().toString()); } private class CreateAttachments extends AsyncTask, Void, List> { private ProgressDialog progressDialog; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(MailChatterCompose.this); progressDialog.setTitle(R.string.title_working); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setMessage("Uploading attachments..."); progressDialog.setMax(horizontalScrollView.getChildCount()); progressDialog.setCancelable(false); progressDialog.setProgress(1); progressDialog.show(); } @Override protected List doInBackground(final List... params) { try { List ids = new ArrayList<>(); for (final OValues value : params[0]) { boolean isImage = (value.getString("file_type").contains("image")); value.put("datas", BitmapUtils.uriToBase64( Uri.parse(value.getString("file_uri")) , getContentResolver(), isImage )); JSONObject data = IrAttachment.valuesToData(irAttachment, value); if (data != null) { runOnUiThread(new Runnable() { @Override public void run() { progressDialog.setProgress(params[0].indexOf(value) + 1); } }); int newId = irAttachment.getServerDataHelper().createOnServer(data); value.put("id", newId); irAttachment.createAttachment(value, mailMessage.getModelName(), 0); ids.add(newId); } } return ids; } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(List ids) { super.onPostExecute(ids); progressDialog.dismiss(); attachmentIds.clear(); attachmentIds.addAll(ids); if (ids != null) postMessage(); } } private class MessagePost extends AsyncTask { private ProgressDialog progressDialog; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(MailChatterCompose.this); progressDialog.setTitle(R.string.title_working); progressDialog.setMessage(((mType == MessageType.Message) ? "Sending message" : "Logging internal note") + "..."); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected Boolean doInBackground(String... params) { try { String subject = params[0]; String body = params[1]; OArguments args = new OArguments(); args.add(server_id); JSONObject data = new JSONObject(); data.put("body", body); data.put("subject", (subject.equals("false")) ? false : subject); data.put("parent_id", false); data.put("attachment_ids", JSONUtils.toArray(attachmentIds)); JSONArray partner_ids = new JSONArray(); if (partner_id != -1 && mType == MessageType.Message) { partner_ids.put(partner_id); } data.put("partner_ids", partner_ids); JSONObject context = new JSONObject(); context.put("mail_read_set_read", true); context.put("default_res_id", server_id); context.put("default_model", mModel.getModelName()); context.put("mail_post_autofollow", true); context.put("mail_post_autofollow_partner_ids", new JSONArray()); data.put("context", context); data.put("type", "comment"); data.put("content_subtype", "plaintext"); data.put("subtype", (mType == MessageType.Message) ? "mail.mt_comment" : false); int newId = (int) mModel.getServerDataHelper().callMethod("message_post", args, null, data); Thread.sleep(500); ODataRow row = new ODataRow(); row.put("id", newId); mailMessage.quickCreateRecord(row); return true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); progressDialog.dismiss(); Context ctx = MailChatterCompose.this; Intent intent = new Intent(); intent.setAction("mail.message.update"); ctx.sendBroadcast(intent); finish(); } } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/widget/MailChatterView.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 25/2/15 12:07 PM */ package com.odoo.base.addons.mail.widget; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.odoo.App; import com.odoo.R; import com.odoo.base.addons.mail.MailMessage; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.StringUtils; import java.util.ArrayList; import java.util.List; import odoo.ODomain; import odoo.controls.ExpandableListControl; public class MailChatterView extends LinearLayout implements ExpandableListControl.ExpandableListAdapterGetViewListener, View.OnClickListener { public static final String TAG = MailChatterView.class.getSimpleName(); private Context mContext; private String modelName = null; private int record_server_id = 0; private View mChatterCardView; private OModel mModel; private ExpandableListControl mChatterListView; private ExpandableListControl.ExpandableListAdapter mListAdapter; private List chatterItems = new ArrayList<>(); private MailMessage mailMessage; private ChatterMessagesLoader messagesLoader; private App app; private Boolean loadAllMessages = false; private boolean isExecuting = false; public MailChatterView(Context context) { super(context); init(context, null, 0); } public MailChatterView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public MailChatterView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr); } private void init(Context context, AttributeSet attrs, int defStyleAttr) { mContext = context; app = (App) mContext.getApplicationContext(); if (attrs != null) { TypedArray types = mContext.obtainStyledAttributes(attrs, R.styleable.MailChatterView); modelName = types.getString(R.styleable.MailChatterView_resModelName); types.recycle(); } setOrientation(VERTICAL); mailMessage = new MailMessage(context, null); mContext.registerReceiver(dataChangeReceiver, new IntentFilter("mail.message.update")); } public void generateView() { Log.v(TAG, "Generating View for Mail Chatter"); removeAllViews(); mChatterCardView = LayoutInflater.from(mContext).inflate(R.layout.base_mail_chatter, this, false); addView(mChatterCardView); findViewById(R.id.chatterSendMessage).setOnClickListener(this); findViewById(R.id.chatterLogInternalNote).setOnClickListener(this); if (modelName != null) { mModel = OModel.get(mContext, modelName, null); if (!mModel.hasMailChatter()) { removeAllViews(); } else { if (record_server_id > 0) { getMessages(); } } } else { removeAllViews(); } } private void getMessages() { mChatterListView = (ExpandableListControl) findViewById(R.id.chatterMessages); mListAdapter = mChatterListView.getAdapter( R.layout.base_mail_chatter_item, chatterItems, this); mListAdapter.notifyDataSetChanged(chatterItems); // Check for server updated messages if (app.inNetwork()) { messagesLoader = new ChatterMessagesLoader(); messagesLoader.execute(); } // Updating chatter messages updateChatterList(); } private void updateChatterList() { // Getting local messages if (modelName != null) { chatterItems.clear(); Cursor cr = mContext.getContentResolver().query(mailMessage.uri(), null, "model = ? and res_id = ?", new String[]{modelName, record_server_id + ""}, "date desc"); if (cr.moveToFirst()) { int limit = (loadAllMessages) ? cr.getCount() : (cr.getCount() > 3) ? 3 : cr.getCount(); for (int i = 0; i < limit; i++) { ODataRow row = OCursorUtils.toDatarow(cr); chatterItems.add(row); cr.moveToNext(); } } TextView loadMore = (TextView) findViewById(R.id.chatterLoadMoreMessages); if (cr.getCount() > 3 && !loadAllMessages) { loadMore.setVisibility(View.VISIBLE); loadMore.setOnClickListener(this); } else { loadMore.setVisibility(View.GONE); } mListAdapter.notifyDataSetChanged(chatterItems); if (chatterItems.isEmpty()) { loadMore.setVisibility(View.VISIBLE); loadMore.setText("No messages !"); } } } public void setModelName(String model) { modelName = model; } public void setRecordServerId(int record_server_id) { this.record_server_id = record_server_id; } @Override public View getView(int position, View view, ViewGroup parent) { ODataRow row = (ODataRow) chatterItems.get(position); if (row.getString("subtype_id").equals("false")) { view.setBackgroundResource(R.color.base_chatter_view_note_background); } else { view.setBackgroundColor(Color.WHITE); } view.findViewById(R.id.imgAttachments).setVisibility( (row.getBoolean("has_attachments")) ? View.VISIBLE : View.GONE ); if (row.getString("subject").equals("false")) { OControls.setGone(view, R.id.chatterSubject); } else { OControls.setVisible(view, R.id.chatterSubject); OControls.setText(view, R.id.chatterSubject, row.getString("subject")); } String date = ODateUtils.convertToDefault(row.getString("date"), ODateUtils.DEFAULT_FORMAT, "MMM dd hh:mm a"); OControls.setText(view, R.id.chatterDate, date); OControls.setText(view, R.id.chatterBody, StringUtils.htmlToString(row.getString("body"))); OControls.setText(view, R.id.chatterAuthor, row.getString("author_name")); String author_image = mailMessage.getAuthorImage(row.getInt(OColumn.ROW_ID)); if (!author_image.equals("false")) { Bitmap author = BitmapUtils.getBitmapImage(mContext, author_image); OControls.setImage(view, R.id.authorImage, author); } else { OControls.setImage(view, R.id.authorImage, R.drawable.avatar); } view.setTag(row); view.setOnClickListener(this); return view; } @Override public void onClick(View v) { Bundle extra = new Bundle(); extra.putString("model", mModel.getModelName()); extra.putInt("server_id", record_server_id); switch (v.getId()) { case R.id.chatterSendMessage: if (app.inNetwork()) { extra.putString("type", MailChatterCompose.MessageType.Message.toString()); IntentUtils.startActivity(mContext, MailChatterCompose.class, extra); } else { Toast.makeText(mContext, OResource.string(mContext, R.string.toast_network_required), Toast.LENGTH_LONG).show(); } break; case R.id.chatterLogInternalNote: if (app.inNetwork()) { extra.putString("type", MailChatterCompose.MessageType.InternalNote.toString()); IntentUtils.startActivity(mContext, MailChatterCompose.class, extra); } else { Toast.makeText(mContext, OResource.string(mContext, R.string.toast_network_required), Toast.LENGTH_LONG).show(); } break; case R.id.chatterLoadMoreMessages: loadAllMessages = true; updateChatterList(); break; default: ODataRow row = (ODataRow) v.getTag(); extra.putAll(row.getPrimaryBundleData()); if (row != null) { IntentUtils.startActivity(mContext, MailDetailDialog.class, extra); } break; } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (messagesLoader != null) messagesLoader.cancel(true); mContext.unregisterReceiver(dataChangeReceiver); } private class ChatterMessagesLoader extends AsyncTask { @Override protected void onPreExecute() { super.onPreExecute(); findViewById(R.id.chatterProgress).setVisibility(View.VISIBLE); findViewById(R.id.chatterOr).setVisibility(View.GONE); } @Override protected Void doInBackground(Void... params) { try { Thread.sleep(500); ODomain domain = new ODomain(); domain.add("model", "=", modelName); domain.add("res_id", "=", record_server_id); List serverIds = mailMessage.getServerIds(modelName, record_server_id); if (serverIds.size() > 0) { domain.add("id", "not in", serverIds); } mailMessage.quickSyncRecords(domain); } catch (Exception e) { } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); findViewById(R.id.chatterProgress).setVisibility(View.GONE); findViewById(R.id.chatterOr).setVisibility(View.VISIBLE); updateChatterList(); isExecuting = false; } } private BroadcastReceiver dataChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (!isExecuting) { if (messagesLoader != null) messagesLoader.cancel(true); messagesLoader = new ChatterMessagesLoader(); messagesLoader.execute(); isExecuting = true; } } }; } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/widget/MailDetailDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 25/2/15 6:26 PM */ package com.odoo.base.addons.mail.widget; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.R; import com.odoo.base.addons.ir.feature.OFileManager; import com.odoo.base.addons.mail.MailMessage; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OStringColorUtil; import java.util.ArrayList; import java.util.List; public class MailDetailDialog extends ActionBarActivity implements View.OnClickListener { public static final String TAG = MailDetailDialog.class.getSimpleName(); private Bundle extra; private MailMessage mailMessage; private OModel baseModel; private TextView recordName; private View parent; private List attachments = new ArrayList<>(); private LoadAttachments loadAttachments = null; private LinearLayout horizontalScrollView; private OFileManager fileManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_mail_chatter_message_detail); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getSupportActionBar().hide(); fileManager = new OFileManager(this); mailMessage = new MailMessage(this, null); extra = getIntent().getExtras(); findViewById(R.id.btnClose).setOnClickListener(this); findViewById(R.id.btnReply).setOnClickListener(this); init(); } private void init() { recordName = (TextView) findViewById(R.id.recordName); parent = (View) recordName.getParent(); ODataRow row = mailMessage.browse(extra.getInt(OColumn.ROW_ID)); attachments.addAll(row.getM2MRecord("attachment_ids").browseEach()); if (attachments.size() > 0) { loadAttachments = new LoadAttachments(); loadAttachments.execute(); } horizontalScrollView = (LinearLayout) findViewById(R.id.attachmentsList); baseModel = OModel.get(this, row.getString("model"), mailMessage.getUser().getAndroidName()); ODataRow record = baseModel.browse(baseModel.selectRowId(row.getInt("res_id"))); String name = record.getString(baseModel.getDefaultNameColumn()); recordName.setText(name); recordName.setBackgroundColor(OStringColorUtil.getStringColor(this, name)); if (!row.getString("subject").equals("false")) OControls.setText(parent, R.id.messageSubject, row.getString("subject")); else OControls.setGone(parent, R.id.messageSubject); WebView messageBody = (WebView) findViewById(R.id.messageBody); messageBody.setBackgroundColor(Color.TRANSPARENT); messageBody.loadData(row.getString("body"), "text/html; charset=UTF-8", "UTF-8"); Bitmap author_image = BitmapUtils.getAlphabetImage(this, row.getString("author_name")); String author_img = mailMessage.getAuthorImage(row.getInt(OColumn.ROW_ID)); if (!author_img.equals("false")) { author_image = BitmapUtils.getBitmapImage(this, author_img); } OControls.setImage(parent, R.id.author_image, author_image); OControls.setText(parent, R.id.authorName, row.getString("author_name")); String date = ODateUtils.convertToDefault(row.getString("date"), ODateUtils.DEFAULT_FORMAT, "MMM dd, yyyy hh:mm a"); OControls.setText(parent, R.id.messageDate, date); } private class LoadAttachments extends AsyncTask { @Override protected Void doInBackground(Void... params) { runOnUiThread(new Runnable() { @Override public void run() { for (ODataRow row : attachments) { addAttachment(row); } } }); return null; } } private void addAttachment(ODataRow values) { View attachmentView = LayoutInflater.from(this) .inflate(R.layout.base_attachment_item, horizontalScrollView, false); String fileName = values.getString("name"); String type = values.getString("file_type"); ImageView imgPreview = (ImageView) attachmentView.findViewById(R.id.attachmentPreview); if (type.contains("image")) { if (!values.getString("file_uri").equals("false")) { Uri uri = Uri.parse(values.getString("file_uri")); imgPreview.setImageBitmap(fileManager.getBitmapFromURI(uri)); } else imgPreview.setImageResource(R.drawable.image); } else if (type.contains("audio")) { imgPreview.setImageResource(R.drawable.audio); } else if (type.contains("video")) { imgPreview.setImageResource(R.drawable.video); } else { imgPreview.setImageResource(R.drawable.file); } OControls.setText(attachmentView, R.id.attachmentFileName, fileName); attachmentView.setTag(values); attachmentView.findViewById(R.id.btnRemoveAttachment).setVisibility(View.GONE); attachmentView.setOnClickListener(this); horizontalScrollView.addView(attachmentView); } @Override public void onClick(View v) { if (v.getTag() != null) { ODataRow attachment = (ODataRow) v.getTag(); fileManager.downloadAttachment(attachment.getInt(OColumn.ROW_ID)); } else { switch (v.getId()) { case R.id.btnClose: finish(); break; case R.id.btnReply: extra.putString("type", MailChatterCompose.MessageType.Message.toString()); IntentUtils.startActivity(this, MailChatterCompose.class, extra); finish(); break; } } } @Override protected void onPause() { super.onPause(); if (loadAttachments != null) { loadAttachments.cancel(true); } } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/mail/widget/MessageObserver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 27/2/15 6:55 PM */ package com.odoo.base.addons.mail.widget; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; public class MessageObserver extends ContentObserver { public static final String TAG = MessageObserver.class.getSimpleName(); private OnDataSetChangeListener mOnDataSetChangeListener; public MessageObserver() { super(new Handler()); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); if (mOnDataSetChangeListener != null) { mOnDataSetChangeListener.onDataSetChange(); } } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); if (mOnDataSetChangeListener != null) { mOnDataSetChangeListener.onDataSetChange(); } } public void setOnDataSetChangeListener(OnDataSetChangeListener listener) { mOnDataSetChangeListener = listener; } public interface OnDataSetChangeListener { public void onDataSetChange(); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/res/ResCompany.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 12:53 PM */ package com.odoo.base.addons.res; import android.content.Context; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class ResCompany extends OModel { public static final String TAG = ResCompany.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); OColumn currency_id = new OColumn("Currency", ResCurrency.class, OColumn.RelationType.ManyToOne); public ResCompany(Context context, OUser user) { super(context, "res.company", user); } public static ODataRow getCurrency(Context context) { ResCompany company = new ResCompany(context, null); int row_id = company.selectRowId(Integer.parseInt(company.getUser().getCompany_id())); return company.browse(row_id).getM2ORecord("currency_id").browse(); } @Override public boolean allowCreateRecordOnServer() { return false; } @Override public boolean allowUpdateRecordOnServer() { return false; } @Override public boolean allowDeleteRecordInLocal() { return false; } public static int myId(Context context) { ResCompany company = new ResCompany(context, null); return company.selectRowId(Integer.parseInt(company.getUser().getCompany_id())); } public static int myCurrency(Context context) { ResCompany company = new ResCompany(context, null); ODataRow row = company.browse(company.selectRowId(Integer.parseInt(company. getUser().getCompany_id()))); return row.getM2ORecord("currency_id").browse().getInt(OColumn.ROW_ID); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/res/ResCountry.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:43 PM */ package com.odoo.base.addons.res; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class ResCountry extends OModel { OColumn name = new OColumn("Name", OVarchar.class).setSize(100); public ResCountry(Context context, OUser user) { super(context, "res.country", user); } @Override public boolean allowCreateRecordOnServer() { return false; } @Override public boolean allowUpdateRecordOnServer() { return false; } @Override public boolean allowDeleteRecordInLocal() { return false; } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/res/ResCurrency.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:19 AM */ package com.odoo.base.addons.res; import android.content.Context; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class ResCurrency extends OModel { public static final String TAG = ResCurrency.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); OColumn symbol = new OColumn("Symbol", OVarchar.class).setSize(10); public ResCurrency(Context context, OUser user) { super(context, "res.currency", user); } public static String getSymbol(Context context, int row_id) { ResCurrency resCurrency = new ResCurrency(context, null); ODataRow row = resCurrency.browse(row_id); return (row != null) ? row.getString("symbol") : ""; } @Override public boolean allowCreateRecordOnServer() { return false; } @Override public boolean allowUpdateRecordOnServer() { return false; } @Override public boolean allowDeleteRecordInLocal() { return false; } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/res/ResPartner.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 4:00 PM */ package com.odoo.base.addons.res; import android.content.Context; import android.net.Uri; import com.odoo.addons.sale.models.AccountPaymentTerm; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBlob; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; import org.json.JSONArray; public class ResPartner extends OModel { public static final String AUTHORITY = "com.odoo.core.crm.provider.content.sync.res_partner"; OColumn name = new OColumn("Name", OVarchar.class).setSize(100).setRequired(); OColumn is_company = new OColumn("Is Company", OBoolean.class).setDefaultValue(false); OColumn image_small = new OColumn("Avatar", OBlob.class).setDefaultValue(false); OColumn street = new OColumn("Street", OVarchar.class).setSize(100); OColumn street2 = new OColumn("Street2", OVarchar.class).setSize(100); OColumn city = new OColumn("City", OVarchar.class); OColumn zip = new OColumn("Zip", OVarchar.class); OColumn website = new OColumn("Website", OVarchar.class).setSize(100); OColumn phone = new OColumn("Phone", OVarchar.class).setSize(15); OColumn mobile = new OColumn("Mobile", OVarchar.class).setSize(15); OColumn email = new OColumn("Email", OVarchar.class); OColumn company_id = new OColumn("Company", ResCompany.class, OColumn.RelationType.ManyToOne); OColumn parent_id = new OColumn("Related Company", ResPartner.class, OColumn.RelationType.ManyToOne) .addDomain("is_company", "=", true); OColumn country_id = new OColumn("Country", ResCountry.class, OColumn.RelationType.ManyToOne); OColumn customer = new OColumn("Customer", OBoolean.class).setDefaultValue("true"); OColumn comment = new OColumn("Internal Note", OText.class); @Odoo.Functional(store = true, depends = {"parent_id"}, method = "storeCompanyName") OColumn company_name = new OColumn("Company Name", OVarchar.class).setSize(100) .setLocalColumn(); OColumn large_image = new OColumn("Image", OBlob.class).setDefaultValue("false").setLocalColumn(); OColumn partner_invoice_id = new OColumn("partner_invoice_id", OVarchar.class).setLocalColumn(); OColumn partner_shipping_id = new OColumn("partner_shipping_id", OVarchar.class).setLocalColumn(); OColumn pricelist_id = new OColumn("pricelist_id", OVarchar.class).setLocalColumn(); OColumn fiscal_position = new OColumn("fiscal_position", OVarchar.class).setLocalColumn(); OColumn payment_term = new OColumn("Payment Term", AccountPaymentTerm.class, OColumn.RelationType.ManyToOne).setLocalColumn(); public ResPartner(Context context, OUser user) { super(context, "res.partner", user); setHasMailChatter(true); } @Override public Uri uri() { return buildURI(AUTHORITY); } public String storeCompanyName(OValues value) { try { if (!value.getString("parent_id").equals("false")) { JSONArray parent_id = new JSONArray(value.getString("parent_id")); return parent_id.getString(1); } } catch (Exception e) { e.printStackTrace(); } return ""; } public static String getContact(Context context, int row_id) { ODataRow row = new ResPartner(context, null).browse(row_id); String contact; if (row.getString("mobile").equals("false")) { contact = row.getString("phone"); } else { contact = row.getString("mobile"); } return contact; } public String getAddress(ODataRow row) { String add = ""; if (!row.getString("street").equals("false")) add += row.getString("street") + ", "; if (!row.getString("street2").equals("false")) add += "\n" + row.getString("street2") + ", "; if (!row.getString("city").equals("false")) add += row.getString("city"); if (!row.getString("zip").equals("false")) add += " - " + row.getString("zip") + " "; return add; } public Uri liveSearchURI() { return uri().buildUpon().appendPath("live_searchable_customer").build(); } } ================================================ FILE: app/src/main/java/com/odoo/base/addons/res/ResUsers.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 10:16 AM */ package com.odoo.base.addons.res; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class ResUsers extends OModel { public static final String TAG = ResUsers.class.getSimpleName(); OColumn name = new OColumn("Name", OVarchar.class); OColumn login = new OColumn("User Login name", OVarchar.class); @Override public boolean allowCreateRecordOnServer() { return false; } @Override public boolean allowUpdateRecordOnServer() { return false; } @Override public boolean allowDeleteRecordInLocal() { return false; } public ResUsers(Context context, OUser user) { super(context, "res.users", user); } public static int myId(Context context) { ResUsers users = new ResUsers(context, null); return users.selectRowId(users.getUser().getUser_id()); } } ================================================ FILE: app/src/main/java/com/odoo/config/Addons.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:11 PM */ package com.odoo.config; import com.odoo.addons.calendar.CalendarDashboard; import com.odoo.addons.crm.CRMLeads; import com.odoo.addons.customers.Customers; import com.odoo.addons.phonecall.PhoneCalls; import com.odoo.addons.sale.Sales; import com.odoo.core.support.addons.AddonsHelper; import com.odoo.core.support.addons.OAddon; public class Addons extends AddonsHelper { /** * Declare your required module here * NOTE: For maintain sequence use object name in asc order. * Ex.: * OAddon partners = new OAddon(Partners.class).setDefault(); */ OAddon a_agenda = new OAddon(CalendarDashboard.class).setDefault(); OAddon b_partners = new OAddon(Customers.class); OAddon c_crm_leads = new OAddon(CRMLeads.class); OAddon e_sale = new OAddon(Sales.class); OAddon f_phone_calls = new OAddon(PhoneCalls.class); } ================================================ FILE: app/src/main/java/com/odoo/config/BaseConfig.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 5/1/15 5:55 PM */ package com.odoo.config; public class BaseConfig { /** * Do not remove following constants. */ public static final boolean DEVELOPER_MODE = true; } ================================================ FILE: app/src/main/java/com/odoo/config/IntroSliderItems.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/2/15 4:16 PM */ package com.odoo.config; import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.odoo.core.utils.OControls; import com.odoo.R; import com.odoo.widgets.slider.SliderItem; import com.odoo.widgets.slider.SliderPagerAdapter; import java.util.ArrayList; import java.util.List; public class IntroSliderItems implements SliderPagerAdapter.SliderBuilderListener, View.OnClickListener { public static final String TAG = IntroSliderItems.class.getSimpleName(); private Context mContext; public List getItems() { List items = new ArrayList<>(); items.add(new SliderItem("Daily Planner", "Odoo CRM keeps you organized, focused and more productive", R.drawable.slide_1, this) .putExtra("sub_title", "Manage everything in one place")); items.add(new SliderItem("Live caller ID", "Get information about customer and recent opportunity before you pickup the phone.", R.drawable.slide_3, this) .putExtra("sub_title", "See who's calling")); items.add(new SliderItem("Reminders", "Use reminders to make sure no phone-calls, meetings or opportunities forgotten", R.drawable.slide_4, this) .putExtra("sub_title", "Reminder with quick actions")); items.add(new SliderItem("Easy actions", "Odoo CRM offers simple, quick and easy actions at your fingertips", R.drawable.slide_5, this) .putExtra("sub_title", "Getting things done quickly")); items.add(new SliderItem("Manage quotations", "Create/Manage quotations and manage order lines easily", R.drawable.slide_6, this) .putExtra("sub_title", "Easily manage order lines")); items.add(new SliderItem("", "All the data automatically synchronized with server when you re-connect to internet", R.drawable.no_network, this) .putExtra("sub_title", "Works offline")); items.add(new SliderItem("", "Works with Odoo Saas cloud", R.drawable.saas_support, this) .putExtra("sub_title", "Odoo Saas Support")); items.add(new SliderItem("Let's Start", "", R.drawable.odoo_shaded, this) .putExtra("sub_title", "Start exploring Odoo CRM")); return items; } @Override public View getCustomView(Context context, SliderItem item, ViewGroup parent) { mContext = context; View view = LayoutInflater.from(context).inflate(R.layout.base_intro_slider_view, parent, false); OControls.setText(view, R.id.big_title, item.getTitle()); OControls.setImage(view, R.id.slider_image, item.getImagePath()); OControls.setText(view, R.id.sub_title, item.getExtras().get("sub_title").toString()); OControls.setText(view, R.id.description, item.getContent()); if (item.getImagePath() == R.drawable.odoo_shaded) { OControls.setGone(view, R.id.description); OControls.setVisible(view, R.id.btnSliderFinish); OControls.setText(view, R.id.btnSliderFinish, "GOT IT, LET'S GO!"); view.findViewById(R.id.btnSliderFinish).setOnClickListener(this); } else { OControls.setVisible(view, R.id.description); OControls.setGone(view, R.id.btnSliderFinish); } return view; } @Override public void onClick(View v) { if (v.getId() == R.id.btnSliderFinish) { ((Activity) mContext).finish(); } } } ================================================ FILE: app/src/main/java/com/odoo/core/account/About.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 11:33 AM */ package com.odoo.core.account; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.text.method.LinkMovementMethod; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.odoo.R; import com.odoo.base.addons.ir.IrModel; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.datas.OConstants; public class About extends ActionBarActivity implements View.OnClickListener { public static final String TAG = About.class.getSimpleName(); private final static String DEVELOPER_MODE = "developer_mode"; private Handler handler = null; private int click_count = 0; private Runnable runnable = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_about); OActionBarUtils.setActionBar(this, true); setTitle(""); findViewById(R.id.abtus_header).setOnClickListener(this); TextView versionName, aboutLine2, aboutLine3, aboutLine4; versionName = (TextView) findViewById(R.id.txvVersionName); handler = new Handler(); try { PackageManager packageManager = getPackageManager(); // setting version name from manifest file String version = packageManager.getPackageInfo( getPackageName(), 0).versionName; String versionCode = packageManager.getPackageInfo( getPackageName(), 0).versionCode + ""; versionName.setText(getResources() .getString(R.string.label_version) + " " + version + " (" + versionCode + ")"); // setting link in textView aboutLine2 = (TextView) findViewById(R.id.line2); if (aboutLine2 != null) { aboutLine2.setMovementMethod(LinkMovementMethod.getInstance()); } aboutLine3 = (TextView) findViewById(R.id.line3); if (aboutLine3 != null) { aboutLine3.setMovementMethod(LinkMovementMethod.getInstance()); } aboutLine4 = (TextView) findViewById(R.id.line4); if (aboutLine4 != null) { aboutLine4.setMovementMethod(LinkMovementMethod.getInstance()); } } catch (Exception e) { e.printStackTrace(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_about, menu); OPreferenceManager pref = new OPreferenceManager(this); if (pref.getBoolean(DEVELOPER_MODE, false)) { menu.findItem(R.id.menu_developer_mode).setVisible(true); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // app icon in action bar clicked; go home finish(); return true; case R.id.menu_about_our_apps: IntentUtils.openURLInBrowser(this, OConstants.URL_ODOO_APPS_ON_PLAY_STORE); return true; case R.id.menu_about_github: IntentUtils.openURLInBrowser(this, OConstants.URL_ODOO_MOBILE_GIT_HUB); return true; case R.id.menu_export_db: IrModel model = new IrModel(this, null); model.exportDB(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onClick(View v) { if (runnable == null) { runnable = new Runnable() { public void run() { click_count = 0; } }; handler.postDelayed(runnable, 7000); } click_count = click_count + 1; if (click_count == 3) { Toast.makeText(this, R.string.developer_2_tap, Toast.LENGTH_SHORT).show(); } if (click_count == 5) { OPreferenceManager pref = new OPreferenceManager(this); pref.setBoolean(DEVELOPER_MODE, true); Toast.makeText(this, R.string.developer_5_tap, Toast.LENGTH_SHORT).show(); finish(); startActivity(new Intent(this, About.class)); } } } ================================================ FILE: app/src/main/java/com/odoo/core/account/AppIntro.java ================================================ package com.odoo.core.account; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import com.odoo.R; import com.odoo.config.IntroSliderItems; import com.odoo.widgets.slider.SliderView; public class AppIntro extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_app_intro); SliderView sliderView = (SliderView) findViewById(R.id.sliderView); IntroSliderItems sliderItems = new IntroSliderItems(); if (!sliderItems.getItems().isEmpty()) { sliderView.setItems(getSupportFragmentManager(), sliderItems.getItems()); } else { finish(); } } } ================================================ FILE: app/src/main/java/com/odoo/core/account/BaseSettings.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 11:35 AM */ package com.odoo.core.account; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceFragment; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.util.Log; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.R; import odoo.controls.DateTimePicker; public class BaseSettings extends PreferenceFragment implements Preference.OnPreferenceClickListener, DateTimePicker.PickerCallBack { public static final String TAG = BaseSettings.class.getSimpleName(); public static final String KEY_ADD_ACCOUNT = "add_account"; private OPreferenceManager mPref; private Preference mTimePreference; // Keys public static final String KEY_LEAD_WORK_DAY_START_TIME = "lead_work_day_start_time"; public static final String KEY_NOTIFICATION_RING_TONE = "phonecall_notification_ringtone"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.base_preference); mPref = new OPreferenceManager(getActivity()); mTimePreference = findPreference(KEY_LEAD_WORK_DAY_START_TIME); if (mTimePreference != null) mTimePreference.setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference.getKey().equals(KEY_LEAD_WORK_DAY_START_TIME)) { showTimePicker(); return true; } return false; } private void showTimePicker() { String defaultDayStartTime = OResource.string(getActivity(), R.string.default_day_start_time); String time = mPref.getString(KEY_LEAD_WORK_DAY_START_TIME, defaultDayStartTime); DateTimePicker.Builder builder = new DateTimePicker.Builder(getActivity()); builder.setTime(ODateUtils.convertToUTC(time, ODateUtils.DEFAULT_TIME_FORMAT, ODateUtils.DEFAULT_TIME_FORMAT)); builder.setType(DateTimePicker.Type.Time); builder.setCallBack(this); builder.build().show(); } private void finish() { getActivity().finish(); } private ActionBar getActionBar() { return ((ActionBarActivity) getActivity()).getSupportActionBar(); } @Override public void onDatePick(String date) { } @Override public void onTimePick(String time) { Log.i(TAG, "Working start day time : " + time); mPref.putString(KEY_LEAD_WORK_DAY_START_TIME, time); } public static Uri getNotificationRingTone(Context context) { OPreferenceManager mPref = new OPreferenceManager(context); String defaultUri = OResource.string(context, R.string.notification_default_ring_tone); return Uri.parse(mPref.getString(KEY_NOTIFICATION_RING_TONE, defaultUri)); } public static String getDayStartTime(Context context) { String defaultDayStartTime = OResource.string(context, R.string.default_day_start_time); OPreferenceManager mPref = new OPreferenceManager(context); return mPref.getString(KEY_LEAD_WORK_DAY_START_TIME, defaultDayStartTime); } } ================================================ FILE: app/src/main/java/com/odoo/core/account/ManageAccounts.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 2:30 PM */ package com.odoo.core.account; import android.app.AlertDialog; import android.content.DialogInterface; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.odoo.OdooActivity; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.support.OUser; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.R; import java.util.ArrayList; import java.util.List; import odoo.controls.ExpandableListControl; public class ManageAccounts extends ActionBarActivity implements View.OnClickListener, ExpandableListControl.ExpandableListAdapterGetViewListener { private List accounts = new ArrayList<>(); private ExpandableListControl mList = null; private ExpandableListControl.ExpandableListAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_manage_accounts); setTitle(R.string.label_accounts); OActionBarUtils.setActionBar(this, true); setResult(RESULT_CANCELED); accounts.clear(); accounts.addAll(OdooAccountManager.getAllAccounts(this)); mList = (ExpandableListControl) findViewById(R.id.accountList); mAdapter = mList.getAdapter(R.layout.base_account_item, accounts, this); mAdapter.notifyDataSetChanged(accounts); } private void generateView(View view, OUser user) { OControls.setText(view, R.id.accountName, user.getName()); OControls.setText(view, R.id.accountURL, (user.isOAauthLogin()) ? user.getInstanceUrl() : user.getHost()); OControls.setImage(view, R.id.profile_image, R.drawable.avatar); if (!user.getAvatar().equals("false")) { Bitmap bmp = BitmapUtils.getBitmapImage(this, user.getAvatar()); if (bmp != null) OControls.setImage(view, R.id.profile_image, bmp); } if (user.isIsactive()) { OControls.setVisible(view, R.id.btnLogout); OControls.setGone(view, R.id.btnLogin); } else { OControls.setGone(view, R.id.btnLogout); OControls.setVisible(view, R.id.btnLogin); } view.findViewById(R.id.btnLogin).setTag(user); view.findViewById(R.id.btnLogout).setTag(user); view.findViewById(R.id.btnRemoveAccount).setTag(user); view.findViewById(R.id.btnLogout).setOnClickListener(this); view.findViewById(R.id.btnLogin).setOnClickListener(this); view.findViewById(R.id.btnRemoveAccount).setOnClickListener(this); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; } return super.onOptionsItemSelected(item); } @Override public void onClick(final View v) { switch (v.getId()) { case R.id.btnLogin: OUser user = (OUser) v.getTag(); OdooAccountManager.login(this, user.getAndroidName()); new Handler().postDelayed(new Runnable() { @Override public void run() { Toast.makeText(ManageAccounts.this, OResource.string(ManageAccounts.this, R.string.status_login_success), Toast.LENGTH_LONG).show(); setResult(RESULT_OK); finish(); } }, OdooActivity.DRAWER_ITEM_LAUNCH_DELAY); break; case R.id.btnLogout: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.title_confirm); builder.setMessage(R.string.toast_are_you_sure_logout); builder.setPositiveButton(R.string.label_logout, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { OUser user = (OUser) v.getTag(); OdooAccountManager.logout(ManageAccounts.this, user.getAndroidName()); new Handler().postDelayed(new Runnable() { @Override public void run() { Toast.makeText(ManageAccounts.this, OResource.string(ManageAccounts.this, R.string.status_logout_success), Toast.LENGTH_LONG).show(); setResult(RESULT_OK); finish(); } }, OdooActivity.DRAWER_ITEM_LAUNCH_DELAY); } }); builder.setNegativeButton(R.string.label_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.show(); break; case R.id.btnRemoveAccount: builder = new AlertDialog.Builder(this); builder.setTitle(R.string.title_confirm); builder.setMessage(R.string.toast_are_you_sure_delete_account); builder.setPositiveButton(R.string.label_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { OUser user = (OUser) v.getTag(); OdooAccountManager.removeAccount(ManageAccounts.this, user.getAndroidName()); new Handler().postDelayed(new Runnable() { @Override public void run() { Toast.makeText(ManageAccounts.this, OResource.string(ManageAccounts.this, R.string.toast_account_removed), Toast.LENGTH_LONG).show(); setResult(RESULT_OK); finish(); } }, OdooActivity.DRAWER_ITEM_LAUNCH_DELAY); } }); builder.setNegativeButton(R.string.label_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.show(); break; } } @Override public View getView(int position, View view, ViewGroup parent) { generateView(view, (OUser) mAdapter.getItem(position)); return view; } } ================================================ FILE: app/src/main/java/com/odoo/core/account/OdooAccountQuickManage.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 16/2/15 12:52 PM */ package com.odoo.core.account; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import com.odoo.OdooActivity; import com.odoo.R; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.support.OUser; import com.odoo.core.support.OdooLoginHelper; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; public class OdooAccountQuickManage extends ActionBarActivity implements View.OnClickListener { public static final String TAG = OdooAccountQuickManage.class.getSimpleName(); private OUser user = null; private ImageView userAvatar; private TextView txvName; private OdooLoginHelper loginHelper; private LoginProcess loginProcess = null; private EditText edtPassword; private String action; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_account_quick_manage); getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getSupportActionBar().hide(); action = getIntent().getAction(); // Removing notification ONotificationBuilder.cancelNotification(this, OSyncAdapter.REQUEST_SIGN_IN_ERROR); user = OdooAccountManager.getDetails(this, getIntent().getStringExtra("android_name")); if (action.equals("remove_account")) { findViewById(R.id.layoutSavePassword).setVisibility(View.GONE); removeAccount(); } else if (action.equals("reset_password")) { updatePassword(); findViewById(R.id.cancel).setOnClickListener(this); findViewById(R.id.save_password).setOnClickListener(this); } } private void updatePassword() { userAvatar = (ImageView) findViewById(R.id.userAvatar); Bitmap userImage = BitmapUtils.getAlphabetImage(this, user.getName()); if (!user.getAvatar().equals("false")) { userImage = BitmapUtils.getBitmapImage(this, user.getAvatar()); } userAvatar.setImageBitmap(userImage); txvName = (TextView) findViewById(R.id.userName); txvName.setText(user.getName()); } private void removeAccount() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.title_confirm); builder.setMessage(R.string.toast_are_you_sure_delete_account); builder.setPositiveButton(R.string.label_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (OdooAccountManager.removeAccount( OdooAccountQuickManage.this, user.getAndroidName())) { new Handler().postDelayed(new Runnable() { @Override public void run() { Intent loginActivity = new Intent(OdooAccountQuickManage.this, OdooLogin.class); loginActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(loginActivity); finish(); } }, 500); } } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); finish(); } }); builder.setNegativeButton(R.string.label_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); finish(); } }); builder.show(); } @Override public void onClick(View v) { ONotificationBuilder.cancelNotification(this, OSyncAdapter.REQUEST_SIGN_IN_ERROR); switch (v.getId()) { case R.id.cancel: finish(); break; case R.id.save_password: savePassword(); break; } } private void savePassword() { edtPassword = (EditText) findViewById(R.id.newPassword); edtPassword.setError(null); if (TextUtils.isEmpty(edtPassword.getText())) { edtPassword.setError("Password required"); edtPassword.requestFocus(); } user.setPassword(edtPassword.getText().toString()); loginProcess = new LoginProcess(); loginHelper = new OdooLoginHelper(getApplicationContext()); loginProcess.execute(user.getDBName(), user.getHost()); } private class LoginProcess extends AsyncTask { private ProgressDialog progressDialog; @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(OdooAccountQuickManage.this); progressDialog.setTitle(R.string.title_working); progressDialog.setMessage(OResource.string(OdooAccountQuickManage.this, R.string.toast_updating_password)); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected OUser doInBackground(String... params) { try { return loginHelper.login(user.getUsername(), user.getPassword(), user.getDatabase(), params[1], user.isAllowSelfSignedSSL()); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(OUser oUser) { super.onPostExecute(oUser); progressDialog.dismiss(); if (oUser != null) { OdooAccountManager.updateUserData(OdooAccountQuickManage.this, user); finish(); Intent intent = new Intent(OdooAccountQuickManage.this, OdooActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); } else { edtPassword.setText(""); edtPassword.setError("Password required"); } } } } ================================================ FILE: app/src/main/java/com/odoo/core/account/OdooLogin.java ================================================ package com.odoo.core.account; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.odoo.OdooActivity; import com.odoo.R; import com.odoo.base.addons.res.ResCompany; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.auth.OdooAuthenticator; import com.odoo.core.orm.ODataRow; import com.odoo.core.support.OUser; import com.odoo.core.support.OdooInstancesSelectorDialog; import com.odoo.core.support.OdooLoginHelper; import com.odoo.core.support.OdooServerTester; import com.odoo.core.support.OdooUserLoginSelectorDialog; import com.odoo.core.utils.IntentUtils; import com.odoo.core.utils.OAlertDialog; import com.odoo.core.utils.OResource; import com.odoo.datas.OConstants; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; import odoo.OdooAccountExpireException; import odoo.OdooInstance; public class OdooLogin extends ActionBarActivity implements View.OnClickListener, View.OnFocusChangeListener, OdooInstancesSelectorDialog.OnInstanceSelectListener, OdooUserLoginSelectorDialog.IUserLoginSelectListener { private EditText edtUsername, edtPassword, edtSelfHosted; private Boolean mCreateAccountRequest = false; private Boolean mSelfHostedURL = false; private Boolean mForceConnect = false; private Boolean mConnectedToServer = false; private Boolean mAutoLogin = false; private Boolean mRequestedForAccount = false; private OdooURLTester odooURLTester = null; private LoginProcess loginProcess = null; private AccountCreater accountCreator = null; private OdooServerTester mServerTester = null; private InstanceGetter instanceGetter = null; private OdooLoginHelper loginHelper = null; private Spinner databaseSpinner = null; private List databases = new ArrayList(); private TextView mLoginProcessStatus = null; private TextView mTermsCondition; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_login); Bundle extras = getIntent().getExtras(); if (extras != null) { if (extras.containsKey(OdooAuthenticator.KEY_NEW_ACCOUNT_REQUEST)) mCreateAccountRequest = true; if (extras.containsKey(OdooActivity.KEY_ACCOUNT_REQUEST)) { mRequestedForAccount = true; setResult(RESULT_CANCELED); } } if (!mCreateAccountRequest) { if (OdooAccountManager.anyActiveUser(this)) { startOdooActivity(); return; } else if (OdooAccountManager.hasAnyAccount(this)) { onRequestAccountSelect(); } } init(); } private void init() { loginHelper = new OdooLoginHelper(this); mServerTester = new OdooServerTester(this); mLoginProcessStatus = (TextView) findViewById(R.id.login_process_status); mTermsCondition = (TextView) findViewById(R.id.termsCondition); mTermsCondition.setMovementMethod(LinkMovementMethod.getInstance()); findViewById(R.id.btnLogin).setOnClickListener(this); findViewById(R.id.forgot_password).setOnClickListener(this); findViewById(R.id.create_account).setOnClickListener(this); findViewById(R.id.txvAddSelfHosted).setOnClickListener(this); edtSelfHosted = (EditText) findViewById(R.id.edtSelfHostedURL); } private void startOdooActivity() { startActivity(new Intent(this, OdooActivity.class)); finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_base_login, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.txvAddSelfHosted: toggleSelfHostedURL(); break; case R.id.btnLogin: loginUser(); break; case R.id.forgot_password: IntentUtils.openURLInBrowser(this, OConstants.URL_ODOO_RESET_PASSWORD); break; case R.id.create_account: IntentUtils.openURLInBrowser(this, OConstants.URL_ODOO_SIGN_UP); break; } } private void toggleSelfHostedURL() { TextView txvAddSelfHosted = (TextView) findViewById(R.id.txvAddSelfHosted); if (!mSelfHostedURL) { mSelfHostedURL = true; findViewById(R.id.layoutSelfHosted).setVisibility(View.VISIBLE); edtSelfHosted.setOnFocusChangeListener(this); edtSelfHosted.requestFocus(); txvAddSelfHosted.setText(R.string.label_login_with_odoo); } else { findViewById(R.id.layoutBorderDB).setVisibility(View.GONE); findViewById(R.id.layoutDatabase).setVisibility(View.GONE); findViewById(R.id.layoutSelfHosted).setVisibility(View.GONE); mSelfHostedURL = false; txvAddSelfHosted.setText(R.string.label_add_self_hosted_url); edtSelfHosted.setText(""); } } @Override public void onFocusChange(final View v, final boolean hasFocus) { new Handler().postDelayed(new Runnable() { @Override public void run() { if (odooURLTester != null) { odooURLTester.cancel(true); } if (mSelfHostedURL && v.getId() == R.id.edtSelfHostedURL && !hasFocus) { if (!TextUtils.isEmpty(edtSelfHosted.getText()) && validateURL(edtSelfHosted.getText().toString())) { String test_url = createServerURL(edtSelfHosted.getText().toString()); odooURLTester = new OdooURLTester(); odooURLTester.execute(test_url); } } } }, 500); } private boolean validateURL(String url) { return (url.contains(".")); } private String createServerURL(String server_url) { StringBuilder serverURL = new StringBuilder(); if (!server_url.contains("http://") && !server_url.contains("https://")) { serverURL.append("http://"); } serverURL.append(server_url); return serverURL.toString(); } private void showForceConnectDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.title_ssl_warning); builder.setMessage(R.string.untrusted_ssl_warning); builder.setPositiveButton(R.string.label_process_anyway, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (odooURLTester != null) { odooURLTester.cancel(true); odooURLTester = null; } mForceConnect = true; odooURLTester = new OdooURLTester(); odooURLTester.execute(createServerURL(edtSelfHosted.getText().toString())); } }); builder.setNegativeButton(R.string.label_cancel, null); builder.show(); } // User Login private void loginUser() { String serverURL = createServerURL((mSelfHostedURL) ? edtSelfHosted.getText().toString() : OConstants.URL_ODOO_ACCOUNTS); String databaseName = null; edtUsername = (EditText) findViewById(R.id.edtUserName); edtPassword = (EditText) findViewById(R.id.edtPassword); if (mSelfHostedURL) { edtSelfHosted.setError(null); if (TextUtils.isEmpty(edtSelfHosted.getText())) { edtSelfHosted.setError(OResource.string(this, R.string.error_provide_server_url)); edtSelfHosted.requestFocus(); return; } if (databaseSpinner != null && databases.size() > 1 && databaseSpinner.getSelectedItemPosition() == 0) { Toast.makeText(this, OResource.string(this, R.string.label_select_database), Toast.LENGTH_LONG).show(); return; } } edtUsername.setError(null); edtPassword.setError(null); if (TextUtils.isEmpty(edtUsername.getText())) { edtUsername.setError(OResource.string(this, R.string.error_provide_username)); edtUsername.requestFocus(); return; } if (TextUtils.isEmpty(edtPassword.getText())) { edtPassword.setError(OResource.string(this, R.string.error_provide_password)); edtPassword.requestFocus(); return; } if (mConnectedToServer) { databaseName = databases.get(0); if (databaseSpinner != null) { databaseName = databases.get(databaseSpinner.getSelectedItemPosition()); } mAutoLogin = false; if (loginProcess != null) { loginProcess.cancel(true); } loginProcess = new LoginProcess(); loginProcess.execute(databaseName, serverURL); } else { if (odooURLTester != null) odooURLTester.cancel(true); mAutoLogin = true; odooURLTester = new OdooURLTester(); odooURLTester.execute(serverURL); } } private void showDatabases() { if (databases.size() > 1) { findViewById(R.id.layoutBorderDB).setVisibility(View.VISIBLE); findViewById(R.id.layoutDatabase).setVisibility(View.VISIBLE); databaseSpinner = (Spinner) findViewById(R.id.spinnerDatabaseList); databases.add(0, OResource.string(this, R.string.label_select_database)); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, databases); databaseSpinner.setAdapter(adapter); } else { databaseSpinner = null; findViewById(R.id.layoutBorderDB).setVisibility(View.GONE); findViewById(R.id.layoutDatabase).setVisibility(View.GONE); } } @Override public void instanceSelected(OdooInstance instance, OUser user) { OUser userData = user; if (!instance.getInstanceUrl().equals(OConstants.URL_ODOO)) { if (loginProcess != null) loginProcess.cancel(true); loginProcess = new LoginProcess(instance, user); loginProcess.execute(); return; } accountCreator = new AccountCreater(); accountCreator.execute(userData); } @Override public void canceledInstanceSelect() { findViewById(R.id.controls).setVisibility(View.VISIBLE); findViewById(R.id.login_progress).setVisibility(View.GONE); } @Override public void onUserSelected(OUser user) { OdooAccountManager.login(this, user.getAndroidName()); startOdooActivity(); } @Override public void onRequestAccountSelect() { OdooUserLoginSelectorDialog dialog = new OdooUserLoginSelectorDialog(this); dialog.setUserLoginSelectListener(this); dialog.show(); } @Override public void onNewAccountRequest() { init(); } @Override public void onCancelSelect() { } private class LoginProcess extends AsyncTask { private OdooInstance mInstance; private OUser mUser; private String mExpireMessage = null; public LoginProcess() { } public LoginProcess(OdooInstance instance, OUser user) { mInstance = instance; mUser = user; } @Override protected void onPreExecute() { super.onPreExecute(); findViewById(R.id.controls).setVisibility(View.GONE); findViewById(R.id.login_progress).setVisibility(View.VISIBLE); if (mInstance != null && mUser != null) mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_logging_in_with_instance)); else mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_logging_in)); } @Override protected OUser doInBackground(String... params) { try { if (mInstance == null && mUser == null) { String username = edtUsername.getText().toString(); String password = edtPassword.getText().toString(); return loginHelper.login(username, password, params[0], params[1], mForceConnect); } else { mSelfHostedURL = true; return loginHelper.instanceLogin(mInstance, mUser); } } catch (OdooAccountExpireException expired) { mExpireMessage = expired.getMessage(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(OUser user) { super.onPostExecute(user); edtUsername.setError(null); if (user == null) { findViewById(R.id.controls).setVisibility(View.VISIBLE); findViewById(R.id.login_progress).setVisibility(View.GONE); if (mExpireMessage != null) { mSelfHostedURL = false; OAlertDialog dialog = new OAlertDialog(OdooLogin.this); dialog.setTitle(OResource.string(OdooLogin.this, R.string.title_instance_expired)); dialog.setMessage(mExpireMessage); dialog.show(); } else { edtUsername.setError(OResource.string(OdooLogin.this, R.string.error_invalid_username_or_password)); } } else { mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_login_success)); if (!mSelfHostedURL) { instanceGetter = new InstanceGetter(); instanceGetter.execute(user); } else { accountCreator = new AccountCreater(); accountCreator.execute(user); } } } } private class InstanceGetter extends AsyncTask> { private OUser mUser; @Override protected void onPreExecute() { super.onPreExecute(); mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_getting_instances)); } @Override protected List doInBackground(OUser... params) { mUser = params[0]; return loginHelper.getOdooInstances(mUser); } @Override protected void onPostExecute(List odooInstances) { super.onPostExecute(odooInstances); if (odooInstances.size() > 1) { OdooInstancesSelectorDialog instancesSelectorDialog = new OdooInstancesSelectorDialog(OdooLogin.this, mUser); instancesSelectorDialog.setInstances(odooInstances); instancesSelectorDialog.setOnInstanceSelectListener(OdooLogin.this); instancesSelectorDialog.showDialog(); } else { // Login to default odoo instance (www.odoo.com) accountCreator = new AccountCreater(); accountCreator.execute(mUser); } } } private class AccountCreater extends AsyncTask { private OUser mUser; @Override protected void onPreExecute() { super.onPreExecute(); mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_creating_account)); } @Override protected Boolean doInBackground(OUser... params) { mUser = params[0]; if (OdooAccountManager.createAccount(OdooLogin.this, params[0])) { mUser = OdooAccountManager.getDetails(OdooLogin.this, mUser.getAndroidName()); try { // Syncing company details ODataRow company_details = new ODataRow(); company_details.put("id", mUser.getCompany_id()); ResCompany company = new ResCompany(OdooLogin.this, mUser); company.quickCreateRecord(company_details); Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } return true; } return false; } @Override protected void onPostExecute(Boolean success) { super.onPostExecute(success); mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_redirecting)); new Handler().postDelayed(new Runnable() { @Override public void run() { if (!mRequestedForAccount) startOdooActivity(); else { Intent intent = new Intent(); intent.putExtra(OdooActivity.KEY_NEW_USER_NAME, mUser.getAndroidName()); setResult(RESULT_OK, intent); finish(); } } }, 1500); } } private class OdooURLTester extends AsyncTask { private Boolean mRequiredForceConnect = false; @Override protected void onPreExecute() { super.onPreExecute(); Log.v("OdooURLTester", "Connecting to Server"); edtSelfHosted.setError(null); if (mAutoLogin) { findViewById(R.id.controls).setVisibility(View.GONE); findViewById(R.id.login_progress).setVisibility(View.VISIBLE); mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_connecting_to_server)); } findViewById(R.id.imgValidURL).setVisibility(View.GONE); findViewById(R.id.serverURLCheckProgress).setVisibility(View.VISIBLE); findViewById(R.id.layoutBorderDB).setVisibility(View.GONE); findViewById(R.id.layoutDatabase).setVisibility(View.GONE); } @Override protected Boolean doInBackground(String... params) { try { return mServerTester.testConnection(params[0], mForceConnect); } catch (SSLPeerUnverifiedException peer) { mRequiredForceConnect = true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override protected void onPostExecute(Boolean success) { super.onPostExecute(success); findViewById(R.id.serverURLCheckProgress).setVisibility(View.GONE); edtSelfHosted.setError(null); if (success) { // Connected to server Log.v("OdooURLTester", "Connected to server."); mLoginProcessStatus.setText(OResource.string(OdooLogin.this, R.string.status_connected_to_server)); databases.clear(); databases.addAll(mServerTester.getDatabases()); showDatabases(); mConnectedToServer = true; findViewById(R.id.imgValidURL).setVisibility(View.VISIBLE); if (mAutoLogin) { loginUser(); } } else if (mRequiredForceConnect) { showForceConnectDialog(); } else { edtSelfHosted.setError(OResource.string(OdooLogin.this, R.string.error_invalid_odoo_url)); edtSelfHosted.requestFocus(); } } } } ================================================ FILE: app/src/main/java/com/odoo/core/account/OdooUserAskPassword.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/4/15 12:06 PM */ package com.odoo.core.account; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.Toast; import com.odoo.R; import com.odoo.core.support.OUser; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OControls; public class OdooUserAskPassword { public static final String TAG = OdooUserAskPassword.class.getSimpleName(); private Context mContext; private OUser mUser; private OnUserPasswordValidateListener mOnUserPasswordValidateListener; private AlertDialog.Builder builder; private EditText edtPassword; public OdooUserAskPassword(Context context, OUser user) { mContext = context; mUser = user; builder = new AlertDialog.Builder(context); } public static OdooUserAskPassword get(Context context, OUser user) { return new OdooUserAskPassword(context, user); } public void show() { builder.setView(getView()); builder.setPositiveButton(R.string.label_login, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String password = edtPassword.getText().toString(); if (TextUtils.isEmpty(password)) { Toast.makeText(mContext, R.string.error_provide_password, Toast.LENGTH_LONG).show(); show(); } else { if (password.equals(mUser.getPassword())) { mOnUserPasswordValidateListener.onSuccess(); } else { mOnUserPasswordValidateListener.onFail(); } } dialog.dismiss(); } }); builder.setNegativeButton(R.string.label_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); mOnUserPasswordValidateListener.onCancel(); } }); builder.create().show(); } private View getView() { View view = LayoutInflater.from(mContext) .inflate(R.layout.base_account_ask_pass, null, false); String avatar = mUser.getAvatar(); Bitmap bitmap; if (avatar.equals("false")) { bitmap = BitmapUtils.getAlphabetImage(mContext, mUser.getName()); } else { bitmap = BitmapUtils.getBitmapImage(mContext, avatar); } OControls.setImage(view, R.id.userAvatar, bitmap); OControls.setText(view, R.id.txvUsername, mUser.getName()); edtPassword = (EditText) view.findViewById(R.id.edtPassword); return view; } public OdooUserAskPassword setOnUserPasswordValidateListener(OnUserPasswordValidateListener listener) { mOnUserPasswordValidateListener = listener; return this; } public interface OnUserPasswordValidateListener { public void onSuccess(); public void onCancel(); public void onFail(); } } ================================================ FILE: app/src/main/java/com/odoo/core/account/Profile.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 11:54 AM */ package com.odoo.core.account; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.odoo.R; import com.odoo.core.orm.ODataRow; import com.odoo.core.support.OUser; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OActionBarUtils; import com.odoo.core.utils.OStringColorUtil; import com.odoo.widgets.parallax.ParallaxScrollView; import odoo.controls.OField; import odoo.controls.OForm; public class Profile extends ActionBarActivity implements View.OnClickListener { public static final String TAG = Profile.class.getSimpleName(); private OUser user; private OForm form; private ParallaxScrollView parallaxScrollView; private TextView title; private Handler handler = null; private int click_count = 0; public static String CONNECT_WITH_ODOO = "enable_connect_with_odoo"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_profile); OActionBarUtils.setActionBar(this, false); user = OUser.current(this); form = (OForm) findViewById(R.id.profileDetails); OField user_login = (OField) findViewById(R.id.user_login); parallaxScrollView = (ParallaxScrollView) findViewById(R.id.parallaxScrollView); parallaxScrollView.setActionBar(getSupportActionBar()); setTitle(""); getSupportActionBar().setBackgroundDrawable(new ColorDrawable((Color.parseColor("#00000000")))); title = (TextView) findViewById(android.R.id.title); int color = OStringColorUtil.getStringColor(this, user.getName()); parallaxScrollView.setParallaxOverLayColor(color); form.setIconTintColor(color); ODataRow userData = new ODataRow(); userData.put("name", user.getName()); userData.put("user_login", user.getUsername()); userData.put("server_url", (user.isOAauthLogin()) ? user.getInstanceUrl() : user.getHost()); userData.put("database", (user.isOAauthLogin()) ? user.getInstanceDatabase() : user.getDatabase()); userData.put("version", user.getVersion_serie()); userData.put("timezone", user.getTimezone()); form.initForm(userData); title.setText(userData.getString("name")); Bitmap avatar; if (user.getAvatar().equals("false")) { avatar = BitmapUtils.getAlphabetImage(this, user.getName()); } else { avatar = BitmapUtils.getBitmapImage(this, user.getAvatar()); } ImageView imageView = (ImageView) findViewById(android.R.id.icon); imageView.setImageBitmap(avatar); user_login.setOnClickListener(this); } @Override public void onClick(View v) { // FIXME: // // handler = getWindow().getDecorView().getHandler(); // click_count = click_count + 1; // Runnable r = new Runnable() { // public void run() { // click_count = 0; // } // }; // handler.postDelayed(r, 7000); // // if (click_count == 3) { // Toast.makeText(this, "Need 2 Tap to connect with odoo", Toast.LENGTH_SHORT).show(); // } // if (click_count == 5) { // OPreferenceManager pref = new OPreferenceManager(this); // pref.setBoolean(CONNECT_WITH_ODOO, true); // } } } ================================================ FILE: app/src/main/java/com/odoo/core/auth/OdooAccountManager.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 17/12/14 6:21 PM */ package com.odoo.core.auth; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.util.Log; import com.odoo.App; import com.odoo.core.orm.OModelRegistry; import com.odoo.core.support.OUser; import com.odoo.core.utils.sys.OCacheUtils; import java.util.ArrayList; import java.util.List; public class OdooAccountManager { public static final String TAG = OdooAccountManager.class.getSimpleName(); public static final String KEY_ACCOUNT_TYPE = "com.odoo.auth"; /** * Gets all the account related Odoo Auth * * @param context * @return List of OUser instances if any */ public static List getAllAccounts(Context context) { List users = new ArrayList(); AccountManager aManager = AccountManager.get(context); for (Account account : aManager.getAccountsByType(KEY_ACCOUNT_TYPE)) { OUser user = new OUser(); user.fillFromAccount(aManager, account); user.setAccount(account); users.add(user); } return users; } /** * Check for any account availability * * @param context * @return true, if any account found */ public static boolean hasAnyAccount(Context context) { if (getAllAccounts(context).size() > 0) return true; return false; } /** * Creates Odoo account for app * * @param context * @param user user instance (OUser) * @return true, if account created successfully */ public static boolean createAccount(Context context, OUser user) { AccountManager accountManager = AccountManager.get(context); Account account = new Account(user.getAndroidName(), KEY_ACCOUNT_TYPE); return accountManager.addAccountExplicitly(account, String.valueOf(user.getPassword()), user.getAsBundle()); } /** * Remove account from device * * @param context * @param username * @return true, if account removed successfully */ public static boolean removeAccount(Context context, String username) { OUser user = getDetails(context, username); if (user != null) { AccountManager accountManager = AccountManager.get(context); accountManager.removeAccount(user.getAccount(), null, null); return true; } return false; } /** * Updates user bundle data in accounts * * @param context * @param newData instance of OUser class * @return new user object with updated values */ public static OUser updateUserData(Context context, OUser newData) { OUser user = getDetails(context, newData.getAndroidName()); if (user != null) { AccountManager accountManager = AccountManager.get(context); for (String key : newData.getAsBundle().keySet()) { accountManager.setUserData(user.getAccount(), key, newData.getAsBundle().getString(key)); } } return newData; } /** * Finds any active user for application * * @param context * @return true, if there is any active user for app */ public static boolean anyActiveUser(Context context) { for (OUser user : getAllAccounts(context)) { if (user.isIsactive()) { return true; } } return false; } /** * Gets active user object * * @param context * @return user object (Instance of OUser class) */ public static OUser getActiveUser(Context context) { for (OUser user : getAllAccounts(context)) { if (user.isIsactive()) { return user; } } return null; } /** * Returns OUser object with username * * @param context * @param username * @return instance for OUser class or null */ public static OUser getDetails(Context context, String username) { for (OUser user : getAllAccounts(context)) if (user.getAndroidName().equals(username)) { return user; } return null; } /** * Login to user account. changes active state for user. * Other users will be automatically logged out * * @param context * @param username * @return new user object */ public static OUser login(Context context, String username) { // Setting odoo instance to null App app = (App) context.getApplicationContext(); app.setOdoo(null, null); // Clearing models registry OModelRegistry registry = new OModelRegistry(); registry.clearAll(); OUser activeUser = getActiveUser(context); // Logging out user if any if (activeUser != null) { logout(context, activeUser.getAndroidName()); } OUser newUser = getDetails(context, username); if (newUser != null) { AccountManager accountManager = AccountManager.get(context); accountManager.setUserData(newUser.getAccount(), "isactive", "true"); Log.i(TAG, newUser.getName() + " Logged in successfully"); return newUser; } // Clearing old cache of the system OCacheUtils.clearSystemCache(context); return null; } /** * Logout user * * @param context * @param username * @return true, if successfully logged out */ public static boolean logout(Context context, String username) { OUser user = getDetails(context, username); if (user != null) { if (cancelUserSync(user.getAccount())) { AccountManager accountManager = AccountManager.get(context); accountManager.setUserData(user.getAccount(), "isactive", "false"); Log.i(TAG, user.getName() + " Logged out successfully"); return true; } } return false; } private static boolean cancelUserSync(Account account) { //Cancel user's sync services. if any. return true; } } ================================================ FILE: app/src/main/java/com/odoo/core/auth/OdooAuthService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 17/12/14 6:20 PM */ package com.odoo.core.auth; import android.accounts.AccountManager; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class OdooAuthService extends Service { private OdooAuthenticator mAuthenticator; @Override public IBinder onBind(Intent intent) { IBinder binder = null; if (intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) { mAuthenticator = new OdooAuthenticator(this); binder = mAuthenticator.getIBinder(); } return binder; } } ================================================ FILE: app/src/main/java/com/odoo/core/auth/OdooAuthenticator.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 17/12/14 6:21 PM */ package com.odoo.core.auth; import android.accounts.AbstractAccountAuthenticator; import android.accounts.Account; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; import android.accounts.NetworkErrorException; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import com.odoo.core.account.OdooLogin; import com.odoo.core.orm.OSQLite; import com.odoo.core.support.OUser; public class OdooAuthenticator extends AbstractAccountAuthenticator { public static final String TAG = OdooAuthenticator.class.getSimpleName(); public static final String KEY_NEW_ACCOUNT_REQUEST = "create_new_account"; private Context mContext; public OdooAuthenticator(Context context) { super(context); mContext = context; } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { final Bundle result; final Intent intent; intent = new Intent(mContext, OdooLogin.class); result = new Bundle(); intent.putExtra(KEY_NEW_ACCOUNT_REQUEST, true); result.putParcelable(AccountManager.KEY_INTENT, intent); return result; } @NonNull @Override public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) throws NetworkErrorException { Bundle result = super.getAccountRemovalAllowed(response, account); if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) && !result.containsKey(AccountManager.KEY_INTENT)) { final boolean removalAllowed = result .getBoolean(AccountManager.KEY_BOOLEAN_RESULT); if (removalAllowed) { OUser user = OdooAccountManager.getDetails(mContext, account.name); OSQLite sqLite = new OSQLite(mContext, user); sqLite.dropDatabase(); } } return result; } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override public String getAuthTokenLabel(String authTokenType) { return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { return null; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/ODataRow.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:47 PM */ package com.odoo.core.orm; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import com.odoo.core.orm.fields.OColumn; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class ODataRow implements Parcelable { public static final String TAG = ODataRow.class.getSimpleName(); HashMap _data = new HashMap<>(); public void put(String key, Object value) { _data.put(key, value); } public Object get(String key) { return _data.get(key); } public Integer getInt(String key) { if (_data.get(key).toString().equals("false")) return 0; else return Integer.parseInt(_data.get(key).toString()); } public Float getFloat(String key) { if (_data.get(key).toString().equals("false")) { _data.put(key, 0); } return Float.parseFloat(_data.get(key).toString()); } public String getString(String key) { if (_data.containsKey(key) && _data.get(key) != null) return _data.get(key).toString(); else return "false"; } public Boolean getBoolean(String key) { return Boolean.parseBoolean(_data.get(key).toString()); } public OM2ORecord getM2ORecord(String key) { return (OM2ORecord) _data.get(key); } public OM2MRecord getM2MRecord(String key) { return (OM2MRecord) _data.get(key); } public OO2MRecord getO2MRecord(String key) { return (OO2MRecord) _data.get(key); } public List values() { List values = new ArrayList<>(); values.addAll(_data.values()); return values; } public List keys() { List list = new ArrayList<>(); list.addAll(_data.keySet()); return list; } public boolean contains(String key) { return _data.containsKey(key); } public int size() { return _data.size(); } @Override public String toString() { return _data.toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } public void remove(String key) { _data.remove(key); } public class IdName { Integer id; String name; public IdName(Integer id, String name) { super(); this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public void addAll(HashMap data) { _data.putAll(data); } public void addAll(ODataRow row) { _data.putAll(row.getAll()); } public HashMap getAll() { return _data; } public OValues toValues() { OValues values = new OValues(); values.addAll(getAll()); return values; } public Bundle getPrimaryBundleData() { Bundle bundle = new Bundle(); bundle.putInt("id", getInt("id")); bundle.putInt(OColumn.ROW_ID, getInt(OColumn.ROW_ID)); return bundle; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OM2MRecord.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:51 PM */ package com.odoo.core.orm; import com.odoo.core.orm.fields.OColumn; import java.util.ArrayList; import java.util.List; public class OM2MRecord { public static final String TAG = OM2MRecord.class.getSimpleName(); private OColumn mCol = null; private int mId = 0; private OModel mDatabase = null; public OM2MRecord(OModel model, OColumn col, int id) { mDatabase = model; mCol = col; mId = id; } public List getRelIds() { List ids = new ArrayList<>(); for (ODataRow row : mDatabase.selectManyToManyRecords(new String[]{OColumn.ROW_ID}, mCol.getName(), mId)) { ids.add(row.getInt(OColumn.ROW_ID)); } return ids; } public List browseEach() { return mDatabase.selectManyToManyRecords(null, mCol.getName(), mId); } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OM2ORecord.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:50 PM */ package com.odoo.core.orm; import com.odoo.core.orm.fields.OColumn; public class OM2ORecord { public static final String TAG = OM2ORecord.class.getSimpleName(); private OColumn mCol = null; private Integer record_id = 0; private OModel base_model = null; private OModel rel_model = null; public OM2ORecord(OModel base, OColumn col, Integer rec_id) { base_model = base; mCol = col; record_id = rec_id; } public Integer getId() { return record_id; } public String getName() { rel_model = base_model.createInstance(mCol.getType()); return rel_model.browse(new String[]{"name"}, OColumn.ROW_ID + "=?", new String[]{record_id + ""}).getString("name"); } public ODataRow browse(OModel rel_model) { if (record_id != null) { return rel_model.browse(record_id); } return null; } public ODataRow browse() { rel_model = base_model.createInstance(mCol.getType()); return browse(rel_model); } @Override public String toString() { return record_id + ""; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OModel.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:31 PM */ package com.odoo.core.orm; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SyncResult; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; import com.odoo.App; import com.odoo.base.addons.ir.IrModel; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.orm.annotation.Odoo; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OSelection; import com.odoo.core.orm.provider.BaseModelProvider; import com.odoo.core.service.ISyncServiceListener; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.support.OUser; import com.odoo.core.support.OdooFields; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OListUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OStorageUtils; import com.odoo.core.utils.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InvalidObjectException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import odoo.ODomain; import odoo.OdooVersion; public class OModel implements ISyncServiceListener { public static final String TAG = OModel.class.getSimpleName(); public String BASE_AUTHORITY = "com.odoo.crm.core.provider.content"; public static final String KEY_UPDATE_IDS = "key_update_ids"; public static final String KEY_INSERT_IDS = "key_insert_ids"; public static final int INVALID_ROW_ID = -1; public static OSQLite sqLite = null; private Context mContext; private OUser mUser; private String model_name = null; private List mColumns = new ArrayList<>(); private List mRelationColumns = new ArrayList<>(); private List mFunctionalColumns = new ArrayList<>(); private HashMap mDeclaredFields = new HashMap<>(); private OdooVersion mOdooVersion = null; private String default_name_column = "name"; public static OModelRegistry modelRegistry = new OModelRegistry(); private boolean hasMailChatter = false; // Relation record command public enum Command { Add(0), Update(1), Delete(2), Replace(6); int type; Command(int type) { this.type = type; } public int getValue() { return type; } } // Base Columns OColumn id = new OColumn("ID", OInteger.class).setDefaultValue(0); @Odoo.api.v8 @Odoo.api.v9alpha public OColumn create_date = new OColumn("Created On", ODateTime.class); @Odoo.api.v8 @Odoo.api.v9alpha public OColumn write_date = new OColumn("Last Updated On", ODateTime.class); // Local Base columns OColumn _id = new OColumn("_ID", OInteger.class).setAutoIncrement().setLocalColumn(); OColumn _write_date = new OColumn("Local Write Date", ODateTime.class).setLocalColumn(); OColumn _is_dirty = new OColumn("Dirty record", OBoolean.class).setDefaultValue(false).setLocalColumn(); OColumn _is_active = new OColumn("Active Record", OBoolean.class).setDefaultValue(true).setLocalColumn(); public OModel(Context context, String model_name, OUser user) { mContext = context; mUser = (user == null) ? OUser.current(context) : user; this.model_name = model_name; if (mUser != null) { mOdooVersion = new OdooVersion(); mOdooVersion.setVersion_number(mUser.getVersion_number()); mOdooVersion.setServer_serie(mUser.getVersion_serie()); if (sqLite == null) { sqLite = new OSQLite(mContext, mUser); } } } public SQLiteDatabase getReadableDatabase() { return sqLite.getReadableDatabase(); } public SQLiteDatabase getWritableDatabase() { return sqLite.getWritableDatabase(); } public String getDatabaseName() { return sqLite.getDatabaseName(); } public void close() { // Any operation when closing database } public void setDefaultNameColumn(String nameColumn) { default_name_column = nameColumn; } public String getDefaultNameColumn() { return default_name_column; } public OModel setModelName(String model_name) { this.model_name = model_name; return this; } public boolean hasMailChatter() { return hasMailChatter; } public void setHasMailChatter(boolean hasMailChatter) { this.hasMailChatter = hasMailChatter; } public OUser getUser() { return mUser; } public OdooVersion getOdooVersion() { return mOdooVersion; } public List getColumns() { if (mColumns.size() == 0) { prepareFields(); } return mColumns; } public List getColumns(Boolean local) { if (local != null) { List cols = new ArrayList<>(); for (OColumn column : getColumns()) if (local == column.isLocal()) cols.add(column); return cols; } else { return mColumns; } } public List getRelationColumns() { if (mColumns.size() <= 0) prepareFields(); return mRelationColumns; } public OColumn getColumn(String column_name) { if (mDeclaredFields.size() <= 0) prepareFields(); Field filed = mDeclaredFields.get(column_name); return getColumn(filed); } private OColumn getColumn(Field field) { OColumn column = null; if (field != null) { try { field.setAccessible(true); column = (OColumn) field.get(this); if (column.getName() == null) column.setName(field.getName()); Boolean validField = compatibleField(field); if (validField) { // Functional Method Method method = checkForFunctionalColumn(field); if (method != null) { column.setIsFunctionalColumn(true); column.setFunctionalMethod(method); column.setFunctionalStore(checkForFunctionalStore(field)); column.setFunctionalStoreDepends(getFunctionalDepends(field)); if (!column.canFunctionalStore()) { column.setLocalColumn(); } } // Onchange method for column Method onChangeMethod = checkForOnChangeMethod(field); if (onChangeMethod != null) { column.setOnChangeMethod(onChangeMethod); column.setOnChangeBGProcess(checkForOnChangeBGProcess(field)); } // domain filter on column column.setHasDomainFilterColumn(isDomainFilterColumn(field)); return column; } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } } return null; } private boolean isDomainFilterColumn(Field field) { Annotation annotation = field.getAnnotation(Odoo.hasDomainFilter.class); if (annotation != null) { Odoo.hasDomainFilter domainFilter = (Odoo.hasDomainFilter) annotation; return domainFilter.checkDomainRuntime(); } return false; } private Boolean checkForOnChangeBGProcess(Field field) { Annotation annotation = field.getAnnotation(Odoo.onChange.class); if (annotation != null) { Odoo.onChange onChange = (Odoo.onChange) annotation; return onChange.bg_process(); } return false; } private Method checkForOnChangeMethod(Field field) { Annotation annotation = field.getAnnotation(Odoo.onChange.class); if (annotation != null) { Odoo.onChange onChange = (Odoo.onChange) annotation; String method_name = onChange.method(); try { return getClass().getMethod(method_name, ODataRow.class); } catch (NoSuchMethodException e) { Log.e(TAG, "No Such Method: " + e.getMessage()); } } return null; } /** * Check for functional store. * * @param field the field * @return the boolean */ public Boolean checkForFunctionalStore(Field field) { Annotation annotation = field.getAnnotation(Odoo.Functional.class); if (annotation != null) { Odoo.Functional functional = (Odoo.Functional) annotation; return functional.store(); } return false; } /** * Gets the functional depends. * * @param field the field * @return the functional depends */ public String[] getFunctionalDepends(Field field) { Annotation annotation = field.getAnnotation(Odoo.Functional.class); if (annotation != null) { Odoo.Functional functional = (Odoo.Functional) annotation; return functional.depends(); } return null; } private Method checkForFunctionalColumn(Field field) { Annotation annotation = field.getAnnotation(Odoo.Functional.class); if (annotation != null) { Odoo.Functional functional = (Odoo.Functional) annotation; String method_name = functional.method(); try { if (functional.store()) return getClass().getMethod(method_name, OValues.class); else return getClass().getMethod(method_name, ODataRow.class); } catch (NoSuchMethodException e) { Log.e(TAG, "No Such Method: " + e.getMessage()); } } return null; } private boolean compatibleField(Field field) { if (mOdooVersion != null) { Annotation[] annotations = field.getDeclaredAnnotations(); if (annotations.length > 0) { int version = 0; for (Annotation annotation : annotations) { // Check for odoo api annotation Class type = annotation.annotationType(); if (type.getDeclaringClass().isAssignableFrom(Odoo.api.class)) { switch (mOdooVersion.getVersion_number()) { case 9: if (type.isAssignableFrom(Odoo.api.v9alpha.class)) { version++; } break; case 8: if (type.isAssignableFrom(Odoo.api.v8.class)) { version++; } break; case 7: if (type.isAssignableFrom(Odoo.api.v7.class)) { version++; } break; } } // Check for functional annotation if (type.isAssignableFrom(Odoo.Functional.class) || type.isAssignableFrom(Odoo.onChange.class) || type.isAssignableFrom(Odoo.hasDomainFilter.class)) { version++; } } return (version > 0) ? true : false; } return true; } return false; } private void prepareFields() { mColumns.clear(); mRelationColumns.clear(); List fields = new ArrayList<>(); fields.addAll(Arrays.asList(getClass().getSuperclass().getDeclaredFields())); fields.addAll(Arrays.asList(getClass().getDeclaredFields())); mDeclaredFields.clear(); for (Field field : fields) { if (field.getType().isAssignableFrom(OColumn.class)) { String name = field.getName(); try { OColumn column = getColumn(field); if (column != null) { name = column.getName(); if (column.getRelationType() != null) { mRelationColumns.add(column); } if (column.isFunctionalColumn()) { if (column.canFunctionalStore()) { mColumns.add(column); } mFunctionalColumns.add(column); } else { mColumns.add(column); } } } catch (Exception e) { e.printStackTrace(); } mDeclaredFields.put(name, field); } } } public List getFunctionalColumns() { if (mColumns.size() <= 0) prepareFields(); return mFunctionalColumns; } public String getModelName() { return model_name; } public List getManyToManyColumns(OModel relation_model) { List cols = new ArrayList(); _write_date.setName("_write_date"); cols.add(_write_date); _is_dirty.setName("_is_dirty"); cols.add(_is_dirty); _is_active.setName("_is_active"); cols.add(_is_active); OColumn base_id = new OColumn("Base Id", OInteger.class); base_id.setName(getTableName() + "_id"); cols.add(base_id); OColumn relation_id = new OColumn("Relation Id", OInteger.class); relation_id.setName(relation_model.getTableName() + "_id"); cols.add(relation_id); return cols; } public OModel createInstance(Class type) { try { Constructor constructor = type.getConstructor(Context.class, OUser.class); return (OModel) constructor.newInstance(mContext, mUser); } catch (Exception e) { e.printStackTrace(); } return null; } public String getTableName() { return getModelName().replaceAll("\\.", "_"); } public String toString() { return getModelName(); } public static OModel get(Context context, String model_name, String username) { OModel model = modelRegistry.getModel(model_name, username); OUser user = OdooAccountManager.getDetails(context, username); if (model == null) { try { OPreferenceManager pfManager = new OPreferenceManager(context); Class model_class = Class.forName(pfManager.getString(model_name, null)); if (model_class != null) { model = new OModel(context, model_name, user).createInstance(model_class); if (model != null) { modelRegistry.register(model); } } } catch (Exception e) { e.printStackTrace(); } } return model; } public String authority() { return BASE_AUTHORITY; } public Uri buildURI(String authority) { BASE_AUTHORITY = authority; String path = getModelName().toLowerCase(Locale.getDefault()); return BaseModelProvider.buildURI(BASE_AUTHORITY, path, mUser.getAndroidName()); } public Uri uri() { String path = getModelName().toLowerCase(Locale.getDefault()); return BaseModelProvider.buildURI(BASE_AUTHORITY, path, mUser.getAndroidName()); } public ODomain defaultDomain() { return new ODomain(); } private String[] updateProjection(String[] projection) { HashSet names = new HashSet<>(); String[] allProjection = projection; if (allProjection == null) { allProjection = projection(); } else { for (String col : projection) { OColumn column = getColumn(col); if (column.isFunctionalColumn() && column.canFunctionalStore()) { names.add(column.getName()); } } } names.addAll(Arrays.asList(allProjection)); names.addAll(Arrays.asList(new String[]{OColumn.ROW_ID, "id", "_write_date", "_is_dirty", "_is_active"})); return names.toArray(new String[names.size()]); } public String[] projection(Boolean onlyServerColumns) { List names = new ArrayList<>(); for (OColumn column : getColumns(false)) { if (column.getRelationType() == null || column.canFunctionalStore()) { names.add(column.getName()); } else if (column.getRelationType() == OColumn.RelationType.ManyToOne) { names.add(column.getName()); } } return names.toArray(new String[names.size()]); } public String[] projection() { List names = new ArrayList<>(); for (OColumn column : getColumns()) { if (column.getRelationType() == null || column.canFunctionalStore()) { names.add(column.getName()); } else if (column.getRelationType() == OColumn.RelationType.ManyToOne) { names.add(column.getName()); } } return names.toArray(new String[names.size()]); } // Sync default methods public boolean checkForCreateDate() { return true; } public boolean checkForWriteDate() { return true; } public boolean allowUpdateRecordOnServer() { return true; } public boolean allowCreateRecordOnServer() { return true; } public boolean allowDeleteRecordOnServer() { return true; } public boolean allowDeleteRecordInLocal() { return true; } // Database Operations public String getLabel(String column, String key) { OColumn col = getColumn(column); if (col.getType().isAssignableFrom(OSelection.class)) { return col.getSelectionMap().get(key); } return "false"; } public ODataRow browse(int row_id) { return browse(null, row_id); } public ODataRow browse(String[] projection, int row_id) { List rows = select(projection, OColumn.ROW_ID + " = ?", new String[]{row_id + ""}); if (rows.size() > 0) { return rows.get(0); } return null; } public ODataRow browse(String[] projection, String selection, String[] args) { List rows = select(updateProjection(projection), selection, args); if (rows.size() > 0) { return rows.get(0); } return null; } public List getServerIds() { List ids = new ArrayList<>(); for (ODataRow row : select(new String[]{"id"})) { if (row.getInt("id") != 0) { ids.add(row.getInt("id")); } } return ids; } public boolean isEmptyTable() { return (count(null, null) <= 0); } public String getLastSyncDateTime() { IrModel model = new IrModel(mContext, mUser); List records = model.select(null, "model = ?", new String[]{getModelName()}); if (records.size() > 0) { String date = records.get(0).getString("last_synced"); Date write_date = ODateUtils.createDateObject(date, ODateUtils.DEFAULT_FORMAT, true); Calendar cal = Calendar.getInstance(); cal.setTime(write_date); /* Fixed for Postgres SQL It stores milliseconds so comparing date wrong. */ cal.set(Calendar.SECOND, cal.get(Calendar.SECOND) + 2); write_date = cal.getTime(); return ODateUtils.getDate(write_date, ODateUtils.DEFAULT_FORMAT); } return null; } public List select() { return select(null, null, null, null); } public List select(String[] projection) { return select(projection, null, null, null); } public List select(String[] projection, String where, String[] args) { return select(projection, where, args, null); } public List select(String[] projection, String where, String[] args, String sortOrder) { Cursor cr = mContext.getContentResolver().query(uri(), updateProjection(projection), where, args, sortOrder); List rows = new ArrayList<>(); try { if (cr != null && cr.moveToFirst()) { do { ODataRow row = OCursorUtils.toDatarow(cr); for (OColumn column : getRelationColumns(projection)) { if (!row.getString(column.getName()).equals("false") || column.getRelationType() == OColumn.RelationType.OneToMany || column.getRelationType() == OColumn.RelationType.ManyToMany) { switch (column.getRelationType()) { case ManyToMany: OM2MRecord m2mRecords = new OM2MRecord(this, column, row.getInt(OColumn.ROW_ID)); row.put(column.getName(), m2mRecords); break; case ManyToOne: OM2ORecord m2ORecord = new OM2ORecord(this, column, row.getInt(column.getName())); row.put(column.getName(), m2ORecord); break; case OneToMany: OO2MRecord o2MRecord = new OO2MRecord(this, column, row.getInt(OColumn.ROW_ID)); row.put(column.getName(), o2MRecord); break; } } } for (OColumn column : getFunctionalColumns(projection)) { List depends = column.getFunctionalStoreDepends(); if (depends != null && depends.size() > 0) { ODataRow values = new ODataRow(); for (String depend : depends) { if (row.contains(depend)) { values.put(depend, row.get(depend)); } } if (values.size() == depends.size()) { Object value = getFunctionalMethodValue(column, values); row.put(column.getName(), value); } } } rows.add(row); } while (cr.moveToNext()); } } finally { cr.close(); } return rows; } public Object getFunctionalMethodValue(OColumn column, Object record) { if (column.isFunctionalColumn()) { Method method = column.getFunctionalMethod(); OModel model = this; try { return method.invoke(model, new Object[]{record}); } catch (Exception e) { e.printStackTrace(); } } return false; } public Object getOnChangeMethodValue(OColumn column, Object record) { Method method = column.getOnChangeMethod(); OModel model = this; try { return method.invoke(model, new Object[]{record}); } catch (Exception e) { e.printStackTrace(); } return false; } private List getFunctionalColumns(String[] projection) { List cols = new ArrayList<>(); if (projection != null) { for (String key : projection) { OColumn column = getColumn(key); if (column.isFunctionalColumn() && !column.canFunctionalStore()) { cols.add(column); } } } else { for (OColumn column : getFunctionalColumns()) { if (!column.canFunctionalStore()) cols.add(column); } } return cols; } private List getRelationColumns(String[] projection) { List cols = new ArrayList<>(); if (projection != null) { for (String key : projection) { OColumn column = getColumn(key); if (column.getRelationType() != null) { cols.add(column); } } } else { cols.addAll(getRelationColumns()); } return cols; } public int insertOrUpdate(int serverId, OValues values) { if (hasServerRecord(serverId)) { int row_id = selectRowId(serverId); update(row_id, values); return row_id; } else { return insert(values); } } public int insertOrUpdate(String selection, String[] args, OValues values) { int count = update(selection, args, values); if (count <= 0) { return insert(values); } else { return selectRowId(selection, args); } } public int selectRowId(String selection, String[] args) { int row_id = INVALID_ROW_ID; SQLiteDatabase db = getReadableDatabase(); Cursor cr = db.query(getTableName(), new String[]{OColumn.ROW_ID}, selection, args, null, null, null); try { if (cr.moveToFirst()) { row_id = cr.getInt(0); } } finally { cr.close(); } return row_id; } public int selectServerId(int row_id) { return browse(row_id).getInt("id"); } public int selectRowId(int server_id) { List rows = select(new String[]{OColumn.ROW_ID}, "id = ?", new String[]{server_id + ""}); if (rows.size() > 0) { return rows.get(0).getInt(OColumn.ROW_ID); } return INVALID_ROW_ID; } public int insert(OValues values) { Uri uri = mContext.getContentResolver().insert(uri(), values.toContentValues()); if (uri != null) { return Integer.parseInt(uri.getLastPathSegment()); } return INVALID_ROW_ID; } public boolean hasServerRecord(int server_id) { int count = count("id = ? ", new String[]{server_id + ""}); return (count > 0); } public boolean isServerRecordDirty(int server_id) { int count = count("id = ? and _is_dirty = ?", new String[]{server_id + "", "true"}); return (count > 0); } public boolean hasRecord(int row_id) { int count = count(OColumn.ROW_ID + " = ? ", new String[]{row_id + ""}); return (count > 0); } public int deleteRecords(List serverIds, boolean permanently) { String selection = "id IN (" + StringUtils.repeat("?, ", serverIds.size() - 1) + " ?)"; String[] args = OListUtils.toStringList(serverIds).toArray(new String[serverIds.size()]); if (permanently) { return delete(selection, args, true); } else { OValues values = new OValues(); values.put("_is_active", "false"); return update(selection, args, values); } } public int delete(String selection, String[] args) { return delete(selection, args, false); } public int delete(String selection, String[] args, boolean permanently) { int count = 0; if (permanently) { count = mContext.getContentResolver().delete(uri(), selection, args); } else { List records = select(new String[]{"_is_active"}, selection, args); for (ODataRow row : records) { if (row.getBoolean("_is_active")) { OValues values = new OValues(); values.put("_is_active", "false"); update(row.getInt(OColumn.ROW_ID), values); } count++; } } return count; } public boolean delete(int row_id) { return delete(row_id, false); } public boolean delete(int row_id, boolean permanently) { int count = 0; if (permanently) count = mContext.getContentResolver().delete(uri().withAppendedPath(uri(), row_id + ""), null, null); else { OValues values = new OValues(); values.put("_is_active", "false"); update(row_id, values); count++; } return (count > 0) ? true : false; } public int update(String selection, String[] args, OValues values) { return mContext.getContentResolver().update(uri(), values.toContentValues(), selection, args); } public boolean update(int row_id, OValues values) { int count = mContext.getContentResolver().update(uri().withAppendedPath(uri(), row_id + ""), values.toContentValues(), null, null); return (count > 0) ? true : false; } public List query(String query) { return query(query, null); } public List query(String query, String[] args) { List rows = new ArrayList<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cr = db.rawQuery(query, args); try { if (cr.moveToFirst()) { do { rows.add(OCursorUtils.toDatarow(cr)); } while (cr.moveToNext()); } } finally { cr.close(); } return rows; } public int count(String selection, String[] args) { int count = 0; SQLiteDatabase db = getReadableDatabase(); Cursor cr = db.query(getTableName(), new String[]{"count(*)"}, selection, args, null, null, null); try { cr.moveToFirst(); count = cr.getInt(0); } finally { cr.close(); } return count; } public void storeManyToManyRecord(String column_name, int row_id, List relationIds, Command command) throws InvalidObjectException { OColumn column = getColumn(column_name); if (column != null) { OModel rel_model = createInstance(column.getType()); String table = getTableName() + "_" + rel_model.getTableName() + "_rel"; String base_column = getTableName() + "_id"; String rel_column = rel_model.getTableName() + "_id"; SQLiteDatabase db = getWritableDatabase(); try { switch (command) { case Add: if (relationIds.size() > 0) { for (int id : relationIds) { ContentValues values = new ContentValues(); values.put(base_column, row_id); values.put(rel_column, id); values.put("_write_date", ODateUtils.getDate()); db.insert(table, null, values); } } break; case Update: break; case Delete: // Deleting records to relation model if (relationIds.size() > 0) { for (int id : relationIds) { db.delete(table, base_column + " = ? AND " + rel_column + " = ?", new String[]{row_id + "", id + ""}); } } break; case Replace: // Removing old entries db.delete(table, base_column + " = ?", new String[]{row_id + ""}); // Creating new entries storeManyToManyRecord(column_name, row_id, relationIds, Command.Add); break; } } finally { db.close(); rel_model.close(); } } else { throw new InvalidObjectException("Column [" + column_name + "] not found in " + getModelName() + " model."); } } public List selectManyToManyRecords(String[] projection, String column_name, int row_id) { OColumn column = getColumn(column_name); OModel rel_model = createInstance(column.getType()); String table = getTableName() + "_" + rel_model.getTableName() + "_rel"; String base_column = getTableName() + "_id"; String rel_column = rel_model.getTableName() + "_id"; // Getting relation table ids List ids = new ArrayList<>(); SQLiteDatabase db = getReadableDatabase(); Cursor cr = null; try { cr = db.query(table, new String[]{rel_column}, base_column + "=?", new String[]{row_id + ""}, null, null, null); if (cr.moveToFirst()) { do { ids.add(cr.getInt(0) + ""); } while (cr.moveToNext()); } } finally { if (cr != null) { cr.close(); } } List data = rel_model.select(projection, OColumn.ROW_ID + " IN (" + StringUtils.repeat(" ?, ", ids.size() - 1) + " ?)", ids.toArray(new String[ids.size()])); rel_model.close(); return data; } public ServerDataHelper getServerDataHelper() { return new ServerDataHelper(mContext, this, getUser()); } public String getName(int row_id) { ODataRow row = browse(row_id); if (row != null) { return row.getString("name"); } return "false"; } public void quickSyncRecords(ODomain domain) { OSyncAdapter syncAdapter = new OSyncAdapter(mContext, getClass(), null, true); syncAdapter.setModel(this); syncAdapter.setDomain(domain); syncAdapter.checkForWriteCreateDate(false); syncAdapter.onPerformSync(getUser().getAccount(), null, authority(), null, new SyncResult()); } public ODataRow quickCreateRecord(ODataRow record) { OSyncAdapter syncAdapter = new OSyncAdapter(mContext, getClass(), null, true); syncAdapter.setModel(this); ODomain domain = new ODomain(); domain.add("id", "=", record.getInt("id")); syncAdapter.setDomain(domain); syncAdapter.checkForWriteCreateDate(false); syncAdapter.onPerformSync(getUser().getAccount(), null, authority(), null, new SyncResult()); return browse(null, "id = ?", new String[]{record.getString("id")}); } public ODataRow countGroupBy(String column, String group_by, String having, String[] args) { String sql = "select count(*) as total, " + column; sql += " from " + getTableName() + " group by " + group_by + " having " + having; List data = query(sql, args); if (data.size() > 0) { return data.get(0); } else { ODataRow row = new ODataRow(); row.put("total", 0); return row; } } public boolean isInstalledOnServer(String module_name) { try { App app = (App) mContext.getApplicationContext(); IrModel model = new IrModel(mContext, getUser()); List modules = model.select(null, "name = ?", new String[]{module_name.trim()}); if (modules.size() > 0) { if (modules.get(0).getString("state").equals("installed")) { return true; } } if (app.inNetwork()) { odoo.Odoo odoo = app.getOdoo(getUser()); OdooFields fields = new OdooFields(new String[]{"state", "name"}); ODomain domain = new ODomain(); domain.add("name", "=", module_name); JSONArray result = odoo.search_read("ir.module.module", fields.get(), domain.get()) .getJSONArray("records"); if (result.length() > 0) { JSONObject record = result.getJSONObject(0); if (record.getString("state").equals("installed")) { OValues values = new OValues(); values.put("id", record.getInt("id")); values.put("name", record.getString("name")); values.put("state", record.getString("state")); model.insertOrUpdate(record.getInt("id"), values); return true; } } } } catch (Exception e) { e.printStackTrace(); } return false; } public String getDatabaseLocalPath() { return sqLite.databaseLocalPath(); } public void exportDB() { FileChannel source; FileChannel destination; String currentDBPath = getDatabaseLocalPath(); String backupDBPath = OStorageUtils.getDirectoryPath("file") + "/" + getDatabaseName(); File currentDB = new File(currentDBPath); File backupDB = new File(backupDBPath); try { source = new FileInputStream(currentDB).getChannel(); destination = new FileOutputStream(backupDB).getChannel(); destination.transferFrom(source, 0, source.size()); source.close(); destination.close(); String subject = "Database Export: " + getDatabaseName(); Uri uri = Uri.fromFile(backupDB); Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.setType("message/rfc822"); mContext.startActivity(intent); } catch (IOException e) { e.printStackTrace(); } } @Override public void onSyncStarted() { // Will be over ride by extending model } @Override public void onSyncFinished() { // Will be over ride by extending model } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OModelRegistry.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 20/1/15 3:49 PM */ package com.odoo.core.orm; import java.util.HashMap; public class OModelRegistry { public static final String TAG = OModelRegistry.class.getSimpleName(); private HashMap modelRegistry = new HashMap<>(); public void register(OModel model) { if (model != null && model.getModelName() != null) { modelRegistry.put(getKey(model), model); } } public OModel getModel(String model, String user) { if (modelRegistry.containsKey(model)) { String key = model + "_" + user; return modelRegistry.get(key); } return null; } public void unRegister(String model, String user) { if (modelRegistry.containsKey(model)) { String key = model + "_" + user; modelRegistry.remove(key); } } public void clearAll() { modelRegistry.clear(); } public int count() { return modelRegistry.size(); } private String getKey(OModel model) { return model.getModelName() + "_" + model.getUser().getAndroidName(); } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OO2MRecord.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:51 PM */ package com.odoo.core.orm; import com.odoo.core.orm.fields.OColumn; import java.util.ArrayList; import java.util.List; public class OO2MRecord { public static final String TAG = OO2MRecord.class.getSimpleName(); private OColumn mCol = null; private int mRecordId = 0; private OModel mDatabase = null; private OModel rel_model = null; private String mOrderBy = null; public OO2MRecord(OModel oModel, OColumn col, int id) { mDatabase = oModel; mCol = col; mRecordId = id; } public OO2MRecord setOrder(String order_by) { mOrderBy = order_by; return this; } public List getIds() { rel_model = mDatabase.createInstance(mCol.getType()); return getIds(rel_model); } public List getIds(OModel rel_model) { List ids = new ArrayList<>(); List records = rel_model.select(new String[]{OColumn.ROW_ID}, mCol.getRelatedColumn() + " = ?", new String[]{mRecordId + ""}); for (ODataRow record : records) { ids.add(record.getInt(OColumn.ROW_ID)); } return ids; } public List browseEach() { rel_model = mDatabase.createInstance(mCol.getType()); return rel_model.select(null, mCol.getRelatedColumn() + " = ?", new String[]{mRecordId + ""}, mOrderBy); } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OSQLHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 3:11 PM */ package com.odoo.core.orm; import android.content.Context; import android.util.Log; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OTypeHelper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class OSQLHelper { public static final String TAG = OSQLHelper.class.getSimpleName(); private Context mContext = null; private List mModels = new ArrayList<>(); private HashMap mSQLStatements = new HashMap<>(); public OSQLHelper(Context context) { mContext = context; } public List getModels() { return mModels; } public void createStatements(OModel model) { StringBuffer sql = null; if (!mModels.contains(model.getModelName())) { mModels.add(model.getModelName()); sql = new StringBuffer(); sql.append("CREATE TABLE IF NOT EXISTS "); sql.append(model.getTableName()); sql.append(" ("); List columns = model.getColumns(); sql.append(generateColumnStatement(model, columns)); sql.deleteCharAt(sql.lastIndexOf(",")); sql.append(")"); mSQLStatements.put(model.getTableName(), sql.toString()); } } private String generateColumnStatement(OModel model, List columns) { StringBuffer column_statement = new StringBuffer(); List finishedColumns = new ArrayList<>(); for (OColumn column : columns) { if (!finishedColumns.contains(column.getName())) { finishedColumns.add(column.getName()); String type = getType(column); if (type != null) { column_statement.append(column.getName()); column_statement.append(" " + type + " "); if (column.isAutoIncrement()) { column_statement.append(" PRIMARY KEY "); column_statement.append(" AUTOINCREMENT "); } Object default_value = column.getDefaultValue(); if (default_value != null) { column_statement.append(" DEFAULT "); if (default_value instanceof String) { column_statement.append("'" + default_value + "'"); } else { column_statement.append(default_value); } } column_statement.append(", "); } if (column.getRelationType() != null) { createRelationTable(model, column); } } } return column_statement.toString(); } private void createRelationTable(OModel base_model, OColumn column) { try { OModel rel_model = base_model.createInstance(column.getType()); switch (column.getRelationType()) { case ManyToOne: case OneToMany: createStatements(rel_model); break; case ManyToMany: manyToManyTable(column, base_model); // Creating master table for related column createStatements(base_model.createInstance(column.getType())); break; } } catch (Exception e) { e.printStackTrace(); } } private void manyToManyTable(OColumn column, OModel model) { StringBuffer sql = null; try { OModel relation_model = model.createInstance(column.getType()); List m2mCols = model.getManyToManyColumns(relation_model); String table_name = model.getTableName() + "_" + relation_model.getTableName() + "_rel"; if (!mModels.contains(table_name)) { sql = new StringBuffer(); mModels.add(table_name); String col_statement = generateColumnStatement(model, m2mCols); sql.append("CREATE TABLE IF NOT EXISTS "); sql.append(table_name); sql.append(" ("); sql.append(col_statement); sql.deleteCharAt(sql.lastIndexOf(",")); sql.append(")"); mSQLStatements.put(table_name, sql.toString()); Log.v(TAG, "Table Created : " + table_name); } } catch (Exception e) { e.printStackTrace(); } } private String getType(OColumn column) { try { if (column.getRelationType() == null) { if (column.getType().getSuperclass().isAssignableFrom(OTypeHelper.class)) { OTypeHelper type = (OTypeHelper) column.getType().newInstance(); type.setSize(column.getSize()); return type.getType(); } } else if (column.getRelationType() == OColumn.RelationType.ManyToOne) { return new OInteger().getType(); } } catch (Exception e) { Log.e(TAG, e.getMessage()); e.printStackTrace(); } return null; } public HashMap getStatements() { return mSQLStatements; } public void createDropStatements(OModel model) { StringBuffer sql = null; try { if (!mModels.contains(model.getTableName())) { mModels.add(model.getTableName()); sql = new StringBuffer(); sql.append("DROP TABLE IF EXISTS "); sql.append(model.getTableName()); mSQLStatements.put(model.getTableName(), sql.toString()); Log.v(TAG, "Table Dropped : " + model.getTableName()); for (OColumn col : model.getColumns()) { if (col.getRelationType() != null) { switch (col.getRelationType()) { case ManyToMany: OModel rel = model.createInstance(col.getType()); String table_name = model.getTableName() + "_" + rel.getTableName() + "_rel"; sql = new StringBuffer(); sql.append("DROP TABLE IF EXISTS "); sql.append(table_name); mModels.add(table_name); mSQLStatements.put(table_name, sql.toString()); Log.v(TAG, "Table Dropped : " + table_name); break; case ManyToOne: case OneToMany: createDropStatements(model.createInstance(col .getType())); break; } } } } } catch (Exception e) { e.printStackTrace(); } } public List getAllModels(List models) { mModels.clear(); List all_models = new ArrayList<>(); for (OModel model : models) { if (!mModels.contains(model.getModelName())) { mModels.add(model.getModelName()); all_models.add(model); // Checks for relation models List relModels = getRelationModels(model, model.getRelationColumns()); all_models.addAll(relModels); } } mModels.clear(); return all_models; } private List getRelationModels(OModel model, List cols) { List models = new ArrayList<>(); for (OColumn col : cols) { OModel rel_model = model.createInstance(col.getType()); if (rel_model != null && !mModels.contains(rel_model.getModelName())) { mModels.add(rel_model.getModelName()); models.add(rel_model); models.addAll(getRelationModels(rel_model, rel_model.getRelationColumns())); } } return models; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OSQLite.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:31 PM */ package com.odoo.core.orm; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Environment; import android.util.Log; import com.odoo.addons.crm.models.CRMCaseCateg; import com.odoo.addons.crm.models.CRMCaseStage; import com.odoo.App; import com.odoo.base.addons.BaseModels; import com.odoo.base.addons.mail.MailMessage; import com.odoo.base.addons.mail.MailMessageSubType; import com.odoo.config.Addons; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OUser; import com.odoo.core.support.addons.OAddon; import com.odoo.core.support.addons.fragment.IBaseFragment; import com.odoo.core.utils.OPreferenceManager; import java.util.ArrayList; import java.util.List; public class OSQLite extends SQLiteOpenHelper { public static final String TAG = OSQLite.class.getSimpleName(); public static final String KEY_MODEL_CLASS_REGISTER = "key_model_class_register"; public static final int DATABASE_VERSION = 2; private Context mContext; private OUser mUser = null; private Addons mAddons; private OPreferenceManager mPref; public OSQLite(Context context, OUser user) { super(context, (user != null) ? user.getDBName() : OUser.current(context).getDBName(), null , DATABASE_VERSION); mContext = context; mAddons = new Addons(); mUser = (user != null) ? user : OUser.current(context); mPref = new OPreferenceManager(mContext); synchronized (this) { if (!mPref.getBoolean(KEY_MODEL_CLASS_REGISTER, false)) { mPref.setBoolean(KEY_MODEL_CLASS_REGISTER, true); // Registering model class paths registerModelsClassPath(); } } } private List getModels() { List models = new ArrayList<>(); models.addAll(BaseModels.baseModels(mContext, mUser)); for (OAddon addon : mAddons.getAddons()) { IBaseFragment fragment = (IBaseFragment) addon.get(); try { Class model = fragment.database(); if (model != null) { OModel dbModel = (OModel) model.getConstructor(Context.class, OUser.class) .newInstance(mContext, mUser); models.add(dbModel); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } } return models; } @Override public void onCreate(SQLiteDatabase db) { Log.i(TAG, "creating database."); OSQLHelper sqlHelper = new OSQLHelper(mContext); // Creating tables for (OModel model : getModels()) { sqlHelper.createStatements(model); } for (String key : sqlHelper.getStatements().keySet()) { String query = sqlHelper.getStatements().get(key); db.execSQL(query); Log.i(TAG, "Table Created : " + key); } registerModels(sqlHelper.getModels()); } private void registerModels(List models) { OPreferenceManager mPref = new OPreferenceManager(mContext); if (mPref.putStringSet("models", models)) { Log.i(TAG, models.size() + " Models registered."); } else { Log.e(TAG, "Unable to register models"); } } private synchronized void registerModelsClassPath() { OSQLHelper sqlHelper = new OSQLHelper(mContext); List modelsClassPath = sqlHelper.getAllModels(getModels()); for (OModel model : modelsClassPath) { String key = model.getModelName(); String path = model.getClass().getName(); // Setting class path mPref.putString(key, path); } Log.i(TAG, modelsClassPath.size() + " models path registered."); } private List getColumns(String model_class, boolean server_columns) { List cols = new ArrayList(); try { OModel m = new OModel(mContext, null, null); Class cls = Class.forName(model_class); OModel model = m.createInstance(cls); for (OColumn col : model.getColumns(!server_columns)) { cols.add(col.getName()); } } catch (Exception e) { e.printStackTrace(); } return cols; } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.i(TAG, "upgrading database."); // Updates in mail messages. added subtype_id MailMessageSubType subType = new MailMessageSubType(mContext, mUser); MailMessage mailMessage = new MailMessage(mContext, mUser); OSQLHelper osqlHelper; // Dropping mail message db.execSQL("DROP TABLE IF EXISTS mail_message "); // re-creating mail message and sub type osqlHelper = new OSQLHelper(mContext); osqlHelper.createStatements(subType); osqlHelper.createStatements(mailMessage); for (String key : osqlHelper.getStatements().keySet()) { String query = osqlHelper.getStatements().get(key); db.execSQL(query); } registerModelsClassPath(); registerModels(osqlHelper.getModels()); // Updating crm case stage model name for saas-6 if (mUser.getVersion_serie().equals("8.saas~6")) { osqlHelper = new OSQLHelper(mContext); CRMCaseCateg caseCateg = new CRMCaseCateg(mContext, mUser); CRMCaseStage caseStage = new CRMCaseStage(mContext, mUser); osqlHelper.createStatements(caseCateg); osqlHelper.createStatements(caseStage); registerModels(osqlHelper.getModels()); db.execSQL("ALTER TABLE crm_case_stage RENAME TO crm_stage"); db.execSQL("ALTER TABLE crm_case_categ RENAME TO crm_lead_tag"); } // OSQLHelper sqlHelper = new OSQLHelper(mContext); // for (OModel model : getModels()) { // sqlHelper.createDropStatements(model); // } // for (String key : sqlHelper.getStatements().keySet()) { // String query = sqlHelper.getStatements().get(key); // db.execSQL(query); // Log.i(TAG, "Table dropped " + key); // } // onCreate(db); } public void dropDatabase() { if (mContext.deleteDatabase(getDatabaseName())) { Log.i(TAG, getDatabaseName() + " database dropped."); } } public String databaseLocalPath() { App app = (App) mContext.getApplicationContext(); return Environment.getDataDirectory().getPath() + "/data/" + app.getPackageName() + "/databases/" + getDatabaseName(); } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/OValues.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:49 PM */ package com.odoo.core.orm; import android.content.ContentValues; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class OValues { public static final String TAG = OValues.class.getSimpleName(); private HashMap _values = new HashMap(); public OValues() { _values.clear(); _values = new HashMap(); } public void put(String key, Object value) { _values.put(key, value); } public Object get(String key) { return _values.get(key); } public long getLong(String key) { if (_values.get(key).toString().equals("false")) { return -1; } return Long.parseLong(_values.get(key).toString()); } public Integer getInt(String key) { if (_values.get(key).toString().equals("false")) { return -1; } return Integer.parseInt(_values.get(key).toString()); } public String getString(String key) { return _values.get(key).toString(); } public Boolean getBoolean(String key) { return Boolean.parseBoolean(_values.get(key).toString()); } public boolean contains(String key) { return _values.containsKey(key); } public List keys() { List list = new ArrayList<>(); list.addAll(_values.keySet()); return list; } public void setAll(OValues values) { for (String key : values.keys()) _values.put(key, values.get(key)); } public int size() { return _values.size(); } @Override public String toString() { return _values.toString(); } public ODataRow toDataRow() { ODataRow row = new ODataRow(); row.addAll(_values); return row; } public ContentValues toContentValues() { ContentValues values = new ContentValues(); for (String key : _values.keySet()) { Object val = _values.get(key); val = (val == null) ? "false" : val; values.put(key, val.toString()); } return values; } public void addAll(HashMap data) { _values.putAll(data); } public static OValues from(ContentValues contentValues) { OValues values = new OValues(); for (String key : contentValues.keySet()) { values.put(key, contentValues.get(key)); } return values; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/ServerDataHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 6:23 PM */ package com.odoo.core.orm; import android.content.Context; import com.odoo.App; import com.odoo.core.service.OSyncAdapter; import com.odoo.core.support.OUser; import com.odoo.core.support.OdooFields; import com.odoo.core.utils.JSONUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import odoo.OArguments; import odoo.ODomain; import odoo.Odoo; public class ServerDataHelper { public static final String TAG = ServerDataHelper.class.getSimpleName(); private OModel mModel; private Context mContext; private Odoo mOdoo; private App mApp; public ServerDataHelper(Context context, OModel model, OUser user) { mContext = context; mModel = model; mApp = (App) mContext.getApplicationContext(); mOdoo = mApp.getOdoo(user); if (mOdoo == null) mOdoo = OSyncAdapter.createOdooInstance(mContext, model.getUser()); } public List nameSearch(String name, ODomain domain, int limit) { List items = new ArrayList<>(); try { if (mApp.inNetwork()) { JSONObject kwargs = new JSONObject(); kwargs.put("name", name); kwargs.put("args", domain.getArray()); kwargs.put("operator", "ilike"); JSONArray records = (JSONArray) callMethod("name_search", new OArguments(), null, kwargs); if (records.length() > 0) { for (int i = 0; i < records.length(); i++) { ODataRow row = new ODataRow(); JSONArray record = records.getJSONArray(i); row.put("id", record.get(0)); row.put(mModel.getDefaultNameColumn(), record.get(1)); items.add(row); } } } } catch (Exception e) { e.printStackTrace(); } return items; } public List searchRecords(OdooFields fields, ODomain domain, int limit) { List items = new ArrayList<>(); try { if (mApp.inNetwork()) { JSONObject result = mOdoo.search_read(mModel.getModelName(), fields.get(), domain.get(), 0, limit, null, null); JSONArray records = result.getJSONArray("records"); if (records.length() > 0) { for (int i = 0; i < records.length(); i++) { items.add(JSONUtils.toDataRow(records.getJSONObject(i))); } } } } catch (Exception e) { e.printStackTrace(); } return items; } public Odoo getOdoo() { return mOdoo; } public Object executeWorkFlow(int server_id, String signal) { try { return mOdoo.exec_workflow(mModel.getModelName(), server_id, signal); } catch (Exception e) { e.printStackTrace(); } return null; } public Object callMethod(String method, OArguments args) { return callMethod(method, args, null, null); } public Object callMethod(String method, OArguments args, JSONObject context) { return callMethod(mModel.getModelName(), method, args, context, null); } public Object callMethod(String method, OArguments args, JSONObject context, JSONObject kwargs) { return callMethod(mModel.getModelName(), method, args, context, kwargs); } public Object callMethod(String model, String method, OArguments args, JSONObject context, JSONObject kwargs) { try { if (kwargs == null) kwargs = new JSONObject(); if (context != null) { args.add(mOdoo.updateContext(context)); } JSONObject result = mOdoo.call_kw(model, method, args.getArray(), kwargs); if (result.has("result")) { return result.get("result"); } } catch (Exception e) { e.printStackTrace(); } return false; } public int createOnServer(JSONObject data) { try { JSONObject result = mOdoo.createNew(mModel.getModelName(), data); return result.getInt("result"); } catch (Exception e) { e.printStackTrace(); } return OModel.INVALID_ROW_ID; } public int updateOnServer(JSONObject data, Integer id) { try { if (mOdoo.updateValues(mModel.getModelName(), data, id)) { return mModel.selectRowId(id); } } catch (Exception e) { e.printStackTrace(); } return OModel.INVALID_ROW_ID; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/annotation/Odoo.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:32 PM */ package com.odoo.core.orm.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) public @interface Odoo { /** * The Interface Functional. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Functional { /** * Method. * * @return the string */ String method() default ""; /** * If true, system create column for this functional field and store * value (on create and update) given by this function * * @return true, if successful */ boolean store() default false; /** * Depends. * * @return the string[] */ String[] depends() default {}; } @Retention(RetentionPolicy.RUNTIME) public @interface api { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface v7 { String[] versions() default {}; String[] exclude() default {}; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface v8 { String[] versions() default {}; String[] exclude() default {}; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface v9alpha { String[] versions() default {}; String[] exclude() default {}; } } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface onChange { String method(); /** * Background process If true, method block executed in background * thread. default false * * @return */ boolean bg_process() default false; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) public @interface hasDomainFilter { boolean checkDomainRuntime() default true; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/OColumn.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:20 AM */ package com.odoo.core.orm.fields; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; public class OColumn { public static final String TAG = OColumn.class.getSimpleName(); public static final String ROW_ID = "_id"; private LinkedHashMap mSelectionMap = new LinkedHashMap<>(); public static enum RelationType { OneToMany, ManyToOne, ManyToMany } private String name, label, related_column; private Integer size; private Class type; private RelationType relationType; private Object defaultValue; private Boolean autoIncrement = false, required = false; private Boolean isLocalColumn = false; private LinkedHashMap columnDomains = new LinkedHashMap<>(); private Integer condition_operator_index = 0; private Integer recordSyncLimit = 0; //Annotation properties private Method mOnChangeMethod = null; private Boolean mOnChangeBGProcess = false; private Boolean mHasDomainFilterColumn = false; private Boolean is_functional_column = false; private Method functional_method = null; private Boolean use_annotation = true; private Boolean functional_store = false; private String[] functional_store_depends = null; public OColumn(String label, Class type) { this.label = label; this.type = type; } public OColumn(String label, Class type, RelationType relationType) { this(label, type); this.relationType = relationType; } public OColumn setName(String name) { this.name = name; return this; } public Integer getRecordSyncLimit() { return recordSyncLimit; } public OColumn setRecordSyncLimit(Integer recordSyncLimit) { this.recordSyncLimit = recordSyncLimit; return this; } public String getName() { return name; } public OColumn setLabel(String label) { this.label = label; return this; } public String getLabel() { return label; } public RelationType getRelationType() { return relationType; } public OColumn setRelatedColumn(String column) { related_column = column; return this; } public OColumn setSize(Integer size) { this.size = size; return this; } public Integer getSize() { return size; } public OColumn setDefaultValue(Object defValue) { defaultValue = defValue; return this; } public OColumn setRequired() { required = true; return this; } public boolean isRequired() { return required; } public OColumn setAutoIncrement() { autoIncrement = true; return this; } public OColumn setLocalColumn() { isLocalColumn = true; return this; } public OColumn setType(Class type) { this.type = type; return this; } public Class getType() { return type; } public Object getDefaultValue() { return defaultValue; } public Boolean isAutoIncrement() { return autoIncrement; } public Boolean isLocal() { return isLocalColumn; } public String getRelatedColumn() { return related_column; } public OColumn addDomain(String column_name, String operator, Object value) { columnDomains.put(column_name, new ColumnDomain(column_name, operator, value)); return this; } public OColumn addDomain(String condition_operator) { columnDomains.put("condition_operator_" + (condition_operator_index++) + condition_operator, new ColumnDomain(condition_operator)); return this; } public LinkedHashMap getDomains() { if (hasDomainFilterColumn()) { return new LinkedHashMap<>(); } return columnDomains; } public boolean hasDomainFilterColumn() { return mHasDomainFilterColumn; } public OColumn setHasDomainFilterColumn(Boolean domainFilterColumn) { mHasDomainFilterColumn = domainFilterColumn; return this; } public boolean hasOnChange() { return (mOnChangeMethod != null); } public Method getOnChangeMethod() { return mOnChangeMethod; } public void setOnChangeMethod(Method method) { mOnChangeMethod = method; } public Boolean isOnChangeBGProcess() { return mOnChangeBGProcess; } public void setOnChangeBGProcess(Boolean process) { mOnChangeBGProcess = process; } public void cleanDomains() { columnDomains.clear(); } public LinkedHashMap getFilterDomains() { return columnDomains; } /** * Clone domain. * * @param domains the domains * @return the o column */ public OColumn cloneDomain(LinkedHashMap domains) { columnDomains.putAll(domains); return this; } /** * Sets the functional store. * * @param store the new functional store */ public void setFunctionalStore(Boolean store) { functional_store = store; } /** * Gets the functional store. * * @return the functional store */ public Boolean canFunctionalStore() { return functional_store; } /** * Sets the functional store depends. * * @param depends the depends * @return the o column */ public OColumn setFunctionalStoreDepends(String[] depends) { functional_store_depends = depends; return this; } public Boolean isFunctionalColumn() { return is_functional_column; } public OColumn setIsFunctionalColumn(Boolean is_functional_column) { this.is_functional_column = is_functional_column; return this; } public Method getFunctionalMethod() { return functional_method; } public OColumn setFunctionalMethod(Method functional_method) { this.functional_method = functional_method; return this; } /** * Gets the functional store depends. * * @return the functional store depends */ public List getFunctionalStoreDepends() { if (functional_store_depends != null) return Arrays.asList(functional_store_depends); return new ArrayList(); } public HashMap getSelectionMap() { return mSelectionMap; } public OColumn addSelection(String key, String value) { mSelectionMap.put(key, value); return this; } @Override public String toString() { return "OColumn{" + "name='" + name + '\'' + ", label='" + label + '\'' + ", related_column='" + related_column + '\'' + ", size=" + size + ", type=" + type + ", relationType=" + relationType + ", defaultValue=" + defaultValue + ", autoIncrement=" + autoIncrement + ", required=" + required + ", isLocalColumn=" + isLocalColumn + ", columnDomains=" + columnDomains + ", condition_operator_index=" + condition_operator_index + ", recordSyncLimit=" + recordSyncLimit + ", mOnChangeMethod=" + mOnChangeMethod + ", mOnChangeBGProcess=" + mOnChangeBGProcess + ", mHasDomainFilterColumn=" + mHasDomainFilterColumn + ", is_functional_column=" + is_functional_column + ", functional_method=" + functional_method + ", use_annotation=" + use_annotation + ", functional_store=" + functional_store + ", functional_store_depends=" + Arrays.toString(functional_store_depends) + '}'; } public class ColumnDomain { private String column = null; private String operator = null; private Object value = null; private String conditional_operator = null; public ColumnDomain(String conditional_operator) { this.conditional_operator = conditional_operator; } public ColumnDomain(String column, String operator, Object value) { this.column = column; this.operator = operator; this.value = value; } public String getColumn() { return column; } public void setColumn(String column) { this.column = column; } public String getOperator() { return operator; } public void setOperator(String operator) { this.operator = operator; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } public String getConditionalOperator() { return conditional_operator; } public void setConditionalOperator(String conditional_operator) { this.conditional_operator = conditional_operator; } @Override public String toString() { StringBuffer domain = new StringBuffer(); domain.append("["); if (this.conditional_operator == null) { domain.append(this.column); domain.append(", "); domain.append(this.operator); domain.append(", "); domain.append(this.value); } else { domain.append(this.conditional_operator); } domain.append("]"); return domain.toString(); } } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OBlob.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OBlob extends OTypeHelper { public static final String TAG = OBlob.class.getSimpleName(); @Override public String getFieldType() { return "BLOB"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OBoolean.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OBoolean extends OTypeHelper { public static final String TAG = OBoolean.class.getSimpleName(); @Override public String getFieldType() { return "VARCHAR"; } @Override public Integer getFieldSize() { return 10; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/ODate.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; import com.odoo.core.utils.ODateUtils; public class ODate extends OTypeHelper { public static final String TAG = ODate.class.getSimpleName(); @Override public String getFieldType() { return "VARCHAR"; } @Override public String getDataFormat() { return ODateUtils.DEFAULT_DATE_FORMAT; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/ODateTime.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; import com.odoo.core.utils.ODateUtils; public class ODateTime extends OTypeHelper { public static final String TAG = ODateTime.class.getSimpleName(); @Override public String getFieldType() { return "VARCHAR"; } @Override public String getDataFormat() { return ODateUtils.DEFAULT_FORMAT; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OFloat.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OFloat extends OTypeHelper { public static final String TAG = OFloat.class.getSimpleName(); @Override public String getFieldType() { return "REAL"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OHtml.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OHtml extends OTypeHelper { public static final String TAG = OHtml.class.getSimpleName(); @Override public String getFieldType() { return "TEXT"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OInteger.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OInteger extends OTypeHelper { public static final String TAG = OInteger.class.getSimpleName(); @Override public String getFieldType() { return "INTEGER"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OSelection.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:30 AM */ package com.odoo.core.orm.fields.types; public class OSelection extends OTypeHelper { public static final String TAG = OSelection.class.getSimpleName(); @Override public String getFieldType() { return "VARCHAR"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OText.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:29 AM */ package com.odoo.core.orm.fields.types; public class OText extends OTypeHelper { public static final String TAG = OText.class.getSimpleName(); @Override public String getFieldType() { return "TEXT"; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OTimestamp.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:30 AM */ package com.odoo.core.orm.fields.types; public class OTimestamp extends ODateTime { public static final String TAG = OTimestamp.class.getSimpleName(); } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OTypeHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:30 AM */ package com.odoo.core.orm.fields.types; public abstract class OTypeHelper { public Integer field_size = 0; public final boolean equals(String type) { return getFieldType().equals(type); } public final String getType() { String type = getFieldType(); if (field_size > 0) { type += "(" + field_size + ")"; } return type; } public void setSize(Integer size) { if (size != null) field_size = size; } public abstract String getFieldType(); public Integer getFieldSize() { return field_size; } public String getDataFormat() { return null; } @Override public String toString() { return "OTypeHelper{" + "field_type='" + getFieldType() + '\'' + ", field_size=" + field_size + '}'; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/fields/types/OVarchar.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:30 AM */ package com.odoo.core.orm.fields.types; public class OVarchar extends OTypeHelper { public static final String TAG = OVarchar.class.getSimpleName(); @Override public String getFieldType() { return "VARCHAR"; } @Override public Integer getFieldSize() { return 64; } } ================================================ FILE: app/src/main/java/com/odoo/core/orm/provider/BaseModelProvider.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 6:54 PM */ package com.odoo.core.orm.provider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OUser; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.ODateUtils; import java.io.InvalidObjectException; import java.util.Arrays; import java.util.HashSet; public class BaseModelProvider extends ContentProvider { public static final String TAG = BaseModelProvider.class.getSimpleName(); public final static String KEY_MODEL = "key_model"; public final static String KEY_USERNAME = "key_username"; private final int COLLECTION = 1; private final int SINGLE_ROW = 2; protected OModel mModel = null; public UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); public static Uri buildURI(String authority, String model, String username) { Uri.Builder uriBuilder = new Uri.Builder(); uriBuilder.authority(authority); uriBuilder.appendPath(model); uriBuilder.appendQueryParameter(KEY_MODEL, model); uriBuilder.appendQueryParameter(KEY_USERNAME, username); uriBuilder.scheme("content"); return uriBuilder.build(); } @Override public boolean onCreate() { return true; } public String authority() { return null; } public OUser getUser(Uri uri) { String username = uri.getQueryParameter(KEY_USERNAME); return OdooAccountManager.getDetails(getContext(), username); } public void setModel(Uri uri) { String path = uri.getQueryParameter(KEY_MODEL); String username = uri.getQueryParameter(KEY_USERNAME); mModel = OModel.get(getContext(), path, username); assert mModel != null; } private void setMatcher(Uri uri) { String authority = (authority() != null) ? authority() : uri.getAuthority(); matcher.addURI(authority, mModel.getModelName(), COLLECTION); matcher.addURI(authority, mModel.getModelName() + "/#", SINGLE_ROW); } @Override public Cursor query(Uri uri, String[] base_projection, String selection, String[] selectionArgs, String sortOrder) { setModel(uri); setMatcher(uri); if (mModel == null) return null; String[] projection = removeRelationColumns(base_projection); int match = matcher.match(uri); SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(mModel.getTableName()); // If selection not null and does not contain _is_active if ((selection != null && !selection.contains("_is_active")) || selection == null) { builder.appendWhere("_is_active = 'true'"); } Cursor cr = null; switch (match) { case COLLECTION: cr = builder.query(mModel.getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder); break; case SINGLE_ROW: int row_id = Integer.parseInt(uri.getLastPathSegment()); cr = builder.query(mModel.getReadableDatabase(), projection, OColumn.ROW_ID + " = ? ", new String[]{row_id + ""}, null, null, null); case UriMatcher.NO_MATCH: break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } Context ctx = getContext(); assert ctx != null; if (cr != null) cr.setNotificationUri(ctx.getContentResolver(), uri); return cr; } private String[] removeRelationColumns(String[] projection) { HashSet columns = new HashSet<>(); if (projection != null && projection.length > 0 && mModel != null) { for (String key : projection) { OColumn column = mModel.getColumn(key); if (column != null && column.getRelationType() == null) { columns.add(key); } else if (column != null && column.getRelationType() == OColumn.RelationType.ManyToOne) { columns.add(key); } } columns.addAll(Arrays.asList(new String[]{OColumn.ROW_ID, "id", "_is_active", "_write_date"})); return columns.toArray(new String[columns.size()]); } return null; } @Override public String getType(Uri uri) { return uri.toString(); } @Override public Uri insert(Uri uri, ContentValues all_values) { setModel(uri); setMatcher(uri); ContentValues[] values = generateValues(all_values); ContentValues value_to_insert = values[0]; value_to_insert.put("_write_date", ODateUtils.getUTCDate()); if (!value_to_insert.containsKey("_is_active")) value_to_insert.put("_is_active", "true"); if (!value_to_insert.containsKey("_is_dirty")) value_to_insert.put("_is_dirty", "false"); int match = matcher.match(uri); switch (match) { case COLLECTION: SQLiteDatabase db = mModel.getWritableDatabase(); long new_id = 0; new_id = db.insert(mModel.getTableName(), null, value_to_insert); // Updating relation columns for record if (values[1].size() > 0) { storeUpdateRelationRecords(values[1], OColumn.ROW_ID + " = ?", new String[]{new_id + ""}); } return uri.withAppendedPath(uri, new_id + ""); case SINGLE_ROW: throw new UnsupportedOperationException( "Insert not supported on URI: " + uri); case UriMatcher.NO_MATCH: break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } notifyDataChange(uri); return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; setModel(uri); setMatcher(uri); int match = matcher.match(uri); switch (match) { case COLLECTION: SQLiteDatabase db = mModel.getWritableDatabase(); count = db.delete(mModel.getTableName(), selection, selectionArgs); break; case SINGLE_ROW: db = mModel.getWritableDatabase(); String row_id = uri.getLastPathSegment(); count = db.delete(mModel.getTableName(), OColumn.ROW_ID + " = ?", new String[]{row_id}); break; case UriMatcher.NO_MATCH: break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } notifyDataChange(uri); return count; } @Override public int update(Uri uri, ContentValues all_values, String selection, String[] selectionArgs) { setModel(uri); setMatcher(uri); ContentValues[] values = generateValues(all_values); ContentValues value_to_update = values[0]; if (!value_to_update.containsKey("_write_date")) { value_to_update.put("_write_date", ODateUtils.getUTCDate()); } if (!value_to_update.containsKey("_is_dirty")) { value_to_update.put("_is_dirty", "true"); } int count = 0; int match = matcher.match(uri); switch (match) { case COLLECTION: SQLiteDatabase db = mModel.getWritableDatabase(); count = db.update(mModel.getTableName(), value_to_update, selection, selectionArgs); // Updating relation columns if (values[1].size() > 0) { storeUpdateRelationRecords(values[1], selection, selectionArgs); } break; case SINGLE_ROW: String row_id = uri.getLastPathSegment(); db = mModel.getWritableDatabase(); count = db.update(mModel.getTableName(), value_to_update, OColumn.ROW_ID + " = ?", new String[]{row_id}); // Updating relation columns for record if (values[1].size() > 0) { storeUpdateRelationRecords(values[1], OColumn.ROW_ID + " = ?", new String[]{row_id}); } break; case UriMatcher.NO_MATCH: break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } notifyDataChange(uri); return count; } private void storeUpdateRelationRecords(ContentValues values, String selection, String[] args) { int row_id = mModel.selectRowId(selection, args); for (String key : values.keySet()) { try { mModel.storeManyToManyRecord(key, row_id, JSONUtils.toList(values.getAsString(key)), OModel.Command.Replace); } catch (InvalidObjectException e) { e.printStackTrace(); } } } private ContentValues[] generateValues(ContentValues values) { OValues data_value = new OValues(); OValues rel_value = new OValues(); for (String key : values.keySet()) { OColumn column = mModel.getColumn(key); if (column != null) { if (column.getRelationType() == null) { data_value.put(key, values.get(key)); } else { if (column.getRelationType() == OColumn.RelationType.ManyToOne) { data_value.put(key, values.get(key)); } else { rel_value.put(key, values.get(key).toString()); } } } } return new ContentValues[]{data_value.toContentValues(), rel_value.toContentValues()}; } private void notifyDataChange(Uri uri) { // Send broadcast to registered ContentObservers, to refresh UI. Context ctx = getContext(); assert ctx != null; ctx.getContentResolver().notifyChange(uri, null); } } ================================================ FILE: app/src/main/java/com/odoo/core/service/ISyncFinishListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/1/15 3:16 PM */ package com.odoo.core.service; import android.content.SyncResult; import com.odoo.core.support.OUser; public interface ISyncFinishListener { public OSyncAdapter performNextSync(OUser user, SyncResult syncResult); } ================================================ FILE: app/src/main/java/com/odoo/core/service/ISyncServiceListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 10/4/15 3:49 PM */ package com.odoo.core.service; public interface ISyncServiceListener { public void onSyncStarted(); public void onSyncFinished(); } ================================================ FILE: app/src/main/java/com/odoo/core/service/OSyncAdapter.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/1/15 3:17 PM */ package com.odoo.core.service; import android.accounts.Account; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.Context; import android.content.SyncResult; import android.os.Bundle; import android.util.Log; import com.odoo.App; import com.odoo.R; import com.odoo.base.addons.ir.IrModel; import com.odoo.base.addons.res.ResCompany; import com.odoo.core.account.OdooAccountQuickManage; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OUser; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import odoo.ODomain; import odoo.Odoo; import odoo.OdooInstance; import odoo.OdooSessionExpiredException; public class OSyncAdapter extends AbstractThreadedSyncAdapter { public static final String TAG = OSyncAdapter.class.getSimpleName(); public static final Integer REQUEST_SIGN_IN_ERROR = 11244; public static final String KEY_AUTH_ERROR = "key_authentication_error"; private Context mContext; private Class mModelClass; private OModel mModel; private OSyncService mService; private OUser mUser; private Boolean checkForWriteCreateDate = true; private Integer mSyncDataLimit = 0; private HashMap mDomain = new HashMap<>(); private OPreferenceManager preferenceManager; private Odoo mOdoo; private HashMap mSyncFinishListeners = new HashMap<>(); private App app = null; public OSyncAdapter(Context context, Class model, OSyncService service, boolean autoInitialize) { super(context, autoInitialize); init(context, model, service); } public OSyncAdapter(Context context, Class model, OSyncService service, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); init(context, model, service); } private void init(Context context, Class model, OSyncService service) { mContext = context; mModelClass = model; mService = service; preferenceManager = new OPreferenceManager(mContext); app = (App) context.getApplicationContext(); } public OSyncAdapter setDomain(ODomain domain) { mDomain.put(mModel.getModelName(), domain); return this; } public OSyncAdapter checkForWriteCreateDate(Boolean check) { checkForWriteCreateDate = check; return this; } public OSyncAdapter syncDataLimit(Integer dataLimit) { mSyncDataLimit = dataLimit; return this; } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { // Creating model Object mModel = new OModel(mContext, null, OdooAccountManager.getDetails(mContext, account.name)) .createInstance(mModelClass); mUser = mModel.getUser(); // Creating Odoo instance mOdoo = createOdooInstance(mContext, mUser); Log.i(TAG, "User : " + mModel.getUser().getAndroidName()); Log.i(TAG, "Model : " + mModel.getModelName()); Log.i(TAG, "Database : " + mModel.getDatabaseName()); Log.i(TAG, "Odoo Version: " + mUser.getVersion_number()); // Calling service callback if (mService != null) mService.performDataSync(this, extras, mUser); //Creating domain ODomain domain = (mDomain.containsKey(mModel.getModelName())) ? mDomain.get(mModel.getModelName()) : null; // Ready for sync data from server syncData(mModel, mUser, domain, syncResult, true, true); } private void syncData(OModel model, OUser user, ODomain domain_filter, SyncResult result, Boolean checkForDataLimit, Boolean createRelationRecords) { Log.v(TAG, "Sync for (" + model.getModelName() + ") Started at " + ODateUtils.getDate()); model.onSyncStarted(); try { ODomain domain = new ODomain(); domain.append(model.defaultDomain()); if (domain_filter != null) { domain.append(domain_filter); } if (checkForWriteCreateDate) { List serverIds = model.getServerIds(); // Model Create date domain filters if (model.checkForCreateDate() && checkForDataLimit) { if (serverIds.size() > 0) { if (model.checkForWriteDate() && !model.isEmptyTable()) { domain.add("|"); } if (model.checkForWriteDate() && !model.isEmptyTable() && createRelationRecords && model.getLastSyncDateTime() != null) domain.add("&"); } int data_limit = preferenceManager.getInt("sync_data_limit", 60); domain.add("create_date", ">=", ODateUtils.getDateBefore(data_limit)); if (serverIds.size() > 0) { domain.add("id", "not in", new JSONArray(serverIds.toString())); } } // Model write date domain filters if (model.checkForWriteDate() && !model.isEmptyTable() && createRelationRecords) { String last_sync_date = model.getLastSyncDateTime(); if (last_sync_date != null) { domain.add("write_date", ">", last_sync_date); } } } // Getting data JSONObject response = mOdoo.search_read(model.getModelName(), getFields(model), domain.get(), 0, mSyncDataLimit, "create_date", "DESC"); OSyncDataUtils dataUtils = new OSyncDataUtils(mContext, mOdoo, model, user, response, result, createRelationRecords); // Updating records on server if local are latest updated. // if model allowed update record to server if (model.allowUpdateRecordOnServer()) { dataUtils.updateRecordsOnServer(this); } // Creating or updating relation records handleRelationRecords(user, dataUtils.getRelationRecordsHashMap(), result); // If model allowed to create record on server if (model.allowCreateRecordOnServer()) { createRecordsOnServer(model); } // If model allowed to delete record on server if (model.allowDeleteRecordOnServer()) { removeRecordOnServer(model); } // If model allowed to delete server removed record from local database if (model.allowDeleteRecordInLocal()) { removeNonExistRecordFromLocal(model); } Log.v(TAG, "Sync for (" + model.getModelName() + ") finished at " + ODateUtils.getDate()); if (createRelationRecords) { IrModel irModel = new IrModel(mContext, user); irModel.setLastSyncDateTimeToNow(model); } model.onSyncFinished(); } catch (OdooSessionExpiredException odooSession) { app.setOdoo(null, user); if (user.isOAauthLogin()) { // FIXME: Saas not working with multi login. Facing issue of session expired. } else { showSignInErrorNotification(user); } } catch (Exception e) { e.printStackTrace(); } // Performing next sync if any in service if (mSyncFinishListeners.containsKey(model.getModelName())) { OSyncAdapter adapter = mSyncFinishListeners.get(model.getModelName()) .performNextSync(user, result); mSyncFinishListeners.remove(model.getModelName()); if (adapter != null) { SyncResult syncResult = new SyncResult(); OModel syncModel = model.createInstance(adapter.getModelClass()); ContentProviderClient contentProviderClient = mContext.getContentResolver().acquireContentProviderClient(syncModel.authority()); adapter.onPerformSync(user.getAccount(), null, syncModel.authority(), contentProviderClient, syncResult); } } model.close(); } private void showSignInErrorNotification(OUser user) { ONotificationBuilder builder = new ONotificationBuilder(mContext, REQUEST_SIGN_IN_ERROR); builder.setTitle("Odoo authentication problem"); builder.setBigText("May be you have changed your account " + "password or your account does not exists"); builder.setIcon(R.drawable.ic_action_alert_warning); builder.setText(user.getAndroidName()); builder.allowVibrate(true); builder.withRingTone(false); builder.setOngoing(true); builder.withLargeIcon(false); builder.setColor(OResource.color(mContext, R.color.android_orange_dark)); Bundle extra = user.getAsBundle(); // Actions ONotificationBuilder.NotificationAction actionReset = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_refresh, "Reset", 110, "reset_password", OdooAccountQuickManage.class, extra ); ONotificationBuilder.NotificationAction deleteAccount = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_navigation_close, "Remove", 111, "remove_account", OdooAccountQuickManage.class, extra ); builder.addAction(actionReset); builder.addAction(deleteAccount); builder.build().show(); } private void handleRelationRecords(OUser user, HashMap relationRecords, SyncResult result) { for (String key : relationRecords.keySet()) { OSyncDataUtils.SyncRelationRecords record = relationRecords.get(key); OModel model = record.getBaseModel(); OModel rel_model = model.createInstance(record.getRelationModel()); model.close(); ODomain domain = new ODomain(); domain.add("id", "in", record.getUniqueIds()); syncData(rel_model, user, domain, result, false, false); // Updating manyToOne record with their relation record row_id switch (record.getRelationType()) { case ManyToOne: // Nothing to do. Already added link with record relation break; case OneToMany: // Update related_column with base id's row_id for each of record ids String related_column = record.getRelatedColumn(); for (Integer id : record.getUniqueIds()) { OValues values = new OValues(); ODataRow rec = rel_model.browse(rel_model.selectRowId(id)); values.put(related_column, rec.getInt(related_column)); values.put("_is_dirty", "false"); rel_model.update(rel_model.selectRowId(id), values); } break; case ManyToMany: // Nothing to do. Already added relation records links break; } rel_model.close(); } } public static Odoo createOdooInstance(Context context, OUser user) { try { App app = (App) context.getApplicationContext(); Odoo odoo = app.getOdoo(user); if (odoo == null) { if (user.isOAauthLogin()) { odoo = new Odoo(context, user.getInstanceUrl(), user.isAllowSelfSignedSSL()); OdooInstance instance = new OdooInstance(); instance.setInstanceUrl(user.getInstanceUrl()); instance.setDatabaseName(user.getInstanceDatabase()); instance.setClientId(user.getClientId()); odoo.oauth_authenticate(instance, user.getUsername(), user.getPassword()); } else { odoo = new Odoo(context, user.getHost(), user.isAllowSelfSignedSSL()); odoo.authenticate(user.getUsername(), user.getPassword(), user.getDatabase()); } app.setOdoo(odoo, user); ResCompany company = new ResCompany(context, user); if (company.count("id = ? ", new String[]{user.getCompany_id()}) <= 0) { ODataRow company_details = new ODataRow(); company_details.put("id", user.getCompany_id()); company.quickCreateRecord(company_details); } } return odoo; } catch (Exception e) { e.printStackTrace(); } return null; } private JSONObject getFields(OModel model) { JSONObject fields = new JSONObject(); try { for (OColumn column : model.getColumns(false)) { fields.accumulate("fields", column.getName()); } } catch (Exception e) { e.printStackTrace(); } return fields; } /** * Creates locally created record on server (id with zero) * * @param model model object */ private void createRecordsOnServer(OModel model) { List records = model.select(null, "(id = ? or id = ?)", new String[]{"0", "false"}); int counter = 0; for (ODataRow record : records) { if (validateRelationRecords(model, record)) { /* Need to check server id for record. It is possible that record created on server by validating main record. */ if (model.selectServerId(record.getInt(OColumn.ROW_ID)) == 0) { int id = createOnServer(model, JSONUtils.createJSONValues(model, record)); if (id != OModel.INVALID_ROW_ID) { OValues values = new OValues(); values.put("id", id); values.put("_is_dirty", "false"); values.put("_write_date", ODateUtils.getUTCDate()); model.update(record.getInt(OColumn.ROW_ID), values); counter++; } else { Log.e(TAG, "Unable to create record on server."); } } } } if (counter == records.size()) { Log.i(TAG, counter + " records created on server."); } } /** * Validate relation record for the record. And if relation record not created on server. * It will be created on server before syncing original record * * @param model * @param row * @return updatedRow */ public boolean validateRelationRecords(OModel model, ODataRow row) { Log.d(TAG, "Validating relation records for record"); // Check for relation local record for (OColumn column : model.getRelationColumns()) { OModel relModel = model.createInstance(column.getType()); switch (column.getRelationType()) { case ManyToOne: if (!row.getString(column.getName()).equals("false")) { ODataRow m2oRec = row.getM2ORecord(column.getName()).browse(); if (m2oRec.getInt("id") == 0) { int new_id = relModel.getServerDataHelper().createOnServer( JSONUtils.createJSONValues(relModel, m2oRec)); updateRecordServerId(relModel, m2oRec.getInt(OColumn.ROW_ID), new_id); } } break; case ManyToMany: List m2mRecs = row.getM2MRecord(column.getName()).browseEach(); if (!m2mRecs.isEmpty()) { for (ODataRow m2mRec : m2mRecs) { if (m2mRec.getInt("id") == 0) { int new_id = relModel.getServerDataHelper().createOnServer( JSONUtils.createJSONValues(relModel, m2mRec)); updateRecordServerId(relModel, m2mRec.getInt(OColumn.ROW_ID), new_id); } } } break; case OneToMany: List o2mRecs = row.getM2MRecord(column.getName()).browseEach(); if (!o2mRecs.isEmpty()) { for (ODataRow o2mRec : o2mRecs) { if (o2mRec.getInt("id") == 0) { int new_id = relModel.getServerDataHelper().createOnServer( JSONUtils.createJSONValues(relModel, o2mRec)); updateRecordServerId(relModel, o2mRec.getInt(OColumn.ROW_ID), new_id); } } } break; } } return true; } /** * Updating local record with server id * * @param model * @param row_id * @param server_id */ private void updateRecordServerId(OModel model, int row_id, int server_id) { OValues values = new OValues(); values.put("id", server_id); values.put("_is_dirty", "false"); model.update(row_id, values); } private int createOnServer(OModel model, JSONObject values) { int id = OModel.INVALID_ROW_ID; try { if (values != null) { JSONObject response = mOdoo.createNew(model.getModelName(), values); id = response.getInt("result"); } } catch (Exception e) { e.printStackTrace(); } return id; } /** * Removes record on server if local record is not active * * @param model */ private void removeRecordOnServer(OModel model) { List records = model.select(new String[]{}, "id != ? and _is_active = ?", new String[]{"0", "false"}); List serverIds = new ArrayList<>(); for (ODataRow record : records) { serverIds.add(record.getInt("id")); } if (serverIds.size() > 0) { if (removeRecordsFromServer(model, serverIds)) { int counter = model.deleteRecords(serverIds, true); Log.i(TAG, counter + " records removed from server and local database"); } else { Log.e(TAG, "Unable to remove records from server"); } } } private boolean removeRecordsFromServer(OModel model, List serverIds) { try { mOdoo.unlink(model.getModelName(), serverIds); return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * Removes non exist record from local database * * @param model */ private void removeNonExistRecordFromLocal(OModel model) { List ids = model.getServerIds(); try { ODomain domain = new ODomain(); domain.add("id", "in", new JSONArray(ids.toString())); JSONObject result = mOdoo.search_read(model.getModelName(), new JSONObject(), domain.get()); JSONArray records = result.getJSONArray("records"); if (records.length() > 0) { for (int i = 0; i < records.length(); i++) { JSONObject record = records.getJSONObject(i); ids.remove(ids.indexOf(record.getInt("id"))); } } int removedCounter = 0; if (ids.size() > 0) { removedCounter = model.deleteRecords(ids, true); } Log.i(TAG, removedCounter + " Records removed from local database."); } catch (Exception e) { e.printStackTrace(); } } public Class getModelClass() { return mModelClass; } public void setModel(OModel model) { mModel = model; } public OModel getModel() { return mModel; } public OSyncAdapter onSyncFinish(ISyncFinishListener syncFinish) { mSyncFinishListeners.put(mModel.getModelName(), syncFinish); return this; } } ================================================ FILE: app/src/main/java/com/odoo/core/service/OSyncDataUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 2/1/15 4:15 PM */ package com.odoo.core.service; import android.content.Context; import android.content.SyncResult; import android.util.Log; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OUser; import com.odoo.core.support.OdooFields; import com.odoo.core.utils.JSONUtils; import com.odoo.core.utils.ODateUtils; import com.odoo.core.utils.OListUtils; import com.odoo.core.utils.StringUtils; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import odoo.ODomain; import odoo.Odoo; public class OSyncDataUtils { public static final String TAG = OSyncDataUtils.class.getSimpleName(); private Context mContext; private OModel mModel; private OUser mUser; private JSONObject response; private HashSet recordsId = new HashSet<>(); private HashMap relationRecordsHashMap = new HashMap<>(); private Odoo mOdoo; private SyncResult mResult; private HashMap> updateToServerRecords = new HashMap<>(); private Boolean mCreateRelationRecords = true; public OSyncDataUtils(Context context, Odoo odoo, OModel model, OUser user, JSONObject response, SyncResult result, Boolean createRelRecord) { mContext = context; mOdoo = odoo; mModel = model; mUser = user; this.response = response; mResult = result; mCreateRelationRecords = createRelRecord; JSONArray updateInLocal = checkLocalUpdatedRecords(); handleResult(updateInLocal); } private JSONArray checkLocalUpdatedRecords() { // Array of records which are new or need to update in local JSONArray finalRecords = new JSONArray(); try { // Getting list of ids which are present in local database List serverIds = new ArrayList<>(); HashMap serverIdRecords = new HashMap<>(); JSONArray records = response.getJSONArray("records"); for (int i = 0; i < records.length(); i++) { JSONObject record = records.getJSONObject(i); if (mModel.hasServerRecord(record.getInt("id")) && mModel.isServerRecordDirty(record.getInt("id"))) { int server_id = record.getInt("id"); serverIds.add(server_id); serverIdRecords.put("key_" + server_id, record); } else { finalRecords.put(record); } } // getting local dirty records if server records length = 0 if (records.length() <= 0) { for (ODataRow row : mModel.select(new String[]{}, "_is_dirty = ? and _is_active = ? and id != ?", new String[]{"true", "true", "0"})) { serverIds.add(row.getInt("id")); } } // Comparing dirty (updated) record List updateToServerIds = new ArrayList<>(); if (serverIds.size() > 0) { HashMap write_dates = getWriteDate(mModel, serverIds); for (Integer server_id : serverIds) { String key = "key_" + server_id; String write_date = write_dates.get(key); ODataRow record = mModel.browse(new String[]{"_write_date"}, "id = ?", new String[]{server_id + ""}); if (record != null) { Date write_date_obj = ODateUtils.createDateObject(write_date, ODateUtils.DEFAULT_FORMAT, false); Date _write_date_obj = ODateUtils.createDateObject(record.getString("_write_date"), ODateUtils.DEFAULT_FORMAT, false); if (_write_date_obj.compareTo(write_date_obj) > 0) { // Local record is latest updateToServerIds.add(server_id); } else { if (serverIdRecords.containsKey(key)) { finalRecords.put(serverIdRecords.get(key)); } } } } } if (updateToServerIds.size() > 0) { updateToServerRecords.put(mModel.getModelName(), updateToServerIds); } } catch (Exception e) { e.printStackTrace(); } return finalRecords; } private HashMap getWriteDate(OModel model, List ids) { HashMap map = new HashMap<>(); try { JSONArray result; if (model.getColumn("write_date") != null) { OdooFields fields = new OdooFields(new String[]{"write_date"}); ODomain domain = new ODomain(); domain.add("id", "in", ids); JSONObject data = mOdoo.search_read(model.getModelName(), fields.get(), domain.get()); result = data.getJSONArray("records"); } else { JSONObject data = mOdoo.perm_read(model.getModelName(), ids); result = data.getJSONArray("result"); } if (result.length() > 0) { for (int i = 0; i < result.length(); i++) { JSONObject obj = result.getJSONObject(i); map.put("key_" + obj.getInt("id"), obj.getString("write_date")); } } } catch (Exception e) { e.printStackTrace(); } return map; } private void handleResult(JSONArray records) { try { recordsId.clear(); int length = records.length(); int counter = 0; List columns = mModel.getColumns(false); columns.addAll(mModel.getFunctionalColumns()); for (int i = 0; i < length; i++) { JSONObject record = records.getJSONObject(i); OValues values = new OValues(); recordsId.add(mModel.getModelName() + "_" + record.getInt("id")); for (OColumn column : columns) { String name = column.getName(); if (column.getRelationType() == null) { // checks for functional store fields if (column.isFunctionalColumn() && column.canFunctionalStore()) { List depends = column.getFunctionalStoreDepends(); OValues dependValues = new OValues(); if (!column.isLocal()) dependValues.put(column.getName(), record.get(column.getName())); for (String depend : depends) { if (record.has(depend)) { dependValues.put(depend, record.get(depend)); } } Object value = mModel.getFunctionalMethodValue(column, dependValues); values.put(column.getName(), value); } else { // Normal Columns values.put(name, record.get(name)); } } else { // Relation Columns if (!(record.get(name) instanceof Boolean)) { switch (column.getRelationType()) { case ManyToOne: JSONArray m2oData = record.getJSONArray(name); OModel m2o_model = mModel.createInstance(column.getType()); String recKey = m2o_model.getModelName() + "_" + m2oData.get(0); int m2oRowId; if (!recordsId.contains(recKey)) { OValues m2oValue = new OValues(); m2oValue.put("id", m2oData.get(0)); m2oValue.put(m2o_model.getDefaultNameColumn(), m2oData.get(1)); m2oValue.put("_is_dirty", "false"); m2oRowId = m2o_model.insertOrUpdate(m2oData.getInt(0), m2oValue); } else { m2oRowId = m2o_model.selectRowId(m2oData.getInt(0)); } values.put(name, m2oRowId); if (mCreateRelationRecords) { // Add id to sync if model contains more than (id,name) columns if (m2o_model.getColumns(false).size() > 2 || (m2o_model.getColumns(false).size() > 4 && mModel.getOdooVersion().getVersion_number() > 7)) { List m2oIds = new ArrayList<>(); m2oIds.add(m2oData.getInt(0)); addUpdateRelationRecord(mModel, m2o_model.getTableName(), column.getType(), name, null, column.getRelationType(), m2oIds); } } m2o_model.close(); break; case ManyToMany: OModel m2mModel = mModel.createInstance(column.getType()); List m2mIds = JSONUtils.toList(record.getJSONArray(name)); if (mCreateRelationRecords) { addUpdateRelationRecord(mModel, m2mModel.getTableName(), column.getType(), name, null, column.getRelationType(), (column.getRecordSyncLimit() > 0) ? m2mIds.subList(0, column.getRecordSyncLimit()) : m2mIds); } List m2mRowIds = new ArrayList<>(); for (Integer id : m2mIds) { recKey = m2mModel.getModelName() + "_" + id; int r_id; if (!recordsId.contains(recKey)) { OValues m2mValues = new OValues(); m2mValues.put("id", id); m2mValues.put("_is_dirty", "false"); r_id = m2mModel.insertOrUpdate(id, m2mValues); } else { r_id = m2mModel.selectRowId(id); } m2mRowIds.add(r_id); } if (m2mRowIds.size() > 0) { // Putting many to many related ids // (generated _id for each of server ids) values.put(name, m2mRowIds); } m2mModel.close(); break; case OneToMany: if (mCreateRelationRecords) { OModel o2mModel = mModel.createInstance(column.getType()); List o2mIds = JSONUtils.toList(record.getJSONArray(name)); addUpdateRelationRecord(mModel, o2mModel.getTableName(), column.getType(), name, column.getRelatedColumn(), column.getRelationType(), (column.getRecordSyncLimit() > 0) ? o2mIds.subList(0, column.getRecordSyncLimit()) : o2mIds); o2mModel.close(); } break; } } } } // Some default values values.put("id", record.getInt("id")); values.put("_write_date", ODateUtils.getUTCDate()); values.put("_is_active", "true"); values.put("_is_dirty", "false"); mModel.insertOrUpdate(record.getInt("id"), values); counter++; } Log.i(TAG, counter + " records affected"); } catch (Exception e) { e.printStackTrace(); } } public boolean updateRecordsOnServer(OSyncAdapter adapter) { try { // Use key (modal name) from updateToServerRecords // use updateToServerRecords ids int counter = 0; for (String key : updateToServerRecords.keySet()) { OModel model = OModel.get(mContext, key, mUser.getAndroidName()); List ids = OListUtils.toStringList(updateToServerRecords.get(key)); counter += ids.size(); for (ODataRow record : model.select(null, "id IN ( " + StringUtils.repeat("?, ", ids.size() - 1) + " ?)", ids.toArray(new String[ids.size()]))) { if (adapter.validateRelationRecords(model, record)) { mOdoo.updateValues(model.getModelName(), JSONUtils.createJSONValues(model, record), record.getInt("id")); OValues value = new OValues(); value.put("_is_dirty", "false"); value.put("_write_date", ODateUtils.getUTCDate()); model.update(record.getInt(OColumn.ROW_ID), value); model.close(); } } } Log.i(TAG, counter + " records updated on server"); } catch (Exception e) { e.printStackTrace(); } return false; } private void addUpdateRelationRecord(OModel baseModel, String relTable, Class model, String column, String relatedColumn, OColumn.RelationType type, List ids) { String key = relTable + "_" + column; if (relationRecordsHashMap.containsKey(key)) { SyncRelationRecords data = relationRecordsHashMap.get(key); data.updateIds(ids); relationRecordsHashMap.put(key, data); } else { relationRecordsHashMap.put(key, new SyncRelationRecords(baseModel, model, column, relatedColumn, type, ids)); } } public HashMap getRelationRecordsHashMap() { if (mCreateRelationRecords) return relationRecordsHashMap; return new HashMap<>(); } @Override protected void finalize() throws Throwable { super.finalize(); if (mModel != null) mModel.close(); } public static class SyncRelationRecords { private OModel baseModel; private Class relationModel; private String relationColumn; private String relatedColumn; private OColumn.RelationType relationType; private List serverIds = new ArrayList<>(); public SyncRelationRecords(OModel baseModel, Class relationModel, String relationColumn, String relatedColumn, OColumn.RelationType relationType, List serverIds) { this.baseModel = baseModel; this.relationModel = relationModel; this.relationColumn = relationColumn; this.relatedColumn = relatedColumn; this.relationType = relationType; this.serverIds.addAll(serverIds); } public OModel getBaseModel() { return baseModel; } public void setBaseModel(OModel baseModel) { this.baseModel = baseModel; } public Class getRelationModel() { return relationModel; } public void setRelationModel(Class relationModel) { this.relationModel = relationModel; } public String getRelationColumn() { return relationColumn; } public void setRelationColumn(String relationColumn) { this.relationColumn = relationColumn; } public String getRelatedColumn() { return relatedColumn; } public void setRelatedColumn(String relatedColumn) { this.relatedColumn = relatedColumn; } public OColumn.RelationType getRelationType() { return relationType; } public void setRelationType(OColumn.RelationType relationType) { this.relationType = relationType; } public List getServerIds() { return serverIds; } public void setServerIds(List serverIds) { this.serverIds.clear(); this.serverIds.addAll(serverIds); } public void updateIds(List ids) { this.serverIds.addAll(ids); } public List getUniqueIds() { List ids = new ArrayList<>(); HashSet uIds = new HashSet<>(serverIds); ids.addAll(uIds); return ids; } } } ================================================ FILE: app/src/main/java/com/odoo/core/service/OSyncService.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/1/15 3:16 PM */ package com.odoo.core.service; import android.app.Service; import android.content.AbstractThreadedSyncAdapter; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import com.odoo.core.service.receivers.ISyncFinishReceiver; import com.odoo.core.support.OUser; public abstract class OSyncService extends Service { public static final String TAG = OSyncService.class.getSimpleName(); private static final Object sSyncAdapterLock = new Object(); private AbstractThreadedSyncAdapter sSyncAdapter = null; private Context mContext; private OSyncService service; @Override public void onCreate() { super.onCreate(); mContext = getApplicationContext(); service = this; Log.i(TAG, "Service created"); synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) { sSyncAdapter = getSyncAdapter(service, mContext); } } } public void setService(OSyncService service) { this.service = service; } public void setContext(Context context) { mContext = context; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "Service destroyed"); Intent intent = new Intent(); intent.setAction(ISyncFinishReceiver.SYNC_FINISH); getApplicationContext().sendBroadcast(intent); } @Override public IBinder onBind(Intent intent) { return sSyncAdapter.getSyncAdapterBinder(); } public abstract OSyncAdapter getSyncAdapter(OSyncService service, Context context); public abstract void performDataSync(OSyncAdapter adapter, Bundle extras, OUser user); } ================================================ FILE: app/src/main/java/com/odoo/core/service/receivers/ISyncFinishReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/1/15 3:18 PM */ package com.odoo.core.service.receivers; import android.content.BroadcastReceiver; public abstract class ISyncFinishReceiver extends BroadcastReceiver { public static final String SYNC_FINISH = "ISyncFinishReceiver.SYNC_FINISH"; } ================================================ FILE: app/src/main/java/com/odoo/core/support/OUser.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 17/12/14 6:19 PM */ package com.odoo.core.support; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.os.Bundle; import com.odoo.core.auth.OdooAccountManager; public class OUser { /** * The username. */ private String username; /** * The name. */ private String name; /** * The user_id. */ private int user_id; /** * The partner_id. */ private int partner_id; /** * The timezone. */ private String timezone; /** * The isactive. */ private boolean isactive; /** * The avatar. */ private String avatar; /** * The database. */ private String database; /** * The host. */ private String host; /** * The android_name. */ private String android_name; /** * The password. */ private String password; /** * The company_id. */ private String company_id; /** * The allow_self_signed_ssl. */ private boolean allow_self_signed_ssl = false; /** * The oauth_login. */ private boolean oauth_login = false; /** * The version_number. */ private Integer version_number = 0; /** * The version_serie. */ private String version_serie = null; // If oauth login /** * The instance_url. */ private String instance_url = null; /** * The instance_database. */ private String instance_database = null; /** * The client_id. */ private String client_id = null; /** * Account instance */ private Account account = null; /** * Current. * * @param context the context * @return the o user */ public static OUser current(Context context) { return OdooAccountManager.getActiveUser(context); } /** * Gets the name. * * @return the name */ public String getName() { return name; } /** * Sets the name. * * @param name the new name */ public void setName(String name) { this.name = name; } /** * Gets the company_id. * * @return the company_id */ public String getCompany_id() { return company_id; } /** * Sets the company_id. * * @param company_id the new company_id */ public void setCompany_id(String company_id) { this.company_id = company_id; } /** * Gets the data as bundle. * * @return the as bundle */ public Bundle getAsBundle() { Bundle bundle = new Bundle(); bundle.putString("name", this.getName()); bundle.putString("username", this.getUsername()); bundle.putString("user_id", this.getUser_id() + ""); bundle.putString("partner_id", this.getPartner_id() + ""); bundle.putString("timezone", this.getTimezone()); bundle.putString("isactive", String.valueOf(this.isIsactive())); bundle.putString("avatar", this.getAvatar()); bundle.putString("database", this.getDatabase()); bundle.putString("host", this.getHost()); bundle.putString("android_name", this.getAndroidName()); bundle.putString("password", this.getPassword()); bundle.putString("company_id", this.getCompany_id()); bundle.putString("allow_self_signed_ssl", String.valueOf(this.isAllowSelfSignedSSL())); bundle.putString("instance_database", this.getInstanceDatabase()); bundle.putString("instance_url", this.getInstanceUrl()); bundle.putString("oauth_login", this.isOAauthLogin() + ""); bundle.putString("client_id", this.getClientId()); bundle.putString("odoo_version_number", getVersion_number() + ""); bundle.putString("odoo_version_serie", getVersion_serie()); return bundle; } /** * Sets the data from bundle. * * @param data the new from bundle */ public void setFromBundle(Bundle data) { setName(data.getString("name")); setUsername(data.getString("username")); setUser_id(Integer.parseInt(data.getString("user_id"))); setPartner_id(Integer.parseInt(data.getString("partner_id"))); setTimezone(data.getString("timezone")); setIsactive(Boolean.parseBoolean(data.getString("isactive"))); setAvatar(data.getString("avatar")); setDatabase(data.getString("database")); setHost(data.getString("host")); setAndroidName(data.getString("android_name")); setPassword(data.getString("password")); setCompany_id(data.getString("company_id")); setAllowSelfSignedSSL(Boolean.parseBoolean(data .getString("allow_self_signed_ssl"))); setVersion_number(Integer.parseInt(data .getString("odoo_version_number"))); setVersion_serie(data.getString("odoo_version_serie")); // If oAuth Login setInstanceDatabase(data.getString("instance_database")); setInstanceUrl(data.getString("instance_url")); setOAuthLogin(Boolean.parseBoolean(data.getString("oauth_login"))); setClientId(data.getString("client_id")); } /** * Fill from account. * * @param accMgr the acc mgr * @param account the account */ public void fillFromAccount(AccountManager accMgr, Account account) { setName(accMgr.getUserData(account, "name")); setUsername(accMgr.getUserData(account, "username")); setUser_id(Integer.parseInt(accMgr.getUserData(account, "user_id"))); setPartner_id(Integer.parseInt(accMgr .getUserData(account, "partner_id"))); setTimezone(accMgr.getUserData(account, "timezone")); setIsactive(Boolean.parseBoolean(accMgr .getUserData(account, "isactive"))); try { setAvatar(accMgr.getUserData(account, "avatar")); } catch (Exception e) { setAvatar("false"); } setDatabase(accMgr.getUserData(account, "database")); setHost(accMgr.getUserData(account, "host")); setAndroidName(accMgr.getUserData(account, "android_name")); setPassword(accMgr.getUserData(account, "password")); setCompany_id(accMgr.getUserData(account, "company_id")); setAllowSelfSignedSSL(Boolean.parseBoolean(accMgr.getUserData(account, "allow_self_signed_ssl"))); String odoo_version_number = accMgr.getUserData(account, "odoo_version_number"); if (odoo_version_number != null && !odoo_version_number.contains("null")) setVersion_number(Integer.parseInt(odoo_version_number)); setVersion_serie(accMgr.getUserData(account, "odoo_version_serie")); // If oAuth login setOAuthLogin(Boolean.parseBoolean(accMgr.getUserData(account, "oauth_login"))); setInstanceDatabase(accMgr.getUserData(account, "instance_database")); setInstanceUrl(accMgr.getUserData(account, "instance_url")); setClientId(accMgr.getUserData(account, "client_id")); } /** * Gets the password. * * @return the password */ public String getPassword() { return password; } /** * Sets the password. * * @param password the new password */ public void setPassword(String password) { this.password = password; } /** * Gets the android name. * * @return the android name */ public String getAndroidName() { return this.android_name; } /** * Sets the android name. * * @param android_name the new android name */ public void setAndroidName(String android_name) { this.android_name = android_name; } /** * Gets the username. * * @return the username */ public String getUsername() { return username; } /** * Sets the username. * * @param username the new username */ public void setUsername(String username) { this.username = username; } /** * Gets the user_id. * * @return the user_id */ public int getUser_id() { return user_id; } /** * Sets the user_id. * * @param user_id the new user_id */ public void setUser_id(int user_id) { this.user_id = user_id; } /** * Gets the partner_id. * * @return the partner_id */ public int getPartner_id() { return partner_id; } /** * Sets the partner_id. * * @param partner_id the new partner_id */ public void setPartner_id(int partner_id) { this.partner_id = partner_id; } /** * Gets the timezone. * * @return the timezone */ public String getTimezone() { return timezone; } /** * Sets the timezone. * * @param timezone the new timezone */ public void setTimezone(String timezone) { this.timezone = timezone; } /** * Checks if is isactive. * * @return true, if is isactive */ public boolean isIsactive() { return isactive; } /** * Sets the isactive. * * @param isactive the new isactive */ public void setIsactive(boolean isactive) { this.isactive = isactive; } /** * Gets the avatar. * * @return the avatar */ public String getAvatar() { return avatar; } /** * Sets the avatar. * * @param avatar the new avatar */ public void setAvatar(String avatar) { this.avatar = avatar; } /** * Gets the database. * * @return the database */ public String getDatabase() { return database; } /** * Sets the database. * * @param database the new database */ public void setDatabase(String database) { this.database = database; } /** * Gets the host. * * @return the host */ public String getHost() { return host; } /** * Sets the host. * * @param host the new host */ public void setHost(String host) { this.host = host; } /** * Checks if is allow self signed ssl. * * @return true, if is allow self signed ssl */ public boolean isAllowSelfSignedSSL() { return allow_self_signed_ssl; } /** * Sets the allow self signed ssl. * * @param allow_self_signed_ssl the new allow self signed ssl */ public void setAllowSelfSignedSSL(boolean allow_self_signed_ssl) { this.allow_self_signed_ssl = allow_self_signed_ssl; } /** * Checks if is o aauth login. * * @return true, if is o aauth login */ public boolean isOAauthLogin() { return oauth_login; } /** * Sets the o auth login. * * @param oauth_login the new o auth login */ public void setOAuthLogin(boolean oauth_login) { this.oauth_login = oauth_login; } /** * Gets the instance url. * * @return the instance url */ public String getInstanceUrl() { return instance_url; } /** * Sets the instance url. * * @param instnace_url the new instance url */ public void setInstanceUrl(String instnace_url) { this.instance_url = instnace_url; } /** * Gets the instance database. * * @return the instance database */ public String getInstanceDatabase() { return instance_database; } /** * Sets the instance database. * * @param instance_database the new instance database */ public void setInstanceDatabase(String instance_database) { this.instance_database = instance_database; } /** * Gets the client id. * * @return the client id */ public String getClientId() { return client_id; } /** * Sets the client id. * * @param client_id the new client id */ public void setClientId(String client_id) { this.client_id = client_id; } public Integer getVersion_number() { return version_number; } public void setVersion_number(Integer version_number) { this.version_number = version_number; } public String getVersion_serie() { return version_serie; } public void setVersion_serie(String version_serie) { this.version_serie = version_serie; } public String getDBName() { String db_name = "OdooSQLite"; db_name += "_" + getUsername(); db_name += "_" + getDatabase(); return db_name + ".db"; } @Override public String toString() { return getAndroidName(); } public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/OdooFields.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 4:23 PM */ package com.odoo.core.support; import com.odoo.core.orm.fields.OColumn; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; public class OdooFields { private JSONObject jFields = new JSONObject(); public OdooFields(List columns) { List fields = new ArrayList<>(); if (columns != null) { for (OColumn column : columns) { if (!column.isLocal() && !column.isFunctionalColumn()) { fields.add(column.getName()); } } } addAll(fields.toArray(new String[fields.size()])); } public OdooFields(String[] fields) { addAll(fields); } public void addAll(String[] fields) { try { for (String field : fields) { jFields.accumulate("fields", field); } if (fields.length == 1) { jFields.accumulate("fields", fields[0]); } } catch (Exception e) { e.printStackTrace(); } } public JSONObject get() { return jFields; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/OdooInstancesSelectorDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 5:56 PM */ package com.odoo.core.support; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.core.utils.controls.ExpandableHeightGridView; import com.odoo.R; import java.net.URL; import java.util.ArrayList; import java.util.List; import odoo.OdooInstance; public class OdooInstancesSelectorDialog implements AdapterView.OnItemClickListener { private Context mContext; private ExpandableHeightGridView mGrid; private ArrayAdapter mAdapter; private List instances = new ArrayList(); private AlertDialog dialog; private AlertDialog.Builder builder; private OnInstanceSelectListener mOnInstanceSelectListener = null; private OUser mUser; private List imageLoaderLists = new ArrayList<>(); public OdooInstancesSelectorDialog(Context context, OUser user) { mContext = context; mUser = user; AbsListView.LayoutParams params = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT); mGrid = new ExpandableHeightGridView(mContext); mGrid.setLayoutParams(params); mAdapter = new ArrayAdapter(mContext, R.layout.base_instance_item, instances) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(mContext).inflate(R.layout.base_instance_item, parent, false); generateView(position, convertView, getItem(position)); return convertView; } }; int padd = OResource.dimen(mContext, R.dimen.activity_horizontal_margin); mGrid.setPadding(padd, padd, padd, padd); mGrid.setNumColumns(2); mGrid.setAdapter(mAdapter); mGrid.setOnItemClickListener(this); } public void setInstances(List items) { instances.clear(); instances.addAll(items); mAdapter.notifyDataSetChanged(); } private void generateView(int position, View view, OdooInstance instance) { OControls.setText(view, R.id.txvInstanceUrl, instance.getInstanceUrl()); OControls.setText(view, R.id.txvInstanceName, instance.getCompanyName()); String imageURL = instance.getInstanceUrl() + "/web/binary/company_logo?dbname=" + instance.getDatabaseName(); ImageLoader imageLoader = new ImageLoader(position, imageURL, R.id.imgInstance); imageLoaderLists.add(imageLoader); imageLoader.execute(); } public void showDialog() { if (dialog != null) dialog.dismiss(); dialog = null; builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.label_select_instance); builder.setView(mGrid); builder.setCancelable(false); builder.setNegativeButton(OResource.string(mContext, R.string.label_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mOnInstanceSelectListener != null) { mOnInstanceSelectListener.canceledInstanceSelect(); } } }); dialog = builder.create(); dialog.show(); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { OdooInstance instance = mAdapter.getItem(position); if (dialog != null) dialog.dismiss(); if (mOnInstanceSelectListener != null) { for (ImageLoader imageLoader : imageLoaderLists) { imageLoader.cancel(true); } mOnInstanceSelectListener.instanceSelected(instance, mUser); } } public void setOnInstanceSelectListener(OnInstanceSelectListener listener) { mOnInstanceSelectListener = listener; } public interface OnInstanceSelectListener { public void instanceSelected(OdooInstance instance, OUser user); public void canceledInstanceSelect(); } class ImageLoader extends AsyncTask { String image_url = ""; int image_view = -1; int view_pos = -1; Bitmap bmp = null; public ImageLoader(int pos, String url, int image_view) { view_pos = pos; this.image_view = image_view; image_url = url; } @Override protected Void doInBackground(Void... params) { try { URL url = new URL(image_url); bmp = BitmapFactory.decodeStream(url.openConnection() .getInputStream()); } catch (Exception e) { } return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); if (bmp != null) { View mView = mGrid.getChildAt(view_pos); OControls.setImage(mView, image_view, bmp); } } } } ================================================ FILE: app/src/main/java/com/odoo/core/support/OdooLoginHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 3:15 PM */ package com.odoo.core.support; import android.content.Context; import com.odoo.App; import com.odoo.datas.OConstants; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import odoo.OArguments; import odoo.ODomain; import odoo.Odoo; import odoo.OdooAccountExpireException; import odoo.OdooInstance; import odoo.OdooVersion; public class OdooLoginHelper { private App mApp; private Odoo mOdoo; private Context mContext; public OdooLoginHelper(Context context) { mContext = context; mApp = (App) mContext.getApplicationContext(); } public OUser login(String username, String password, String database, String serverURL, Boolean forceConnect) { try { mOdoo = new Odoo(mContext, serverURL, forceConnect); JSONObject res = mOdoo.authenticate(username, password, database); int user_id; if (res.get("uid") instanceof Integer) { user_id = res.getInt("uid"); OUser user = new OUser(); user.setOAuthLogin(false); user.setPassword(password); user.setUsername(username); user.setAllowSelfSignedSSL(forceConnect); user.setHost(serverURL); user.setUser_id(user_id); user.setDatabase(database); ODomain domain = new ODomain(); domain.add("id", "=", user_id); user = getUserDetails(domain, user, null); mApp.setOdoo(mOdoo, user); return user; } } catch (Exception e) { e.printStackTrace(); } return null; } public OUser instanceLogin(OdooInstance instance, OUser uData) throws OdooAccountExpireException { try { JSONObject res = mOdoo.oauth_authenticate(instance, uData.getUsername(), uData.getPassword()); int user_id; if (res.get("uid") instanceof Integer) { user_id = res.getInt("uid"); ODomain domain = new ODomain(); domain.add("id", "=", user_id); uData = getUserDetails(domain, uData, instance); mApp.setOdoo(mOdoo, uData); return uData; } } catch (OdooAccountExpireException e) { throw new OdooAccountExpireException(e.getMessage()); } catch (Exception e) { e.printStackTrace(); } return null; } private OUser getUserDetails(ODomain domain, OUser data, OdooInstance instance) { OUser user = data; try { OdooFields fields = new OdooFields(new String[]{ "name", "partner_id", "tz", "image_medium", "company_id" }); JSONObject res = mOdoo.search_read("res.users", fields.get(), domain.get()); JSONObject userData = res.getJSONArray("records").getJSONObject(0); String database = user.getDatabase(); if (instance != null) { user.setOAuthLogin(true); database = instance.getDatabaseName(); user.setInstanceDatabase(instance.getDatabaseName()); user.setInstanceUrl(instance.getInstanceUrl()); user.setClientId(instance.getClientId()); } user.setUser_id(userData.getInt("id")); user.setName(userData.getString("name")); user.setAvatar(userData.getString("image_medium")); user.setIsactive(true); user.setAndroidName(androidName(user.getUsername(), database)); user.setPartner_id(userData.getJSONArray("partner_id").getInt(0)); user.setTimezone(userData.getString("tz")); user.setCompany_id(userData.getJSONArray("company_id").getInt(0) + ""); OdooVersion version = mOdoo.getOdooVersion(); user.setVersion_number(version.getVersion_number()); user.setVersion_serie(version.getServer_serie()); } catch (Exception e) { e.printStackTrace(); } return user; } public String androidName(String username, String database) { return username + "[" + database + "]"; } public List getOdooInstances(OUser user) { List instances = new ArrayList(); //Default Instance (www.odoo.com) OdooInstance oInstance = new OdooInstance(); oInstance.setCompanyName(OConstants.ODOO_COMPANY_NAME); oInstance.setInstanceUrl(OConstants.URL_ODOO); instances.add(oInstance); //Getting user instances try { for (OdooInstance instance : mOdoo.get_instances(new OArguments().get())) { if (instance.isSaas()) { instances.add(instance); } } } catch (Exception e) { e.printStackTrace(); } return instances; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/OdooServerTester.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 12:00 PM */ package com.odoo.core.support; import android.content.Context; import android.text.TextUtils; import com.odoo.core.utils.JSONUtils; import org.json.JSONArray; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; import odoo.OVersionException; import odoo.Odoo; public class OdooServerTester { private Context mContext; private Boolean mForceConnect = false; private Odoo mOdoo; private JSONArray mDatabases = null; public OdooServerTester(Context context) { mContext = context; } public boolean testConnection(String serverURL, Boolean forceConnect) throws SSLPeerUnverifiedException, OVersionException { mForceConnect = forceConnect; if (!TextUtils.isEmpty(serverURL)) { try { mOdoo = new Odoo(mContext, serverURL, forceConnect); mDatabases = mOdoo.getDatabaseList(); if (mDatabases == null) { mDatabases = new JSONArray(); if (mOdoo.getDatabaseName() != null) { mDatabases.put(mOdoo.getDatabaseName()); } } if (mDatabases.length() > 0) return true; } catch (SSLPeerUnverifiedException peer) { throw new SSLPeerUnverifiedException(peer.getMessage()); } catch (OVersionException version) { throw new OVersionException(version.getMessage()); } catch (Exception e) { e.printStackTrace(); } } return false; } public List getDatabases() { return JSONUtils.toList(mDatabases); } } ================================================ FILE: app/src/main/java/com/odoo/core/support/OdooUserLoginSelectorDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 1:49 PM */ package com.odoo.core.support; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ImageView; import com.odoo.R; import com.odoo.core.account.OdooUserAskPassword; import com.odoo.core.auth.OdooAccountManager; import com.odoo.core.utils.BitmapUtils; import com.odoo.core.utils.OAlert; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.core.utils.controls.ExpandableHeightGridView; import java.util.List; import odoo.controls.BezelImageView; public class OdooUserLoginSelectorDialog implements AdapterView.OnItemClickListener { private Context mContext; private ExpandableHeightGridView mGrid; private ArrayAdapter mAdapter; private AlertDialog dialog; private AlertDialog.Builder builder; private OUser mUser; private IUserLoginSelectListener mIUserLoginSelectListener = null; private OdooUserAskPassword askPassword = null; public OdooUserLoginSelectorDialog(Context context) { mContext = context; AbsListView.LayoutParams params = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT); mGrid = new ExpandableHeightGridView(mContext); mGrid.setLayoutParams(params); List accounts = OdooAccountManager.getAllAccounts(mContext); mAdapter = new ArrayAdapter(mContext, R.layout.base_instance_item, accounts) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(mContext).inflate(R.layout.base_instance_item, parent, false); generateView(position, convertView, getItem(position)); return convertView; } }; int padding = OResource.dimen(mContext, R.dimen.activity_horizontal_margin); mGrid.setPadding(padding, padding, padding, padding); if (accounts.size() > 1) mGrid.setNumColumns(2); mGrid.setAdapter(mAdapter); mGrid.setOnItemClickListener(this); } private void generateView(int position, View view, OUser user) { BezelImageView imgView = (BezelImageView) view.findViewById(R.id.imgInstance); if (user.getAvatar().equals("false")) { imgView.setImageResource(R.drawable.avatar); } else { imgView.setImageBitmap(BitmapUtils.getBitmapImage(mContext, user.getAvatar())); } imgView.setScaleType(ImageView.ScaleType.CENTER_CROP); imgView.autoSetMaskDrawable(); OControls.setText(view, R.id.txvInstanceName, user.getName()); OControls.setText(view, R.id.txvInstanceUrl, (user.isOAauthLogin()) ? user.getInstanceUrl() : user.getHost()); } public void setUserLoginSelectListener(IUserLoginSelectListener listener) { mIUserLoginSelectListener = listener; } public void show() { if (dialog != null) dialog.dismiss(); dialog = null; builder = new AlertDialog.Builder(mContext); builder.setTitle(R.string.label_select_user); builder.setView(mGrid); builder.setCancelable(false); builder.setPositiveButton(OResource.string(mContext, R.string.label_drawer_account_add_account), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mIUserLoginSelectListener != null) { mIUserLoginSelectListener.onNewAccountRequest(); } dialog.dismiss(); } }); builder.setNegativeButton(OResource.string(mContext, R.string.label_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mIUserLoginSelectListener != null) { mIUserLoginSelectListener.onCancelSelect(); } dialog.dismiss(); } }); dialog = builder.create(); dialog.show(); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { final OUser user = mAdapter.getItem(position); dialog.dismiss(); if (mIUserLoginSelectListener != null) { // Ask for password of account askPassword = OdooUserAskPassword.get(mContext, user); askPassword.setOnUserPasswordValidateListener(new OdooUserAskPassword.OnUserPasswordValidateListener() { @Override public void onSuccess() { mIUserLoginSelectListener.onUserSelected(user); } @Override public void onCancel() { mIUserLoginSelectListener.onRequestAccountSelect(); } @Override public void onFail() { OAlert.showError(mContext, OResource.string(mContext, R.string.error_invalid_password), new OAlert.OnAlertDismissListener() { @Override public void onAlertDismiss() { onCancel(); } }); } }); askPassword.show(); } } public interface IUserLoginSelectListener { public void onUserSelected(OUser user); public void onNewAccountRequest(); public void onCancelSelect(); public void onRequestAccountSelect(); } } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/AddonsHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:12 PM */ package com.odoo.core.support.addons; import android.util.Log; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class AddonsHelper { public static final String TAG = AddonsHelper.class.getSimpleName(); private List addons = new ArrayList(); private OAddon defaultAddon = null; public List getAddons() { if (addons.size() <= 0) { prepareAddons(); } return addons; } private void prepareAddons() { addons.clear(); for (Field addon : getClass().getDeclaredFields()) { if (addon.getType().isAssignableFrom(OAddon.class)) { addon.setAccessible(true); try { OAddon mAddon = (OAddon) addon.get(this); if (mAddon.isDefault()) { defaultAddon = mAddon; addons.add(0, defaultAddon); } else { addons.add(mAddon); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } } } } public OAddon getDefaultAddon() { prepareAddons(); return defaultAddon; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/OAddon.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:13 PM */ package com.odoo.core.support.addons; import android.util.Log; public class OAddon { public static final String TAG = OAddon.class.getSimpleName(); private Class addon = null; private Boolean isDefault = false; public OAddon(Class addon_class) { addon = addon_class; } public OAddon setDefault() { isDefault = true; return this; } public Object get() { try { return addon.newInstance(); } catch (Exception e) { Log.i(TAG, e.getMessage()); } return null; } public Boolean isDefault() { return isDefault; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/fragment/BaseFragment.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:29 PM */ package com.odoo.core.support.addons.fragment; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SyncStatusObserver; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.SearchView; import android.text.TextUtils; import android.view.Menu; import android.view.View; import android.widget.ListView; import com.odoo.App; import com.odoo.OdooActivity; import com.odoo.R; import com.odoo.core.orm.OModel; import com.odoo.core.service.receivers.ISyncFinishReceiver; import com.odoo.core.support.OUser; import com.odoo.core.utils.OResource; import odoo.controls.fab.FloatingActionButton; public abstract class BaseFragment extends Fragment implements IBaseFragment { private Context mContext; private OModel syncStatusObserverModel = null; private String drawerRefreshTag = null; private Object mSyncObserverHandle; private ISyncStatusObserverListener mSyncStatusObserverListener = null; private SwipeRefreshLayout mSwipeRefresh = null; private IOnSearchViewChangeListener mOnSearchViewChangeListener; private SearchView mSearchView; private FloatingActionButton mFab = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getActivity(); parent().setHasActionBarSpinner(false); } public void setTitle(String title) { getActivity().setTitle(title); } public OModel db() { Class model = database(); if (model != null) { return new OModel(mContext, null, user()).createInstance(model); } return null; } public OUser user() { if (mContext != null) return OUser.current(mContext); return null; } public OdooActivity parent() { return (OdooActivity) mContext; } public String _s(int res_id) { return OResource.string(mContext, res_id); } public int _c(int res_id) { return OResource.color(mContext, res_id); } public int _dim(int res_id) { return OResource.dimen(mContext, res_id); } // Sync Observer public void setHasSyncStatusObserver(String drawerRefreshTag, ISyncStatusObserverListener syncStatusObserver, OModel model) { this.drawerRefreshTag = drawerRefreshTag; mSyncStatusObserverListener = syncStatusObserver; syncStatusObserverModel = model; } private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() { /** Callback invoked with the sync adapter status changes. */ @Override public void onStatusChanged(int which) { boolean refreshing = false; switch (which) { case ContentResolver.SYNC_OBSERVER_TYPE_PENDING: case ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE: refreshing = true; } final boolean finalRefreshing = refreshing; getActivity().runOnUiThread(new Runnable() { @Override public void run() { mSyncStatusObserverListener.onStatusChange(finalRefreshing); } }); } }; // Swipe refresh view public void setHasSwipeRefreshView(View parent, int resource_id, SwipeRefreshLayout.OnRefreshListener listener) { mSwipeRefresh = (SwipeRefreshLayout) parent.findViewById(resource_id); mSwipeRefresh.setOnRefreshListener(listener); mSwipeRefresh.setColorSchemeResources(R.color.android_blue, R.color.android_green, R.color.android_orange_dark, R.color.android_red); } public void setSwipeRefreshing(boolean refreshing) { if (mSwipeRefresh != null) mSwipeRefresh.setRefreshing(refreshing); } public void hideRefreshingProgress() { if (mSwipeRefresh != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { mSwipeRefresh.setRefreshing(false); } }, 1000); } } public boolean inNetwork() { App app = (App) mContext.getApplicationContext(); return app.inNetwork(); } @Override public void onPause() { super.onPause(); if (mSyncObserverHandle != null) { ContentResolver.removeStatusChangeListener(mSyncObserverHandle); mSyncObserverHandle = null; } try { parent().unregisterReceiver(syncFinishReceiver); } catch (Exception e) { // Skipping issue related to unregister receiver } } @Override public void onDestroy() { super.onDestroy(); onNavSpinnerDestroy(); } protected void onNavSpinnerDestroy() { if (parent().getActionBarSpinner() != null) { parent().getActionBarSpinner().setAdapter(null); parent().setHasActionBarSpinner(false); } } @Override public void onResume() { super.onResume(); if (OUser.current(getActivity()) == null) return; if (mSyncStatusObserverListener != null) { mSyncStatusObserver.onStatusChanged(0); int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING | ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE; mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask, mSyncStatusObserver); } parent().registerReceiver(syncFinishReceiver, new IntentFilter(ISyncFinishReceiver.SYNC_FINISH)); } public void setHasSearchView(IOnSearchViewChangeListener listener, Menu menu, int menu_id) { mOnSearchViewChangeListener = listener; mSearchView = (SearchView) MenuItemCompat.getActionView(menu .findItem(menu_id)); if (mSearchView != null) { mSearchView.setOnCloseListener(closeListener); mSearchView.setOnQueryTextListener(searchViewQueryListener); mSearchView.setIconifiedByDefault(true); } } private SearchView.OnCloseListener closeListener = new SearchView.OnCloseListener() { @Override public boolean onClose() { // Restore the SearchView if a query was entered if (!TextUtils.isEmpty(mSearchView.getQuery())) { mSearchView.setQuery(null, true); } mOnSearchViewChangeListener.onSearchViewClose(); return true; } }; private SearchView.OnQueryTextListener searchViewQueryListener = new SearchView.OnQueryTextListener() { public boolean onQueryTextChange(String newText) { String newFilter = !TextUtils.isEmpty(newText) ? newText : null; return mOnSearchViewChangeListener .onSearchViewTextChange(newFilter); } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } }; public void setHasFloatingButton(View view, int res_id, ListView list, View.OnClickListener clickListener) { mFab = (FloatingActionButton) view.findViewById(res_id); if (mFab != null) { if (list != null) mFab.listenTo(list); mFab.setOnClickListener(clickListener); } } public void hideFab() { if (mFab != null) { mFab.setVisibility(View.GONE); } } public void showFab() { if (mFab != null) { mFab.setVisibility(View.VISIBLE); } } private ISyncFinishReceiver syncFinishReceiver = new ISyncFinishReceiver() { @Override public void onReceive(Context context, Intent intent) { hideRefreshingProgress(); if (mSyncStatusObserverListener != null) mSyncStatusObserverListener.onStatusChange(false); } }; public void startFragment(Fragment fragment, Boolean addToBackState, Bundle extra) { parent().loadFragment(fragment, addToBackState, extra); } public void startFragment(Fragment fragment, Boolean addToBackState) { startFragment(fragment, addToBackState, null); } } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/fragment/IBaseFragment.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 3:30 PM */ package com.odoo.core.support.addons.fragment; import android.content.Context; import com.odoo.core.support.drawer.ODrawerItem; import java.util.List; public interface IBaseFragment { public List drawerMenus(Context context); public Class database(); } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/fragment/IOnSearchViewChangeListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/1/15 6:30 PM */ package com.odoo.core.support.addons.fragment; public interface IOnSearchViewChangeListener { public static final String TAG = IOnSearchViewChangeListener.class.getSimpleName(); public boolean onSearchViewTextChange(String newFilter); public void onSearchViewClose(); } ================================================ FILE: app/src/main/java/com/odoo/core/support/addons/fragment/ISyncStatusObserverListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/1/15 12:23 PM */ package com.odoo.core.support.addons.fragment; public interface ISyncStatusObserverListener { public static final String TAG = ISyncStatusObserverListener.class.getSimpleName(); public void onStatusChange(Boolean refreshing); } ================================================ FILE: app/src/main/java/com/odoo/core/support/drawer/ODrawerItem.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 4:09 PM */ package com.odoo.core.support.drawer; import android.os.Bundle; import java.io.Serializable; import java.util.Locale; public class ODrawerItem implements Serializable { private static final long serialVersionUID = 1L; private String key = null, title = null; private String unique_key = null; private Integer counter = 0, icon = 0; private Object instance = null; private Boolean mGroupTitle = false; private Bundle mBundle = null; public ODrawerItem(String key) { this.key = key; } public ODrawerItem setTitle(String title) { this.title = title; unique_key = key.toLowerCase(Locale.getDefault()) + "_" + title.replaceAll(" ", "_") .toLowerCase(Locale.getDefault()); return this; } public String getTitle() { return title; } public ODrawerItem setIcon(int icon) { this.icon = icon; return this; } public Integer getIcon() { return icon; } public ODrawerItem setCounter(int counter) { this.counter = counter; return this; } public Integer getCounter() { return counter; } public ODrawerItem setInstance(Object instance) { this.instance = instance; return this; } public Object getInstance() { return instance; } public ODrawerItem setGroupTitle() { mGroupTitle = true; return this; } public Boolean isGroupTitle() { return mGroupTitle; } public ODrawerItem setExtra(Bundle bundle) { mBundle = bundle; return this; } public Bundle getExtra() { return mBundle; } public String getKey() { return unique_key; } } ================================================ FILE: app/src/main/java/com/odoo/core/support/list/IOnItemClickListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/1/15 3:09 PM */ package com.odoo.core.support.list; import android.view.View; public interface IOnItemClickListener { public void onItemDoubleClick(View view, int position); public void onItemClick(View view, int position); } ================================================ FILE: app/src/main/java/com/odoo/core/support/list/OCursorListAdapter.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/1/15 12:44 PM */ package com.odoo.core.support.list; import android.content.Context; import android.database.Cursor; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.CursorAdapter; import android.widget.SectionIndexer; import com.odoo.core.orm.ODataRow; import com.odoo.core.utils.logger.OLog; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import odoo.controls.OForm; public class OCursorListAdapter extends CursorAdapter implements AdapterView.OnItemClickListener, SectionIndexer { public static final String TAG = OCursorListAdapter.class.getSimpleName(); private Integer mLayout = null; private LayoutInflater mInflater = null; private OnViewCreateListener mOnViewCreateListener = null; private HashMap mViewClickListeners = new HashMap<>(); private HashMap mViewCache = new HashMap(); private Boolean mCacheViews = false; private OnViewBindListener mOnViewBindListener = null; private BeforeBindUpdateData mBeforeBindUpdateData = null; private Context mContext = null; private IOnItemClickListener mIOnItemClickListener = null; private AbsListView mListView = null; private Boolean hasIndexers = false; private String mIndexerColumn = null; private HashMap azIndexers = new HashMap<>(); private String[] sections = new String[0]; public OCursorListAdapter(Context context, Cursor c, int layout) { super(context, c, false); mLayout = layout; mInflater = LayoutInflater.from(context); mContext = context; } public OCursorListAdapter allowCacheView(Boolean cache) { mCacheViews = cache; return this; } public View getCachedView(Cursor cr) { int pos = cr.getPosition(); if (mViewCache.size() > pos) { return mViewCache.get("view_" + pos); } return null; } @Override public void bindView(View view, Context context, Cursor cursor) { final ODataRow row = new ODataRow(); for (String col : cursor.getColumnNames()) { row.put(col, getValue(cursor, col)); } if (mBeforeBindUpdateData != null) { row.addAll(mBeforeBindUpdateData.updateDataRow(cursor)); } if (view instanceof OForm) { OForm form = (OForm) view; form.initForm(row); } if (mOnViewBindListener != null) { mOnViewBindListener.onViewBind(view, cursor, row); } } @Override public View getView(final int position, View view, ViewGroup viewGroup) { Cursor cursor = (Cursor) getItem(position); view = getCachedView(cursor); if (mCacheViews && view != null) { return view; } view = newView(mContext, cursor, (ViewGroup) view); final View mView = view; for (final int id : mViewClickListeners.keySet()) { new Handler().postDelayed(new Runnable() { @Override public void run() { if (mView.findViewById(id) != null) { mView.findViewById(id).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { OnRowViewClickListener listener = mViewClickListeners .get(id); Cursor c = getCursor(); c.moveToPosition(position); listener.onRowViewClick(position, c, v, mView); } }); } else { OLog.log("View @id/" + mContext.getResources().getResourceEntryName( id) + " not found"); } } }, 100); } return super.getView(position, view, viewGroup); } private Object getValue(Cursor c, String column) { Object value = false; int index = c.getColumnIndex(column); switch (c.getType(index)) { case Cursor.FIELD_TYPE_NULL: value = false; break; case Cursor.FIELD_TYPE_BLOB: case Cursor.FIELD_TYPE_STRING: value = c.getString(index); break; case Cursor.FIELD_TYPE_FLOAT: value = c.getFloat(index); break; case Cursor.FIELD_TYPE_INTEGER: value = c.getInt(index); break; } return value; } @Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { View view; if (mCacheViews && getCachedView(cursor) != null) { view = getCachedView(cursor); if (!view.isDirty()) { return view; } } if (mOnViewCreateListener != null) { view = mOnViewCreateListener.onViewCreated(context, viewGroup, cursor, cursor.getPosition()); if (view == null) { view = mInflater.inflate(mLayout, viewGroup, false); } } else view = mInflater.inflate(mLayout, viewGroup, false); if (mCacheViews) { mViewCache.put("view_" + cursor.getPosition(), view); } return view; } @Override public void notifyDataSetChanged() { super.notifyDataSetChanged(); if (hasIndexers && mIndexerColumn != null) { Cursor cr = getCursor(); if (cr.getCount() > 0) { int pos = 0; if (cr.moveToFirst()) { List keys = new ArrayList<>(); do { int index = cr.getColumnIndex(mIndexerColumn); if (index > -1) { String colValue = cr.getString(index); azIndexers.put(colValue.substring(0, 1), pos); keys.add(colValue.substring(0, 1)); } pos++; } while (cr.moveToNext()); Collections.sort(keys); sections = keys.toArray(new String[keys.size()]); } } } } public View inflate(int resource, ViewGroup viewGroup) { return mInflater.inflate(resource, viewGroup, false); } public int getResource() { return mLayout; } public void setOnRowViewClickListener(int view_id, OnRowViewClickListener listener) { mViewClickListeners.put(view_id, listener); } public void setOnViewCreateListener(OnViewCreateListener viewCreateListener) { mOnViewCreateListener = viewCreateListener; } public void setOnViewBindListener(OnViewBindListener bindListener) { mOnViewBindListener = bindListener; } public void setBeforeBindUpdateData(BeforeBindUpdateData updater) { mBeforeBindUpdateData = updater; } public interface OnRowViewClickListener { public void onRowViewClick(int position, Cursor cursor, View view, View parent); } public interface OnViewBindListener { public void onViewBind(View view, Cursor cursor, ODataRow row); } public interface BeforeBindUpdateData { public ODataRow updateDataRow(Cursor cr); } public interface OnViewCreateListener { public View onViewCreated(Context context, ViewGroup view, Cursor cr, int position); } public void handleItemClickListener(AbsListView absListView, IOnItemClickListener listener) { mIOnItemClickListener = listener; mListView = absListView; if (mListView != null && mIOnItemClickListener != null) { mListView.setOnItemClickListener(this); } } private Boolean mDoubleClick = false; private Integer mDoubleClickItemIndex = -1; @Override public void onItemClick(AdapterView parent, final View view, final int position, long id) { if (mDoubleClick && mDoubleClickItemIndex == position) { mDoubleClick = false; mIOnItemClickListener.onItemDoubleClick(view, position); } else { mDoubleClick = true; mDoubleClickItemIndex = position; } new Handler().postDelayed(new Runnable() { @Override public void run() { if (mDoubleClick) { mDoubleClick = false; mDoubleClickItemIndex = -1; mIOnItemClickListener.onItemClick(view, position); } } }, 500); } public void setHasSectionIndexers(boolean hasSectionIndexers, String onColumn) { hasIndexers = hasSectionIndexers; mIndexerColumn = onColumn; } // Section Indexers @Override public Object[] getSections() { return sections; } @Override public int getPositionForSection(int sectionIndex) { return azIndexers.get(sections[sectionIndex]); } @Override public int getSectionForPosition(int position) { return azIndexers.get(sections[position]); } } ================================================ FILE: app/src/main/java/com/odoo/core/support/list/OListAdapter.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 6:07 PM */ package com.odoo.core.support.list; import android.content.Context; import android.text.TextUtils; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.Filter; import java.util.ArrayList; import java.util.List; public class OListAdapter extends ArrayAdapter { public static final String TAG = OListAdapter.class.getSimpleName(); private Context mContext = null; private List mObjects = null; private List mAllObjects = null; private RowFilter mFilter = null; private int mResourceId = 0; private RowFilterTextListener mRowFilterTextListener = null; private OnSearchChange mOnSearchChange = null; public OListAdapter(Context context, int resource, List objects) { super(context, resource, objects); Log.d(TAG, "OListAdapter->constructor()"); mContext = context; mObjects = new ArrayList<>(objects); mAllObjects = new ArrayList<>(objects); mResourceId = resource; } @Override public Filter getFilter() { if (mFilter == null) { mFilter = new RowFilter(); } return mFilter; } public int getResource() { return mResourceId; } public void replaceObjectAtPosition(int position, Object object) { mAllObjects.remove(position); mAllObjects.add(position, object); mObjects.remove(position); mObjects.add(position, object); } public void notifiyDataChange(List objects) { Log.d(TAG, "OListAdapter->notifiyDataChange()"); mAllObjects.clear(); mObjects.clear(); mAllObjects.addAll(objects); mObjects.addAll(objects); notifyDataSetChanged(); } class RowFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults result = new FilterResults(); if (!TextUtils.isEmpty(constraint)) { String searchingStr = constraint.toString().toLowerCase(); List filteredItems = new ArrayList(); for (Object item : mAllObjects) { String filterText = ""; if (mRowFilterTextListener != null) { filterText = mRowFilterTextListener.filterCompareWith( item).toLowerCase(); } else { filterText = item.toString().toLowerCase(); } if (filterText.contains(searchingStr)) { filteredItems.add(item); } } result.count = filteredItems.size(); result.values = filteredItems; } else { synchronized (this) { result.count = mAllObjects.size(); result.values = mAllObjects; } } return result; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { clear(); mObjects = (List) results.values; addAll(mObjects); notifyDataSetChanged(); if (mOnSearchChange != null) { mOnSearchChange.onSearchChange(mObjects); } } } public void setOnSearchChange(OnSearchChange callback) { mOnSearchChange = callback; } public void setRowFilterTextListener(RowFilterTextListener listener) { mRowFilterTextListener = listener; } public interface RowFilterTextListener { public String filterCompareWith(Object object); } public interface OnSearchChange { public void onSearchChange(List newRecords); } } ================================================ FILE: app/src/main/java/com/odoo/core/support/sync/SyncUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 8/1/15 11:39 AM */ package com.odoo.core.support.sync; import android.accounts.Account; import android.content.ContentResolver; import android.content.Context; import android.os.Bundle; import android.util.Log; import com.odoo.core.support.OUser; public class SyncUtils { public static final String TAG = SyncUtils.class.getSimpleName(); private Context mContext; private OUser mUser; public SyncUtils(Context context, OUser user) { mContext = context; mUser = (user != null) ? user : OUser.current(context); } public static SyncUtils get(Context context) { return new SyncUtils(context, null); } public static SyncUtils get(Context context, OUser user) { return new SyncUtils(context, user); } public void setAutoSync(String authority, boolean autoSync) { try { Account account = mUser.getAccount(); if (!ContentResolver.isSyncActive(account, authority)) { ContentResolver.setSyncAutomatically(account, authority, autoSync); } } catch (NullPointerException e) { Log.e(TAG, e.getMessage()); e.printStackTrace(); } } public void requestSync(String authority) { requestSync(authority, null); } public void requestSync(String authority, Bundle bundle) { Account account = mUser.getAccount(); Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); if (bundle != null) { settingsBundle.putAll(bundle); } ContentResolver.requestSync(account, authority, settingsBundle); } public void setSyncPeriodic(String authority, long interval_in_minute, long seconds_per_minute, long milliseconds_per_second) { Account account = mUser.getAccount(); Bundle extras = new Bundle(); this.setAutoSync(authority, true); ContentResolver.setIsSyncable(account, authority, 1); final long sync_interval = interval_in_minute * seconds_per_minute * milliseconds_per_second; ContentResolver.addPeriodicSync(account, authority, extras, sync_interval); } public void cancelSync(String authority) { Account account = mUser.getAccount(); ContentResolver.cancelSync(account, authority); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/BitmapUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 11:42 AM */ package com.odoo.core.utils; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.net.Uri; import android.text.TextPaint; import android.util.Base64; import com.odoo.R; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import odoo.controls.OControlHelper; public class BitmapUtils { public static final int THUMBNAIL_SIZE = 500; /** * Read bytes. * * @param uri the uri * @param resolver the resolver * @return the byte[] * @throws IOException Signals that an I/O exception has occurred. */ private static byte[] readBytes(Uri uri, ContentResolver resolver, boolean thumbnail) throws IOException { // this dynamically extends to take the bytes you read InputStream inputStream = resolver.openInputStream(uri); ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); if (!thumbnail) { // this is storage overwritten on each iteration with bytes int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; // we need to know how may bytes were read to write them to the // byteBuffer int len = 0; while ((len = inputStream.read(buffer)) != -1) { byteBuffer.write(buffer, 0, len); } } else { Bitmap imageBitmap = BitmapFactory.decodeStream(inputStream); int thumb_width = imageBitmap.getWidth() / 2; int thumb_height = imageBitmap.getHeight() / 2; if (thumb_width > THUMBNAIL_SIZE) { thumb_width = THUMBNAIL_SIZE; } if (thumb_width == THUMBNAIL_SIZE) { thumb_height = ((imageBitmap.getHeight() / 2) * THUMBNAIL_SIZE) / (imageBitmap.getWidth() / 2); } imageBitmap = Bitmap.createScaledBitmap(imageBitmap, thumb_width, thumb_height, false); imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteBuffer); } // and then we can return your byte array. return byteBuffer.toByteArray(); } public static String uriToBase64(Uri uri, ContentResolver resolver) { return uriToBase64(uri, resolver, false); } public static String uriToBase64(Uri uri, ContentResolver resolver, boolean thumbnail) { String encodedBase64 = ""; try { byte[] bytes = readBytes(uri, resolver, thumbnail); encodedBase64 = Base64.encodeToString(bytes, 0); } catch (IOException e1) { e1.printStackTrace(); } return encodedBase64; } /** * Gets the bitmap image. * * @param context the context * @param base64 the base64 * @return the bitmap image */ public static Bitmap getBitmapImage(Context context, String base64) { byte[] imageAsBytes = Base64.decode(base64.getBytes(), 5); return BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.length); } public static Bitmap getAlphabetImage(Context context, String content) { Resources res = context.getResources(); Bitmap mDefaultBitmap = BitmapFactory.decodeResource(res, android.R.drawable.sym_def_app_icon); int width = mDefaultBitmap.getWidth(); int height = mDefaultBitmap.getHeight(); TextPaint mPaint = new TextPaint(); mPaint.setTypeface(OControlHelper.boldFont()); mPaint.setColor(Color.WHITE); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setAntiAlias(true); int textSize = res.getDimensionPixelSize(R.dimen.text_size_xxlarge); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(); Rect mBounds = new Rect(); canvas.setBitmap(bitmap); canvas.drawColor(OStringColorUtil.getStringColor(context, content)); if (content != null) { char[] alphabet = {Character.toUpperCase(content.charAt(0))}; mPaint.setTextSize(textSize); mPaint.getTextBounds(alphabet, 0, 1, mBounds); canvas.drawText(alphabet, 0, 1, 0 + width / 2, 0 + height / 2 + (mBounds.bottom - mBounds.top) / 2, mPaint); } return bitmap; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/IntentUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 11:31 AM */ package com.odoo.core.utils; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; public class IntentUtils { public static void openURLInBrowser(Context context, String url) { if (!url.equals("false") && !url.equals("")) { if (!url.contains("http")) { url = "http://" + url; } Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); context.startActivity(intent); } } public static void startActivity(Context context, Class activity_class, Bundle data) { Intent intent = new Intent(context, activity_class); if (data != null) intent.putExtras(data); context.startActivity(intent); } public static void redirectToMap(Context context, String location) { if (!location.equals("false") && !location.equals("")) { String map = "geo:0,0?q=" + location; Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(map)); context.startActivity(intent); } } public static void requestMessage(Context context, String email) { if (!email.equals("false") && !email.equals("")) { Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setType("text/plain"); intent.setData(Uri.parse("mailto:" + email)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } } public static void requestCall(Context context, String number) { if (!number.equals("false") && !number.equals("")) { Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:" + number)); context.startActivity(intent); } } public static void startContactIntent(Context context, String number) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("tel:" + number)); context.startActivity(intent); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/JSONUtils.java ================================================ package com.odoo.core.utils; import android.text.TextUtils; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import org.json.JSONArray; import org.json.JSONObject; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class JSONUtils { public static List toList(JSONArray array) { List list = new ArrayList(); try { if (array != null) { for (int i = 0; i < array.length(); i++) { list.add((T) array.get(i)); } } } catch (Exception e) { e.printStackTrace(); } return list; } public static JSONArray toArray(List list) { JSONArray array = new JSONArray(); try { for (T obj : list) array.put(obj); } catch (Exception e) { e.printStackTrace(); } return array; } public static List toList(String list_data) { List list = new ArrayList<>(); try { list.addAll(JSONUtils.toList(new JSONArray(list_data))); } catch (Exception e) { e.printStackTrace(); } return list; } public static ODataRow toDataRow(JSONObject json) { ODataRow row = new ODataRow(); try { @SuppressWarnings("unchecked") Iterator keys = json.keys(); while (keys.hasNext()) { String key = keys.next(); row.put(key, json.get(key)); } } catch (Exception e) { e.printStackTrace(); } return row; } public static JSONObject toJSONObject(ODataRow row) { JSONObject json = new JSONObject(); try { for (String key : row.keys()) { json.put(key, row.get(key)); } } catch (Exception e) { e.printStackTrace(); } return json; } public static JSONObject createJSONValues(OModel model, ODataRow row) { JSONObject values = null; try { values = new JSONObject(); for (OColumn col : model.getColumns(false)) { if (col.getName().equals("id") && row.getInt("id") == 0) { /* FIXME: 7.0 not supporting Response from server : column "id" specified more than once */ continue; } if (col.getRelationType() == null) { if (!col.getName().equals("create_date") || !col.getName().equals("write_date")) { Object val = row.get(col.getName()); if (val == null || val.toString().equals("false") || TextUtils.isEmpty(val.toString())) { val = false; } values.put(col.getName(), val); } } else { // Relation columns switch (col.getRelationType()) { case ManyToOne: if (!row.getString(col.getName()).equals("false")) { ODataRow m2o = row.getM2ORecord(col.getName()).browse(); if (m2o != null) values.put(col.getName(), m2o.getInt("id")); } break; case OneToMany: JSONArray o2mRecords = new JSONArray(); List o2mRecordList = row.getO2MRecord( col.getName()).browseEach(); if (o2mRecordList.size() > 0) { JSONArray rec_ids = new JSONArray(); for (ODataRow o2mR : o2mRecordList) { if (o2mR.getInt("id") != 0) rec_ids.put(o2mR.getInt("id")); } o2mRecords.put(6); o2mRecords.put(false); o2mRecords.put(rec_ids); values.put(col.getName(), new JSONArray().put(o2mRecords)); } break; case ManyToMany: JSONArray m2mRecords = new JSONArray(); List m2mRecordList = row.getM2MRecord( col.getName()).browseEach(); if (!m2mRecordList.isEmpty()) { JSONArray rec_ids = new JSONArray(); for (ODataRow o2mR : m2mRecordList) { if (o2mR.getInt("id") != 0) rec_ids.put(o2mR.getInt("id")); } m2mRecords.put(6); m2mRecords.put(false); m2mRecords.put(rec_ids); values.put(col.getName(), new JSONArray().put(m2mRecords)); } break; } } } } catch (Exception e) { e.printStackTrace(); } return values; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OActionBarUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 2:36 PM */ package com.odoo.core.utils; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.support.v7.widget.Toolbar; import com.odoo.R; public class OActionBarUtils { public static void setActionBar(ActionBarActivity activity, Boolean withHomeButtonEnabled) { Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar); if (toolbar != null) { activity.setSupportActionBar(toolbar); ActionBar actionBar = activity.getSupportActionBar(); if (withHomeButtonEnabled) { actionBar.setHomeButtonEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true); } } } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OAlert.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 12/1/15 5:25 PM */ package com.odoo.core.utils; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.text.TextUtils; import android.util.TypedValue; import android.widget.EditText; import android.widget.LinearLayout; import com.odoo.R; public class OAlert { public static final String TAG = OAlert.class.getSimpleName(); private enum Type { Alert, Warning, Error } public static enum ConfirmType { POSITIVE, NEGATIVE } public static void showAlert(Context context, String message) { showAlert(context, message, null); } public static void showWarning(Context context, String message) { showWarning(context, message, null); } public static void showError(Context context, String message) { showError(context, message, null); } public static void showAlert(Context context, String message, OnAlertDismissListener listener) { show(context, message, Type.Alert, listener); } public static void showWarning(Context context, String message, OnAlertDismissListener listener) { show(context, message, Type.Warning, listener); } public static void showError(Context context, String message, OnAlertDismissListener listener) { show(context, message, Type.Error, listener); } private static void show(Context context, String message, Type type, final OnAlertDismissListener listener) { AlertDialog.Builder mBuilder; mBuilder = new AlertDialog.Builder(context); switch (type) { case Alert: mBuilder.setTitle(R.string.label_alert); break; case Error: mBuilder.setTitle(R.string.label_error); break; case Warning: mBuilder.setTitle(R.string.label_warning); } mBuilder.setMessage(message); mBuilder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); if (listener != null) { listener.onAlertDismiss(); } } }); mBuilder.create().show(); } public static void showConfirm(Context context, String message, final OnAlertConfirmListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("Confirm"); builder.setMessage(message); builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (listener != null) { listener.onConfirmChoiceSelect(ConfirmType.POSITIVE); } } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (listener != null) { listener.onConfirmChoiceSelect(ConfirmType.NEGATIVE); } } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (listener != null) { listener.onConfirmChoiceSelect(ConfirmType.NEGATIVE); } } }); builder.create().show(); } public static void inputDialog(Context context, String title, final OnUserInputListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(context); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); int margin = OResource.dimen(context, R.dimen.default_8dp); params.setMargins(margin, margin, margin, margin); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setLayoutParams(params); linearLayout.setPadding(margin, margin, margin, margin); final EditText edtInput = new EditText(context); edtInput.setLayoutParams(params); edtInput.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); if (listener != null) { listener.onViewCreated(edtInput); } linearLayout.addView(edtInput); builder.setView(linearLayout); if (title != null) builder.setTitle(title); builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (TextUtils.isEmpty(edtInput.getText())) { edtInput.setError("Field required"); edtInput.requestFocus(); } else { if (listener != null) { listener.onUserInputted(edtInput.getText()); } } } }); builder.setNegativeButton("Cancel", null); builder.create().show(); } public static interface OnAlertConfirmListener { public void onConfirmChoiceSelect(ConfirmType type); } public static interface OnAlertDismissListener { public void onAlertDismiss(); } public static interface OnUserInputListener { public void onViewCreated(EditText inputView); public void onUserInputted(Object value); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OAlertDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 6:36 PM */ package com.odoo.core.utils; import android.app.AlertDialog; import android.content.Context; import com.odoo.R; public class OAlertDialog { private Context mContext; private String title, message; private Boolean cancelable = true; public OAlertDialog(Context context) { mContext = context; } public OAlertDialog setTitle(String title) { this.title = title; return this; } public OAlertDialog setCancelable(Boolean cancelable) { this.cancelable = cancelable; return this; } public OAlertDialog setMessage(String message) { this.message = message; return this; } public void show() { AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle(title); builder.setMessage(message); builder.setNegativeButton(OResource.string(mContext, R.string.label_ok), null); builder.setCancelable(cancelable); builder.create().show(); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OControls.java ================================================ package com.odoo.core.utils; import android.graphics.Bitmap; import android.graphics.Paint; import android.view.View; import android.widget.ImageView; import android.widget.TextView; public class OControls { public static void setText(View parent_view, int textview_id, Object value) { TextView textView = (TextView) parent_view.findViewById(textview_id); if (value instanceof String || value instanceof CharSequence) textView.setText(value.toString()); if (value instanceof Integer) textView.setText(Integer.parseInt(value.toString())); } public static String getText(View parent_view, int textview_id) { TextView textView = (TextView) parent_view.findViewById(textview_id); return textView.getText().toString(); } public static void toggleViewVisibility(View parent_view, int view_id, Boolean visible) { int view_visibility = (visible) ? View.VISIBLE : View.GONE; parent_view.findViewById(view_id).setVisibility(view_visibility); } public static void setImage(View parent_view, int imageview_id, Bitmap bitmap) { ImageView imgView = (ImageView) parent_view.findViewById(imageview_id); imgView.setImageBitmap(bitmap); } public static void setImage(View parent_view, int imageview_id, int drawable_id) { ImageView imgView = (ImageView) parent_view.findViewById(imageview_id); imgView.setImageResource(drawable_id); } public static void setVisible(View parent_view, int resource_id) { View view = parent_view.findViewById(resource_id); view.setVisibility(View.VISIBLE); } public static void setInvisible(View parent_view, int resource_id) { parent_view.findViewById(resource_id).setVisibility(View.INVISIBLE); } public static void setGone(View parent_view, int resource_id) { View view = parent_view.findViewById(resource_id); view.setVisibility(View.GONE); } public static void setTextViewStrikeThrough(View parent, int res_id) { TextView tv = (TextView) parent.findViewById(res_id); tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } public static void setTextColor(View parent, int txv_id, int color) { TextView tv = (TextView) parent.findViewById(txv_id); tv.setTextColor(color); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OCursorUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/1/15 12:39 PM */ package com.odoo.core.utils; import android.database.Cursor; import com.odoo.core.orm.ODataRow; public class OCursorUtils { public static final String TAG = OCursorUtils.class.getSimpleName(); public static ODataRow toDatarow(Cursor cr) { ODataRow row = new ODataRow(); for (String col : cr.getColumnNames()) { row.put(col, OCursorUtils.cursorValue(col, cr)); } return row; } public static Object cursorValue(String column, Cursor cr) { Object value = false; int index = cr.getColumnIndex(column); switch (cr.getType(index)) { case Cursor.FIELD_TYPE_NULL: value = false; break; case Cursor.FIELD_TYPE_STRING: value = cr.getString(index); break; case Cursor.FIELD_TYPE_INTEGER: value = cr.getInt(index); break; case Cursor.FIELD_TYPE_FLOAT: value = cr.getFloat(index); break; case Cursor.FIELD_TYPE_BLOB: value = cr.getBlob(index); break; } return value; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/ODateUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 11:36 AM */ package com.odoo.core.utils; import android.util.Log; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import java.util.concurrent.TimeUnit; public class ODateUtils { public final static String TAG = ODateUtils.class.getSimpleName(); public static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; /** * Return Current date string in "yyyy-MM-dd HH:mm:ss" format * * @return current date string (Default timezone) */ public static String getDate() { return getDate(new Date(), DEFAULT_FORMAT); } /** * Returns current date string in given format * * @param format, date format * @return current date string (Default timezone) */ public static String getDate(String format) { return getDate(new Date(), format); } /** * Returns current date string in given format * * @param date, date object * @param defaultFormat, date format * @return current date string (default timezone) */ public static String getDate(Date date, String defaultFormat) { return createDate(date, defaultFormat, false); } /** * Returns UTC date string in "yyyy-MM-dd HH:mm:ss" format. * * @return string, UTC Date */ public static String getUTCDate() { return getUTCDate(new Date(), DEFAULT_FORMAT); } /** * Return UTC date in given format * * @param format, date format * @return UTC date string */ public static String getUTCDate(String format) { return getUTCDate(new Date(), format); } /** * Returns UTC Date string in given date format * * @param date, Date object * @param defaultFormat, Date pattern format * @return UTC date string */ public static String getUTCDate(Date date, String defaultFormat) { return createDate(date, defaultFormat, true); } /** * Convert UTC date to default timezone date * * @param date date in string * @param dateFormat default date format * @return string converted date string */ public static String convertToDefault(String date, String dateFormat) { return convertToDefault(date, dateFormat, dateFormat); } /** * Convert UTC date to default timezone * * @param date UTC date string * @param dateFormat default date format * @param toFormat converting date format * @return string converted date string */ public static String convertToDefault(String date, String dateFormat, String toFormat) { return createDate(createDateObject(date, dateFormat, false), toFormat, false); } /** * Convert to UTC date * * @param date date in string * @param dateFormat default date format * @return string date string in UTC timezone */ public static String convertToUTC(String date, String dateFormat) { return convertToUTC(date, dateFormat, dateFormat); } /** * Convert default timezone date to UTC timezone * * @param date, date in string * @param dateFormat default date format * @param toFormat display format * @return string, returns string converted to UTC */ public static String convertToUTC(String date, String dateFormat, String toFormat) { return createDate(createDateObject(date, dateFormat, true), toFormat, true); } public static String parseDate(String date, String dateFormat, String toFormat) { return createDate(createDateObject(date, dateFormat, false), toFormat, true); } /** * Create Date instance from given date string. * * @param date date in string * @param dateFormat, original date format * @param hasDefaultTimezone if date is in default timezone than true, otherwise false * @return Date, returns Date object with given date */ public static Date createDateObject(String date, String dateFormat, Boolean hasDefaultTimezone) { Date dateObj = null; try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); if (!hasDefaultTimezone) { simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } dateObj = simpleDateFormat.parse(date); } catch (Exception e) { Log.e(TAG, e.getMessage()); } return dateObj; } /** * Returns date before given days * * @param days days to before * @return string date string before days */ public static String getDateBefore(int days) { Date today = new Date(); Calendar cal = new GregorianCalendar(); cal.setTime(today); cal.add(Calendar.DAY_OF_MONTH, days * -1); Date date = cal.getTime(); SimpleDateFormat gmtFormat = new SimpleDateFormat(); gmtFormat.applyPattern("yyyy-MM-dd 00:00:00"); TimeZone gmtTime = TimeZone.getTimeZone("GMT"); gmtFormat.setTimeZone(gmtTime); return gmtFormat.format(date); } public static Date setDateTime(Date originalDate, int hour, int minute, int second) { Calendar cal = new GregorianCalendar(); cal.setTime(originalDate); cal.set(Calendar.HOUR, hour); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.SECOND, second); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } public static String getDateDayBeforeAfterUTC(String utcDate, int days) { Date dt = createDateObject(utcDate, DEFAULT_FORMAT, false); Calendar cal = new GregorianCalendar(); cal.setTime(dt); cal.add(Calendar.DAY_OF_MONTH, days); return createDate(cal.getTime(), DEFAULT_FORMAT, true); } public static Date getDateDayBefore(Date originalDate, int days) { Calendar cal = new GregorianCalendar(); cal.setTime(originalDate); cal.add(Calendar.DAY_OF_MONTH, days * -1); return cal.getTime(); } public static String getCurrentDateWithHour(int addHour) { Calendar cal = Calendar.getInstance(); int hour = cal.get(Calendar.HOUR); cal.set(Calendar.HOUR, hour + addHour); Date date = cal.getTime(); return ODateUtils.createDate(date, ODateUtils.DEFAULT_FORMAT, true); } public static Date getDateMinuteBefore(Date originalDate, int minutes) { Calendar cal = new GregorianCalendar(); cal.setTime(originalDate); cal.add(Calendar.MINUTE, minutes * -1); return cal.getTime(); } private static String createDate(Date date, String defaultFormat, Boolean utc) { SimpleDateFormat gmtFormat = new SimpleDateFormat(); gmtFormat.applyPattern(defaultFormat); TimeZone gmtTime = (utc) ? TimeZone.getTimeZone("GMT") : TimeZone.getDefault(); gmtFormat.setTimeZone(gmtTime); return gmtFormat.format(date); } public static String floatToDuration(String duration_in_float) { duration_in_float = String.format("%2.2f", Float.parseFloat(duration_in_float)); String[] parts = duration_in_float.split("\\."); long minute = Long.parseLong(parts[0]); long seconds = (60 * Long.parseLong(parts[1])) / 100; return String.format("%02d:%02d", minute, seconds); } public static String durationToFloat(String duration) { String[] parts = duration.split("\\:"); if (parts.length == 2) { long minute = Long.parseLong(parts[0]); long seconds = Long.parseLong(parts[1]); if (seconds == 60) { minute = minute + 1; seconds = 0; } else { seconds = (100 * seconds) / 60; } return String.format("%d.%d", minute, seconds); } return "false"; } public static String durationToFloat(long milliseconds) { long minute = TimeUnit.MILLISECONDS.toMinutes(milliseconds); long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) - TimeUnit.MINUTES.toSeconds(minute); if (seconds == 60) { minute = minute + 1; seconds = 0; } else { seconds = (100 * seconds) / 60; } return String.format("%d.%d", minute, seconds); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OFragmentUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 5:44 PM */ package com.odoo.core.utils; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBarActivity; import android.util.Log; import com.odoo.R; public class OFragmentUtils { public static final String TAG = OFragmentUtils.class.getSimpleName(); private ActionBarActivity mActivity; private Context mContext; private Bundle savedInstance = null; private FragmentManager fragmentManager; public OFragmentUtils(ActionBarActivity activity, Bundle savedInstance) { mActivity = activity; mContext = activity; fragmentManager = mActivity.getSupportFragmentManager(); } public static OFragmentUtils get(ActionBarActivity activity, Bundle savedInstance) { return new OFragmentUtils(activity, savedInstance); } public void startFragment(Fragment fragment, boolean addToBackState, Bundle extra) { Bundle extra_data = fragment.getArguments(); if (extra_data == null) extra_data = new Bundle(); if (extra != null) extra_data.putAll(extra); fragment.setArguments(extra_data); loadFragment(fragment, addToBackState); } private void loadFragment(Fragment fragment, Boolean addToBackState) { String tag = fragment.getClass().getCanonicalName(); if (fragmentManager.findFragmentByTag(tag) != null && savedInstance != null) { fragmentManager.popBackStack(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE); fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } if (savedInstance == null) { Log.i(TAG, "Fragment Loaded (" + tag + ")"); FragmentTransaction tran = fragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment, tag); if (addToBackState) tran.addToBackStack(tag); tran.commitAllowingStateLoss(); } } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OListUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 12:43 PM */ package com.odoo.core.utils; import java.util.ArrayList; import java.util.List; public class OListUtils { public static final String TAG = OListUtils.class.getSimpleName(); public static List toStringList(List list) { List items = new ArrayList<>(); for (Integer item : list) { items.add(item + ""); } return items; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OPreferenceManager.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 31/12/14 5:39 PM */ package com.odoo.core.utils; import android.content.Context; import android.content.SharedPreferences; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class OPreferenceManager { public static final String TAG = OPreferenceManager.class.getSimpleName(); private SharedPreferences mPref = null; public OPreferenceManager(Context context) { mPref = android.preference.PreferenceManager .getDefaultSharedPreferences(context); } public void putString(String key, String value) { SharedPreferences.Editor editor = mPref.edit(); editor.putString(key, value); editor.commit(); } public boolean putStringSet(String key, List values) { SharedPreferences.Editor editor = mPref.edit(); editor.putStringSet(key, new HashSet<>(values)); return editor.commit(); } public List getStringSet(String key) { List list = new ArrayList<>(); Set vals = mPref.getStringSet(key, null); if (vals != null) list.addAll(vals); return list; } public String getString(String key, String default_value) { return mPref.getString(key, default_value); } public int getInt(String key, int default_value) { return Integer.parseInt(mPref.getString(key, default_value + "")); } public void setBoolean(String key, Boolean value) { SharedPreferences.Editor editor = mPref.edit(); editor.putBoolean(key, value); editor.commit(); } public boolean getBoolean(String key, boolean defValue) { return mPref.getBoolean(key, defValue); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OResource.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 11:25 AM */ package com.odoo.core.utils; import android.content.Context; public class OResource { public static String string(Context context, int res_id) { return context.getResources().getString(res_id); } public static Integer dimen(Context context, int res_id) { return (int) context.getResources().getDimension(res_id); } public static int color(Context context, int res_id) { return context.getResources().getColor(res_id); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OStorageUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 15/1/15 5:09 PM */ package com.odoo.core.utils; import android.os.Environment; import java.io.File; public class OStorageUtils { public static final String TAG = OStorageUtils.class.getSimpleName(); public static String getDirectoryPath(String file_type) { File externalStorage = Environment.getExternalStorageDirectory(); String path = externalStorage.getAbsolutePath() + "/Odoo"; File baseDir = new File(path); if (!baseDir.isDirectory()) { baseDir.mkdir(); } if (file_type == null) { file_type = "file"; } if (file_type.contains("image")) { path += "/Images"; } else if (file_type.contains("audio")) { path += "/Audio"; } else { path += "/Files"; } File fileDir = new File(path); if (!fileDir.isDirectory()) { fileDir.mkdir(); } return path; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/OStringColorUtil.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 15/1/15 12:55 PM */ package com.odoo.core.utils; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import com.odoo.R; import java.util.Locale; public class OStringColorUtil { public static final String TAG = OStringColorUtil.class.getSimpleName(); public static int getStringColor(Context context, String content) { Resources res = context.getResources(); TypedArray mColors = res.obtainTypedArray(R.array.letter_tile_colors); int MAX_COLORS = mColors.length(); int firstCharAsc = content.toUpperCase(Locale.getDefault()).charAt(0); int index = (firstCharAsc % MAX_COLORS); if (index > MAX_COLORS - 1) { index = index / 2; } int color = mColors.getColor(index, Color.WHITE); mColors.recycle(); return color; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/StringUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 6/1/15 3:28 PM */ package com.odoo.core.utils; import android.text.Html; import android.text.Spanned; public class StringUtils { public static final String TAG = StringUtils.class.getSimpleName(); public static String repeat(String string, int repeat) { StringBuffer str = new StringBuffer(); for (int i = 0; i < repeat; i++) str.append(string); return str.toString(); } public static String capitalizeString(String string) { char[] chars = string.toLowerCase().toCharArray(); boolean found = false; for (int i = 0; i < chars.length; i++) { if (!found && Character.isLetter(chars[i])) { chars[i] = Character.toUpperCase(chars[i]); found = true; } else if (Character.isWhitespace(chars[i]) || chars[i] == '.' || chars[i] == '\'') { found = false; } } return String.valueOf(chars); } /** * Html to string. * * @param html the html * @return the string */ public static String htmlToString(String html) { return Html.fromHtml( html.replaceAll("\\<.*?\\>", "").replaceAll("\n", "") .replaceAll("\t", " ")).toString(); } /** * String to html. * * @param string the string * @return the spanned */ public static Spanned stringToHtml(String string) { return Html.fromHtml(string); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/controls/ExpandableHeightGridView.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * */ package com.odoo.core.utils.controls; import android.content.Context; import android.util.AttributeSet; import android.view.ViewGroup; import android.widget.GridView; public class ExpandableHeightGridView extends GridView { boolean expanded = false; public ExpandableHeightGridView(Context context) { super(context); } public ExpandableHeightGridView(Context context, AttributeSet attrs) { super(context, attrs); } public ExpandableHeightGridView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public boolean isExpanded() { return expanded; } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (isExpanded()) { int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); ViewGroup.LayoutParams params = getLayoutParams(); params.height = getMeasuredHeight(); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } public void setExpanded(boolean expanded) { this.expanded = expanded; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/dialog/OChoiceDialog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 6/2/15 2:41 PM */ package com.odoo.core.utils.dialog; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import java.util.ArrayList; import java.util.List; public class OChoiceDialog implements DialogInterface.OnClickListener { public static final String TAG = OChoiceDialog.class.getSimpleName(); private Context mContext; private AlertDialog.Builder mBuilder; private List options = new ArrayList<>(); private OnChoiceSelectListener mOnChoiceSelectListener; private int defaultSelected = -1; private String title = null; public OChoiceDialog(Context context) { mContext = context; mBuilder = new AlertDialog.Builder(mContext); } public static OChoiceDialog get(Context context) { return new OChoiceDialog(context); } public OChoiceDialog withTitle(String title) { this.title = title; return this; } public OChoiceDialog withOptions(List options, int selected) { this.options = options; defaultSelected = selected; return this; } public void show(OnChoiceSelectListener listener) { mOnChoiceSelectListener = listener; if (title != null) { mBuilder.setTitle(title); } mBuilder.setSingleChoiceItems(options.toArray(new String[options.size()]), defaultSelected, this); mBuilder.show(); } @Override public void onClick(DialogInterface dialog, int which) { if (mOnChoiceSelectListener != null) { mOnChoiceSelectListener.choiceSelected(which, options.get(which)); } dialog.dismiss(); } public interface OnChoiceSelectListener { public void choiceSelected(int position, String value); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/drawer/DrawerUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 30/12/14 4:27 PM */ package com.odoo.core.utils.drawer; import android.content.Context; import android.graphics.Typeface; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.SettingsActivity; import com.odoo.config.Addons; import com.odoo.core.account.Profile; import com.odoo.core.support.OUser; import com.odoo.core.support.addons.OAddon; import com.odoo.core.support.addons.fragment.IBaseFragment; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import com.odoo.R; import com.odoo.news.News; import com.odoo.news.models.OdooNews; import java.util.ArrayList; import java.util.List; import odoo.zxing.handler.OdooMobileQRReader; public class DrawerUtils { public static List getDrawerItems(Context context) { List items = new ArrayList<>(); for (OAddon addon : new Addons().getAddons()) { IBaseFragment frag = (IBaseFragment) addon.get(); if (frag != null) { List menus = frag.drawerMenus(context); if (menus != null) { items.addAll(menus); } } } items.addAll(DrawerUtils.baseSettingsItems(context)); return items; } public static List baseSettingsItems(Context context) { String key = "base.settings"; OPreferenceManager pref = new OPreferenceManager(context); List settings = new ArrayList<>(); settings.add(new ODrawerItem(key).setTitle(OResource.string(context, R.string.label_settings)) .setGroupTitle()); settings.add(new ODrawerItem(key).setTitle(OResource.string(context, R.string.title_profile)) .setInstance(Profile.class).setIcon(R.drawable.ic_action_user)); settings.add(new ODrawerItem(key).setTitle(OResource.string(context, R.string.label_settings)) .setIcon(R.drawable.ic_action_settings) .setInstance(SettingsActivity.class)); if (pref.getBoolean(Profile.CONNECT_WITH_ODOO, false)) settings.add(new ODrawerItem(key).setTitle(OResource.string(context, R.string.label_access_odoo_mobile)) .setInstance(OdooMobileQRReader.class).setIcon(R.drawable.ic_action_qrcode) .setExtra(OUser.current(context).getAsBundle())); OdooNews news = new OdooNews(context, null); if (!news.isEmptyTable()) { settings.add(new ODrawerItem(key).setTitle("Odoo News") .setInstance(new News()).setIcon(R.drawable.ic_odoo_o) ); } return settings; } public static View fillDrawerItemValue(View view, ODrawerItem item) { if (item.isGroupTitle()) { OControls.setText(view, R.id.group_title, item.getTitle()); } else { if (item.getIcon() > 0) OControls.setImage(view, R.id.icon, item.getIcon()); else view.findViewById(R.id.icon).setVisibility(View.GONE); OControls.setText(view, R.id.title, item.getTitle()); if (item.getCounter() > 0) { OControls.setText(view, R.id.counter, item.getCounter() + ""); } } return view; } public static IBaseFragment getDefaultDrawerFragment() { OAddon addon = new Addons().getDefaultAddon(); if (addon != null) { return (IBaseFragment) addon.get(); } return null; } public static ODrawerItem getStartableObject(Context context, IBaseFragment fragment) { List items = fragment.drawerMenus(context); if (items != null) { for (ODrawerItem item : items) { if (!item.isGroupTitle() && item.getInstance() != null) { return item; } } } return null; } public static void focusOnView(Context context, View view, boolean focused) { ODrawerItem item = (ODrawerItem) view.getTag(); if (!item.isGroupTitle()) { ImageView icon = (ImageView) view.findViewById(R.id.icon); TextView title = (TextView) view.findViewById(R.id.title); TextView counter = (TextView) view.findViewById(R.id.counter); if (focused) { icon.setColorFilter(context.getResources().getColor(R.color.drawer_icon_tint_selected)); title.setTextColor(context.getResources().getColor(R.color.drawer_text_color_selected)); title.setTypeface(title.getTypeface(), Typeface.BOLD); counter.setTextColor(context.getResources().getColor(R.color.drawer_text_color_selected)); counter.setTypeface(title.getTypeface(), Typeface.BOLD); } else { icon.setColorFilter(context.getResources().getColor(R.color.drawer_icon_tint)); title.setTextColor(context.getResources().getColor(R.color.drawer_text_color)); title.setTypeface(null, Typeface.NORMAL); counter.setTextColor(context.getResources().getColor(R.color.drawer_text_color)); counter.setTypeface(null, Typeface.NORMAL); } } } public static Integer findItemIndex(ODrawerItem item, LinearLayout itemContainer) { for (int i = 0; i < itemContainer.getChildCount(); i++) { ODrawerItem dItem = (ODrawerItem) itemContainer.getChildAt(i).getTag(); if (dItem != null && dItem.getKey().equals(item.getKey())) { return i; } } return -1; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/drawer/ODrawerScrollView.java ================================================ package com.odoo.core.utils.drawer; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.widget.ScrollView; import com.odoo.R; public class ODrawerScrollView extends ScrollView { private Drawable mInsetForeground; private Rect mInsets; private Rect mTempRect = new Rect(); private OnInsetsCallback mOnInsetsCallback; public ODrawerScrollView(Context context) { super(context); init(context, null, 0); } public ODrawerScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0); } public ODrawerScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ODrawerScrollView, defStyle, 0); if (a == null) { return; } mInsetForeground = a .getDrawable(R.styleable.ODrawerScrollView_insetForeground); a.recycle(); setWillNotDraw(true); } @Override protected boolean fitSystemWindows(Rect insets) { mInsets = new Rect(insets); setWillNotDraw(mInsetForeground == null); ViewCompat.postInvalidateOnAnimation(this); if (mOnInsetsCallback != null) { mOnInsetsCallback.onInsetsChanged(insets); } return true; // consume insets } @Override public void draw(Canvas canvas) { super.draw(canvas); int width = getWidth(); int height = getHeight(); if (mInsets != null && mInsetForeground != null) { int sc = canvas.save(); canvas.translate(getScrollX(), getScrollY()); // Top mTempRect.set(0, 0, width, mInsets.top); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Bottom mTempRect.set(0, height - mInsets.bottom, width, height); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Left mTempRect .set(0, mInsets.top, mInsets.left, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); // Right mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom); mInsetForeground.setBounds(mTempRect); mInsetForeground.draw(canvas); canvas.restoreToCount(sc); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(this); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mInsetForeground != null) { mInsetForeground.setCallback(null); } } /** * Allows the calling container to specify a callback for custom processing * when insets change (i.e. when {@link #fitSystemWindows(android.graphics.Rect)} is called. * This is useful for setting padding on UI elements based on UI chrome * insets (e.g. a Google Map or a ListView). When using with ListView or * GridView, remember to set clipToPadding to false. */ public void setOnInsetsCallback(OnInsetsCallback onInsetsCallback) { mOnInsetsCallback = onInsetsCallback; } public static interface OnInsetsCallback { public void onInsetsChanged(Rect insets); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/logger/OLog.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * */ package com.odoo.core.utils.logger; import android.text.TextUtils; import android.util.Log; public class OLog { public static void log(String... messages) { String message = TextUtils.join(", ", messages); final Throwable throwable = new Throwable(); final StackTraceElement[] elements = throwable.getStackTrace(); final String callerClassName = elements[1].getClassName(); final String callerMethodName = elements[1].getMethodName(); Log.e(callerClassName + "[" + callerMethodName + "] ", message); } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/notification/ONotificationBuilder.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 6:51 PM */ package com.odoo.core.utils.notification; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; import com.odoo.R; import com.odoo.core.account.BaseSettings; import com.odoo.core.utils.OPreferenceManager; import com.odoo.core.utils.OResource; import java.util.ArrayList; import java.util.List; public class ONotificationBuilder { public static final String TAG = ONotificationBuilder.class.getSimpleName(); private Context mContext; private Builder mNotificationBuilder = null; private PendingIntent mNotificationResultIntent = null; private NotificationManager mNotificationManager = null; private String title, text, bigText; private boolean mOnGoing = false, mAutoCancel = true; private Intent resultIntent = null; private int icon = R.drawable.ic_odoo_o_white; private int small_icon = R.drawable.ic_odoo_o_white; private List mActions = new ArrayList(); private int notification_id = 0; private Boolean withVibrate = true; private Boolean withLargeIcon = true; private Boolean withRingTone = true; private int notification_color = R.color.theme_secondary; private OPreferenceManager mPref; private int maxProgress = -1; private int currentProgress = -1; private boolean indeterminate = false; private Bitmap bigPictureStyle = null; public ONotificationBuilder(Context context, int notification_id) { mContext = context; mPref = new OPreferenceManager(mContext); this.notification_id = notification_id; } public ONotificationBuilder setTitle(String title) { this.title = title; return this; } public ONotificationBuilder setText(String text) { this.text = text; return this; } public ONotificationBuilder setIcon(int res_id) { icon = res_id; return this; } public ONotificationBuilder withLargeIcon(boolean largeIcon) { withLargeIcon = largeIcon; return this; } public boolean withLargeIcon() { return withLargeIcon; } public ONotificationBuilder withRingTone(Boolean ringTone) { withRingTone = ringTone; return this; } public boolean withRingTone() { return withRingTone; } public ONotificationBuilder setBigPicture(Bitmap bitmap) { bigPictureStyle = bitmap; return this; } public ONotificationBuilder setBigText(String bigText) { this.bigText = bigText; return this; } public ONotificationBuilder setOngoing(boolean onGoing) { mOnGoing = onGoing; return this; } public ONotificationBuilder setAutoCancel(boolean autoCancel) { mAutoCancel = autoCancel; return this; } public ONotificationBuilder addAction(NotificationAction action) { mActions.add(action); return this; } public ONotificationBuilder allowVibrate(Boolean vibrate) { withVibrate = vibrate; return this; } public ONotificationBuilder setColor(int res_id) { notification_color = res_id; return this; } public ONotificationBuilder setProgress(int max, int progress, boolean indeterminate) { maxProgress = max; currentProgress = progress; this.indeterminate = indeterminate; return this; } private void init() { mNotificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); mNotificationBuilder = new NotificationCompat.Builder(mContext); mNotificationBuilder.setContentTitle(title); mNotificationBuilder.setContentText(text); if (bigText == null) mNotificationBuilder.setContentInfo(text); if (withLargeIcon()) { mNotificationBuilder.setSmallIcon(small_icon); Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(), this.icon); Bitmap newIcon = Bitmap.createBitmap(icon.getWidth(), icon.getHeight(), icon.getConfig()); Canvas canvas = new Canvas(newIcon); canvas.drawColor(OResource.color(mContext, R.color.theme_primary)); canvas.drawBitmap(icon, 0, 0, null); mNotificationBuilder.setLargeIcon(newIcon); } else { mNotificationBuilder.setSmallIcon(icon); } mNotificationBuilder.setAutoCancel(mAutoCancel); mNotificationBuilder.setOngoing(mOnGoing); mNotificationBuilder.setColor(OResource.color(mContext, notification_color)); if (bigText != null) { NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle(); notiStyle.setBigContentTitle(title); notiStyle.setSummaryText(text); notiStyle.bigText(bigText); mNotificationBuilder.setStyle(notiStyle); } if (bigPictureStyle != null) { mNotificationBuilder.setStyle(new NotificationCompat.BigPictureStyle() .bigPicture(bigPictureStyle)); } if (maxProgress != -1) { mNotificationBuilder.setProgress(maxProgress, currentProgress, indeterminate); } } private void setSoundForNotification() { Uri uri = BaseSettings.getNotificationRingTone(mContext); mNotificationBuilder.setSound(uri); } private void setVibrateForNotification() { mNotificationBuilder.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000}); } public ONotificationBuilder setResultIntent(Intent intent) { resultIntent = intent; return this; } public ONotificationBuilder build() { init(); if (withVibrate) { setVibrateForNotification(); } if (withRingTone()) setSoundForNotification(); if (resultIntent != null) { _setResultIntent(); } if (mActions.size() > 0) { _addActions(); } return this; } private void _addActions() { for (NotificationAction action : mActions) { Intent intent = new Intent(mContext, action.getIntent()); intent.setAction(action.getAction()); intent.putExtras(action.getExtras()); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mNotificationBuilder.addAction(action.getIcon(), action.getTitle(), pendingIntent); } } private void _setResultIntent() { mNotificationResultIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); mNotificationBuilder.setDefaults(Notification.DEFAULT_ALL); if (mPref.getBoolean("hands_up_notification", true)) { mNotificationBuilder.setFullScreenIntent(mNotificationResultIntent, true); } else { mNotificationBuilder.setContentIntent(mNotificationResultIntent); } } public void show() { if (mNotificationBuilder == null) { build(); } mNotificationManager.notify(notification_id, mNotificationBuilder.build()); } public static void cancelNotification(Context context, int id) { NotificationManager nMgr = (NotificationManager) context.getSystemService( Context.NOTIFICATION_SERVICE ); nMgr.cancel(id); } public static class NotificationAction { private int icon; private int requestCode; private String title; private String action; private Bundle extras; private Class intent; public NotificationAction(int icon, String title, int requestCode, String action, Class intent, Bundle extras) { super(); this.icon = icon; this.title = title; this.requestCode = requestCode; this.action = action; this.intent = intent; this.extras = extras; } public Class getIntent() { return intent; } public void setIntent(Class intent) { this.intent = intent; } public int getIcon() { return icon; } public void setIcon(int icon) { this.icon = icon; } public int getRequestCode() { return requestCode; } public void setRequestCode(int requestCode) { this.requestCode = requestCode; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public Bundle getExtras() { return extras; } public void setExtras(Bundle extras) { this.extras = extras; } } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/reminder/ReminderActionReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 6:15 PM */ package com.odoo.core.utils.reminder; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class ReminderActionReceiver extends BroadcastReceiver { public static final String TAG = ReminderActionReceiver.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/reminder/ReminderReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 6:15 PM */ package com.odoo.core.utils.reminder; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import com.odoo.addons.calendar.EventDetail; import com.odoo.addons.calendar.models.CalendarEvent; import com.odoo.addons.crm.CRMDetail; import com.odoo.addons.crm.models.CRMLead; import com.odoo.addons.phonecall.PhoneCallDetail; import com.odoo.addons.phonecall.models.CRMPhoneCalls; import com.odoo.base.addons.res.ResCurrency; import com.odoo.base.addons.res.ResPartner; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.OResource; import com.odoo.core.utils.notification.ONotificationBuilder; import com.odoo.R; public class ReminderReceiver extends BroadcastReceiver { public static final String TAG = ReminderReceiver.class.getSimpleName(); public static final int REQUEST_EVENT_REMINDER = 12345; public static final int REQUEST_PHONE_CALL_REMINDER = 12346; public static final String ACTION_EVENT_REMINDER_DONE = "action_event_reminder_done"; public static final String ACTION_EVENT_REMINDER_RE_SCHEDULE = "action_event_reminder_re_schedule"; public static final String ACTION_PHONE_CALL_REMINDER_CALLBACK = "action_phone_call_reminder_callback"; public static final String ACTION_PHONE_CALL_REMINDER_DONE = "action_phone_call_reminder_done"; public static final String ACTION_PHONE_CALL_REMINDER_RE_SCHEDULE = "action_phone_call_reminder_re_schedule"; @Override public void onReceive(Context context, Intent intent) { String type = intent.getStringExtra(ReminderUtils.KEY_REMINDER_TYPE); showNotification(context, type, intent.getExtras()); } private void showNotification(Context context, String type, Bundle data) { ONotificationBuilder builder = new ONotificationBuilder(context, data.getInt(OColumn.ROW_ID)); Class resultClass = null; int icon = R.drawable.ic_action_event; ODataRow record = null; data.putString("type", type); if (type.equals("event")) { resultClass = EventDetail.class; CalendarEvent event = new CalendarEvent(context, null); int row_id = data.getInt(OColumn.ROW_ID); record = event.browse(new String[]{"name", "description", "location"}, row_id); if (record != null) { if (record.getString("description").equals("false")) { record.put("description", record.getString("name")); } } ONotificationBuilder.NotificationAction actionDone = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_action_done_all, OResource.string(context, R.string.label_mark_done), REQUEST_EVENT_REMINDER, ACTION_EVENT_REMINDER_DONE, EventDetail.class, data ); builder.addAction(actionDone); Bundle reScheduleData = data; reScheduleData.putBoolean(EventDetail.KEY_RESCHEDULE, true); ONotificationBuilder.NotificationAction actionReSchedule = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_time_clock, OResource.string(context, R.string.label_re_schedule), REQUEST_EVENT_REMINDER, ACTION_EVENT_REMINDER_RE_SCHEDULE, EventDetail.class, reScheduleData ); builder.addAction(actionReSchedule); } if (type.equals("phonecall")) { icon = R.drawable.ic_action_call_logs; resultClass = PhoneCallDetail.class; CRMPhoneCalls phoneCalls = new CRMPhoneCalls(context, null); int row_id = data.getInt(OColumn.ROW_ID); record = phoneCalls.browse(new String[]{"name", "description", "partner_id"}, row_id); if (record != null) { if (record.getString("description").equals("false")) { record.put("description", record.getString("name")); } ResPartner partner = new ResPartner(context, null); data.putString("contact", partner.getContact(context, record.getInt("partner_id"))); } ONotificationBuilder.NotificationAction actionCallBack = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_phone, "Call back", REQUEST_PHONE_CALL_REMINDER, ACTION_PHONE_CALL_REMINDER_CALLBACK, PhoneCallDetail.class, data ); builder.addAction(actionCallBack); ONotificationBuilder.NotificationAction actionDone = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_action_done_all, OResource.string(context, R.string.label_mark_done), REQUEST_PHONE_CALL_REMINDER, ACTION_PHONE_CALL_REMINDER_DONE, PhoneCallDetail.class, data ); builder.addAction(actionDone); ONotificationBuilder.NotificationAction actionReSchedule = new ONotificationBuilder.NotificationAction( R.drawable.ic_action_time_clock, OResource.string(context, R.string.label_re_schedule), REQUEST_PHONE_CALL_REMINDER, ACTION_PHONE_CALL_REMINDER_RE_SCHEDULE, PhoneCallDetail.class, data ); builder.addAction(actionReSchedule); } if (type.equals("opportunity")) { boolean reminderOnExpiryDate = data.getBoolean("expiry_date"); icon = R.drawable.ic_action_opportunities; resultClass = CRMDetail.class; CRMLead lead = new CRMLead(context, null); int row_id = data.getInt(OColumn.ROW_ID); record = lead.browse(row_id); String desc = record.getString("planned_revenue") + " " + ResCurrency.getSymbol(context, record.getInt("company_currency")) + " at " + record.getString("probability") + " %"; if (!record.getString("title_action").equals("false")) { desc += "\n" + record.getString("title_action"); } record.put("description", desc); //FIXME: Add reminder actions } if (record != null) { builder.setAutoCancel(true); builder.setIcon(icon); builder.setTitle(record.getString("name")); builder.setBigText(record.getString("description")); Intent resultIntent = new Intent(context, resultClass); resultIntent.putExtras(data); builder.setResultIntent(resultIntent); builder.build().show(); } } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/reminder/ReminderUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 6:12 PM */ package com.odoo.core.utils.reminder; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import com.odoo.core.orm.fields.OColumn; import java.util.Calendar; import java.util.Date; public class ReminderUtils { public static final String TAG = ReminderUtils.class.getSimpleName(); public static final String KEY_REMINDER_TYPE = "key_reminder_type"; private Context mContext; public ReminderUtils(Context context) { mContext = context; } public static ReminderUtils get(Context context) { return new ReminderUtils(context); } public boolean setReminder(Date date, Bundle extra) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.set(Calendar.SECOND, 0); Intent myIntent = new Intent(mContext, ReminderReceiver.class); myIntent.putExtras(extra); int row_id = extra.getInt(OColumn.ROW_ID); AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, row_id, myIntent, 0); alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTime().getTime(), pendingIntent); return true; } public boolean resetReminder(Date date, Bundle extra) { if (cancelReminder(date, extra)) { setReminder(date, extra); } return true; } public boolean cancelReminder(Date date, Bundle extra) { Intent myIntent = new Intent(mContext, ReminderReceiver.class); myIntent.putExtras(extra); int row_id = extra.getInt(OColumn.ROW_ID); AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, row_id, myIntent, PendingIntent.FLAG_CANCEL_CURRENT); alarmManager.cancel(pendingIntent); return true; } } ================================================ FILE: app/src/main/java/com/odoo/core/utils/sys/IOnActivityResultListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 1:00 PM */ package com.odoo.core.utils.sys; import android.content.Intent; public interface IOnActivityResultListener { public void onOdooActivityResult(int requestCode, int resultCode, Intent data); } ================================================ FILE: app/src/main/java/com/odoo/core/utils/sys/IOnBackPressListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 19/12/14 11:24 AM */ package com.odoo.core.utils.sys; public interface IOnBackPressListener { public boolean onBackPressed(); } ================================================ FILE: app/src/main/java/com/odoo/core/utils/sys/OCacheUtils.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 10/2/15 11:39 AM */ package com.odoo.core.utils.sys; import android.content.Context; import java.io.File; public class OCacheUtils { public static final String TAG = OCacheUtils.class.getSimpleName(); public static void clearSystemCache(Context context) { try { File dir = context.getCacheDir(); if (dir != null && dir.isDirectory()) { deleteDir(dir); } } catch (Exception e) { } } public static boolean deleteDir(File dir) { if (dir != null && dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } return dir.delete(); } } ================================================ FILE: app/src/main/java/com/odoo/datas/OConstants.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 18/12/14 11:28 AM */ package com.odoo.datas; public class OConstants { public static final String URL_ODOO = "https://www.odoo.com"; public static final String URL_ODOO_RESET_PASSWORD = URL_ODOO + "/web/reset_password"; public static final String URL_ODOO_SIGN_UP = URL_ODOO + "/web/signup"; public static final String URL_ODOO_ACCOUNTS = "https://accounts.odoo.com"; public static final String URL_ODOO_MOBILE_GIT_HUB = "https://github.com/Odoo-mobile"; public static final String URL_ODOO_APPS_ON_PLAY_STORE = "https://play.google.com/store/apps/developer?id=Odoo+SA"; public static final String ODOO_COMPANY_NAME = "Odoo"; } ================================================ FILE: app/src/main/java/com/odoo/news/News.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/3/15 3:35 PM */ package com.odoo.news; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import com.odoo.R; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.support.list.OCursorListAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OCursorUtils; import com.odoo.core.utils.StringUtils; import com.odoo.news.models.OdooNews; import java.util.List; import odoo.Odoo; public class News extends BaseFragment implements OCursorListAdapter. OnViewBindListener, LoaderManager.LoaderCallbacks, AdapterView.OnItemClickListener { public static final String TAG = News.class.getSimpleName(); private OdooNews news = null; private View mView = null; private ListView mList = null; private OCursorListAdapter mAdapter = null; private DataRefreshReceiver dataRefreshReceiver = null; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { news = new OdooNews(getActivity(), null); return inflater.inflate(R.layout.news_list, container, false); } @Override public List drawerMenus(Context context) { return null; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mView = view; dataRefreshReceiver = new DataRefreshReceiver(); initAdapter(); } private void initAdapter() { mList = (ListView) mView.findViewById(R.id.newsList); mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.odoo_news); mAdapter.setOnViewBindListener(this); mList.setAdapter(mAdapter); mList.setOnItemClickListener(this); getLoaderManager().initLoader(0, null, this); } @Override public Class database() { return OdooNews.class; } @Override public void onViewBind(View view, Cursor cursor, ODataRow row) { OControls.setText(view, R.id.subject, row.getString("subject")); OControls.setText(view, R.id.message, StringUtils.htmlToString(row.getString("message"))); } @Override public Loader onCreateLoader(int id, Bundle args) { return new CursorLoader(getActivity(), db().uri(), null, null, null, OColumn.ROW_ID + " desc"); } @Override public void onLoadFinished(Loader loader, Cursor data) { mAdapter.changeCursor(data); } @Override public void onLoaderReset(Loader loader) { mAdapter.changeCursor(null); } @Override public void onResume() { super.onResume(); parent().registerReceiver(dataRefreshReceiver, new IntentFilter(Odoo.ACTION_ODOO_UPDATES)); getLoaderManager().restartLoader(0, null, News.this); } @Override public void onPause() { super.onPause(); parent().unregisterReceiver(dataRefreshReceiver); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { NewsDetail nDetail = new NewsDetail(); ODataRow row = OCursorUtils.toDatarow((Cursor) mAdapter.getItem(position)); nDetail.setArguments(row.getPrimaryBundleData()); startFragment(nDetail, true); } class DataRefreshReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { getLoaderManager().restartLoader(0, null, News.this); } } } ================================================ FILE: app/src/main/java/com/odoo/news/NewsDetail.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/3/15 5:29 PM */ package com.odoo.news; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import com.odoo.R; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.addons.fragment.BaseFragment; import com.odoo.core.support.drawer.ODrawerItem; import com.odoo.core.utils.OControls; import com.odoo.news.models.OdooNews; import java.util.List; public class NewsDetail extends BaseFragment { public static final String TAG = NewsDetail.class.getSimpleName(); private int id = 0; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.news_detail, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); OdooNews news = new OdooNews(getActivity(), null); getArgument(); ODataRow row = news.browse(id); OControls.setText(view, R.id.detailSubject, row.getString("subject")); WebView wvMessage = (WebView) view.findViewById(R.id.detailMessage); wvMessage.loadData(row.getString("message"), "text/html;UTF-8", "UTF-8"); } private void getArgument() { Bundle b = getArguments(); id = b.getInt(OColumn.ROW_ID); } @Override public List drawerMenus(Context context) { return null; } @Override public Class database() { return null; } } ================================================ FILE: app/src/main/java/com/odoo/news/OdooNewsReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/3/15 2:29 PM */ package com.odoo.news; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.odoo.core.orm.OValues; import com.odoo.news.models.OdooNews; /** * Odoo News Receiver */ public class OdooNewsReceiver extends BroadcastReceiver { public static final String TAG = OdooNewsReceiver.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { // Use Keys: subject, from, message OdooNews news = new OdooNews(context, null); OValues values = new OValues(); values.put("sender", intent.getExtras().getString("from")); values.put("subject", intent.getExtras().getString("subject")); values.put("message", intent.getExtras().getString("message")); news.insert(values); } } ================================================ FILE: app/src/main/java/com/odoo/news/models/OdooNews.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 13/3/15 2:43 PM */ package com.odoo.news.models; import android.content.Context; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.core.support.OUser; public class OdooNews extends OModel { public static final String TAG = OdooNews.class.getSimpleName(); OColumn message = new OColumn("Message", OText.class); OColumn sender = new OColumn("sender", OVarchar.class); OColumn subject = new OColumn("Subject", OVarchar.class); public OdooNews(Context context, OUser user) { super(context, "odoo.news", user); } } ================================================ FILE: app/src/main/java/com/odoo/server/notifications/OdooServerNotificationReceiver.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 1/4/15 7:38 PM */ package com.odoo.server.notifications; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import com.odoo.core.utils.notification.ONotificationBuilder; import odoo.OdooServerNotification; public class OdooServerNotificationReceiver extends BroadcastReceiver { public static final String TAG = OdooServerNotificationReceiver.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { Bundle data = intent.getExtras(); int message_id = Integer.parseInt(data.getString(OdooServerNotification.KEY_MESSAGE_ID)); ONotificationBuilder builder = new ONotificationBuilder(context, message_id); builder.setTitle(data.getString(OdooServerNotification.KEY_MESSAGE_AUTHOR_NAME)); builder.setBigText(data.getString(OdooServerNotification.KEY_MESSAGE_BODY)); builder.build().show(); } } ================================================ FILE: app/src/main/java/odoo/controls/BezelImageView.java ================================================ /* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package odoo.controls; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.widget.ImageView; import com.odoo.R; /** * An {@link android.widget.ImageView} that draws its contents inside a mask and * draws a border drawable on top. This is useful for applying a beveled look to * image contents, but is also flexible enough for use with other desired * aesthetics. */ public class BezelImageView extends ImageView { private Paint mBlackPaint; private Paint mMaskedPaint; private Rect mBounds; private RectF mBoundsF; private Drawable mBorderDrawable; private Drawable mMaskDrawable; private ColorMatrixColorFilter mDesaturateColorFilter; private boolean mDesaturateOnPress = false; private boolean mCacheValid = false; private Bitmap mCacheBitmap; private int mCachedWidth; private int mCachedHeight; private Context mContext; public BezelImageView(Context context) { this(context, null); } public BezelImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BezelImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; // Attribute initialization final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BezelImageView, defStyle, 0); mMaskDrawable = a.getDrawable(R.styleable.BezelImageView_maskDrawable); if (mMaskDrawable != null) { mMaskDrawable.setCallback(this); } mBorderDrawable = a .getDrawable(R.styleable.BezelImageView_borderDrawable); if (mBorderDrawable != null) { mBorderDrawable.setCallback(this); } mDesaturateOnPress = a.getBoolean( R.styleable.BezelImageView_desaturateOnPress, mDesaturateOnPress); a.recycle(); otherInit(); } public void autoSetMaskDrawable() { mMaskDrawable = mContext.getResources().getDrawable( R.drawable.circle_mask); otherInit(); } private void otherInit() { // Other initialization mBlackPaint = new Paint(); mBlackPaint.setColor(0xff000000); mMaskedPaint = new Paint(); mMaskedPaint .setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // Always want a cache allocated. mCacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); if (mDesaturateOnPress) { // Create a desaturate color filter for pressed state. ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); mDesaturateColorFilter = new ColorMatrixColorFilter(cm); } } @Override protected boolean setFrame(int l, int t, int r, int b) { final boolean changed = super.setFrame(l, t, r, b); mBounds = new Rect(0, 0, r - l, b - t); mBoundsF = new RectF(mBounds); if (mBorderDrawable != null) { mBorderDrawable.setBounds(mBounds); } if (mMaskDrawable != null) { mMaskDrawable.setBounds(mBounds); } if (changed) { mCacheValid = false; } return changed; } @Override protected void onDraw(Canvas canvas) { if (mBounds == null) { return; } int width = mBounds.width(); int height = mBounds.height(); if (width == 0 || height == 0) { return; } if (!mCacheValid || width != mCachedWidth || height != mCachedHeight) { // Need to redraw the cache if (width == mCachedWidth && height == mCachedHeight) { // Have a correct-sized bitmap cache already allocated. Just // erase it. mCacheBitmap.eraseColor(0); } else { // Allocate a new bitmap with the correct dimensions. mCacheBitmap.recycle(); // noinspection AndroidLintDrawAllocation mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCachedWidth = width; mCachedHeight = height; } Canvas cacheCanvas = new Canvas(mCacheBitmap); if (mMaskDrawable != null) { int sc = cacheCanvas.save(); mMaskDrawable.draw(cacheCanvas); mMaskedPaint .setColorFilter((mDesaturateOnPress && isPressed()) ? mDesaturateColorFilter : null); cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else if (mDesaturateOnPress && isPressed()) { int sc = cacheCanvas.save(); cacheCanvas.drawRect(0, 0, mCachedWidth, mCachedHeight, mBlackPaint); mMaskedPaint.setColorFilter(mDesaturateColorFilter); cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else { super.onDraw(cacheCanvas); } if (mBorderDrawable != null) { mBorderDrawable.draw(cacheCanvas); } } // Draw from cache canvas.drawBitmap(mCacheBitmap, mBounds.left, mBounds.top, null); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mBorderDrawable != null && mBorderDrawable.isStateful()) { mBorderDrawable.setState(getDrawableState()); } if (mMaskDrawable != null && mMaskDrawable.isStateful()) { mMaskDrawable.setState(getDrawableState()); } if (isDuplicateParentStateEnabled()) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public void invalidateDrawable(Drawable who) { if (who == mBorderDrawable || who == mMaskDrawable) { invalidate(); } else { super.invalidateDrawable(who); } } @Override protected boolean verifyDrawable(Drawable who) { return who == mBorderDrawable || who == mMaskDrawable || super.verifyDrawable(who); } } ================================================ FILE: app/src/main/java/odoo/controls/DateTimePicker.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.TimePickerDialog; import android.content.Context; import android.content.DialogInterface; import com.odoo.core.utils.ODateUtils; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public class DateTimePicker { public static final String TAG = DateTimePicker.class.getSimpleName(); public enum Type { Date, DateTime, Time } private Context mContext = null; private Builder mBuilder; private DatePicker mDatePicker; private TimePicker mTimePicker; public DateTimePicker() { } public DateTimePicker(Context context, Builder builder) { mContext = context; mBuilder = builder; } public void show() { if (mBuilder.getType() == Type.Time) { mTimePicker = new TimePicker(mContext, mBuilder.getTime()); mTimePicker.setPickerCallback(callBack); mTimePicker.show(); } else { mDatePicker = new DatePicker(mContext, mBuilder.getDate()); mDatePicker.setPickerCallback(callBack); mDatePicker.show(); } } PickerCallBack callBack = new PickerCallBack() { @Override public void onTimePick(String time) { mBuilder.getCallBack().onTimePick(time); mTimePicker.dismiss(); } @Override public void onDatePick(String date) { mDatePicker.dismiss(); if (mBuilder.getType() == Type.DateTime) { mTimePicker = new TimePicker(mContext, mBuilder.getTime()); mTimePicker.setPickerCallback(callBack); mTimePicker.show(); } mBuilder.getCallBack().onDatePick(date); } }; public static class Builder { private Context mContext; private Type mType = Type.DateTime; private PickerCallBack mCallback; private String mDialogTitle = null; private String time = null; private String date = null; private String dateTime = null; public Builder(Context context) { mContext = context; } public Builder setDate(String date) { this.date = date; return this; } public Builder setTime(String time) { this.time = time; return this; } public Builder setDateTime(String dateTime) { if (dateTime != null) { date = ODateUtils.parseDate(dateTime, ODateUtils.DEFAULT_FORMAT, ODateUtils.DEFAULT_DATE_FORMAT); time = ODateUtils.parseDate(dateTime, ODateUtils.DEFAULT_FORMAT, ODateUtils.DEFAULT_TIME_FORMAT); } return this; } public Calendar getDate() { if (date != null) { Date dt = ODateUtils.createDateObject(date, ODateUtils.DEFAULT_DATE_FORMAT, false); Calendar cal = Calendar.getInstance(); cal.setTime(dt); return cal; } return null; } public Calendar getTime() { if (time != null) { Date dt = ODateUtils.createDateObject(time, ODateUtils.DEFAULT_TIME_FORMAT, false); Calendar cal = Calendar.getInstance(); cal.setTime(dt); return cal; } return null; } public Builder setType(Type type) { mType = type; return this; } public Type getType() { return mType; } public Builder setCallBack(PickerCallBack callback) { mCallback = callback; return this; } public PickerCallBack getCallBack() { return mCallback; } public Builder setTitle(String title) { mDialogTitle = title; return this; } public Builder setTitle(int res_id) { mDialogTitle = mContext.getResources().getString(res_id); return this; } public String getDialogTitle() { return mDialogTitle; } public DateTimePicker build() { DateTimePicker picker = new DateTimePicker(mContext, this); return picker; } } public class DatePicker implements DatePickerDialog.OnDateSetListener, DialogInterface.OnCancelListener { private PickerCallBack mCallback; private boolean called = false; private Dialog mDialog; public DatePicker(Context context, Calendar date) { final Calendar c = (date != null) ? date : Calendar.getInstance(); int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH); int day = c.get(Calendar.DAY_OF_MONTH); mDialog = new DatePickerDialog(context, this, year, month, day); mDialog.setOnCancelListener(this); } @Override public void onCancel(DialogInterface dialog) { } @Override public void onDateSet(android.widget.DatePicker view, int year, int monthOfYear, int dayOfMonth) { if (mCallback != null && !called) { Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, monthOfYear); cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); cal.set(Calendar.YEAR, year); Date now = cal.getTime(); String date = new SimpleDateFormat(ODateUtils.DEFAULT_DATE_FORMAT) .format(now); mCallback.onDatePick(date); called = true; } } public void setPickerCallback(PickerCallBack callback) { mCallback = callback; } public void show() { mDialog.show(); } public void dismiss() { mDialog.dismiss(); } } public class TimePicker implements TimePickerDialog.OnTimeSetListener, DialogInterface.OnCancelListener { private PickerCallBack mCallback; private TimePickerDialog mDialog = null; public TimePicker(Context context, Calendar time) { final Calendar c = (time != null) ? time : Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); mDialog = new TimePickerDialog(context, this, hour, minute, false); mDialog.setOnCancelListener(this); } @Override public void onTimeSet(android.widget.TimePicker view, int hourOfDay, int minute) { if (mCallback != null) { Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, hourOfDay); cal.set(Calendar.MINUTE, minute); cal.set(Calendar.MILLISECOND, 0); Date now = cal.getTime(); String time = new SimpleDateFormat(ODateUtils.DEFAULT_TIME_FORMAT) .format(now); mCallback.onTimePick(time); } } @Override public void onCancel(DialogInterface dialog) { } public void setPickerCallback(PickerCallBack callback) { mCallback = callback; } public void show() { mDialog.show(); } public void dismiss() { mDialog.dismiss(); } } public interface PickerCallBack { public void onDatePick(String date); public void onTimePick(String time); } } ================================================ FILE: app/src/main/java/odoo/controls/ExpandableListControl.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 3/2/15 2:08 PM */ package odoo.controls; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import java.util.ArrayList; import java.util.List; public class ExpandableListControl extends LinearLayout implements ExpandableListOperationListener { public static final String TAG = ExpandableListControl.class.getSimpleName(); private ExpandableListAdapter mAdapter; private Context context; public ExpandableListControl(Context context) { super(context); this.context = context; } public ExpandableListControl(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } public ExpandableListControl(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; } @Override public void onAdapterDataChange(List items) { removeAllViews(); for (int i = 0; i < items.size(); i++) { View view = mAdapter.getView(i, null, this); addView(view); } } public ExpandableListAdapter getAdapter(int resource, List objects, final ExpandableListAdapterGetViewListener listener) { mAdapter = new ExpandableListAdapter(context, resource, objects) { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(getResource(), parent, false); } if (listener != null) { return listener.getView(position, convertView, parent); } return convertView; } }; mAdapter.setOperationListener(this); return mAdapter; } public abstract static class ExpandableListAdapter { private List objects = new ArrayList<>(); private Context context; private int resource = android.R.layout.simple_list_item_1; private ExpandableListOperationListener listener; public ExpandableListAdapter(Context context, int resource, List objects) { this.context = context; this.objects = objects; this.resource = resource; } public abstract View getView(int position, View convertView, ViewGroup parent); public void notifyDataSetChanged(List items) { objects = items; listener.onAdapterDataChange(items); } public Object getItem(int position) { return objects.get(position); } public void setOperationListener(ExpandableListOperationListener listener) { this.listener = listener; } public int getResource() { return resource; } } public static interface ExpandableListAdapterGetViewListener { public View getView(int position, View view, ViewGroup parent); } } ================================================ FILE: app/src/main/java/odoo/controls/ExpandableListOperationListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 3/2/15 2:17 PM */ package odoo.controls; import java.util.List; public interface ExpandableListOperationListener { public void onAdapterDataChange(List items); } ================================================ FILE: app/src/main/java/odoo/controls/IOControlData.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:15 PM */ package odoo.controls; import android.view.View; import com.odoo.core.orm.fields.OColumn; public interface IOControlData { public static final String TAG = IOControlData.class.getSimpleName(); public void setValue(Object value); public Object getValue(); public void setEditable(Boolean editable); public Boolean isEditable(); public void setLabelText(String label); public void setColumn(OColumn column); public void initControl(); public String getLabel(); public void setValueUpdateListener(ValueUpdateListener listener); public static interface ValueUpdateListener { public void onValueUpdate(Object value); public void visibleControl(boolean isVisible); } public Boolean isControlReady(); public void resetData(); public View getFieldView(); public void setError(String error); } ================================================ FILE: app/src/main/java/odoo/controls/IOnChangeCallback.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:11 PM */ package odoo.controls; import com.odoo.core.orm.ODataRow; public interface IOnChangeCallback { public static final String TAG = IOnChangeCallback.class.getSimpleName(); public void onValueChange(ODataRow row); } ================================================ FILE: app/src/main/java/odoo/controls/IOnDomainFilterCallbacks.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:11 PM */ package odoo.controls; import com.odoo.core.orm.fields.OColumn; public interface IOnDomainFilterCallbacks { public static final String TAG = IOnDomainFilterCallbacks.class.getSimpleName(); public void onFieldValueChanged(OColumn.ColumnDomain domain); } ================================================ FILE: app/src/main/java/odoo/controls/IOnQuickRecordCreateListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 20/1/15 5:01 PM */ package odoo.controls; import com.odoo.core.orm.ODataRow; public interface IOnQuickRecordCreateListener { public void onRecordCreated(ODataRow row); } ================================================ FILE: app/src/main/java/odoo/controls/OBlobField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.BitmapUtils; public class OBlobField extends LinearLayout implements IOControlData { public static final String TAG = OBlobField.class.getSimpleName(); private Context mContext; private Boolean mReady = false, isEditable = false; private ValueUpdateListener mValueUpdateListener = null; private String mLabel; private OColumn mCol; private Object mValue; private BezelImageView imgView; private OField.WidgetType mWidget = OField.WidgetType.Image; private float imageSize = -1; private Integer defaultImage = -1; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OBlobField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OBlobField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OBlobField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public OBlobField(Context context) { super(context); init(context, null, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { } if (mContext.getClass().getSimpleName().contains("BridgeContext")) initControl(); } @Override public void initControl() { LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); if (imageSize > -1) { params = new LayoutParams((int) imageSize, (int) imageSize); } removeAllViews(); setOrientation(VERTICAL); imgView = new BezelImageView(mContext); imgView.setLayoutParams(params); switch (mWidget) { case ImageCircle: imgView.autoSetMaskDrawable(); case Image: break; } addView(imgView); } @Override public void setValue(Object value) { mValue = value; if (mValue != null && imgView != null) { if (!mValue.equals("false")) { imgView.setImageBitmap(BitmapUtils.getBitmapImage(mContext, mValue.toString())); } else if (defaultImage > -1) { imgView.setImageResource(defaultImage); } } } @Override public void setError(String error) { } @Override public View getFieldView() { return imgView; } @Override public Object getValue() { return mValue; } @Override public void setEditable(Boolean editable) { isEditable = editable; } @Override public Boolean isEditable() { return isEditable; } @Override public void setColumn(OColumn column) { mCol = column; } @Override public void setLabelText(String label) { mLabel = label; } @Override public String getLabel() { if (mLabel != null) return mLabel; if (mCol != null) return mCol.getLabel(); return "unknown"; } @Override public void setValueUpdateListener(ValueUpdateListener listener) { mValueUpdateListener = listener; } @Override public Boolean isControlReady() { return mReady; } @Override public void resetData() { } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mReady = true; } public void setWidgetType(OField.WidgetType type) { if (type != null) { mWidget = type; initControl(); } } public void setImageSize(float size) { imageSize = size; } public void setDefaultImage(int image) { defaultImage = image; } } ================================================ FILE: app/src/main/java/odoo/controls/OBooleanField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; import com.odoo.core.orm.fields.OColumn; public class OBooleanField extends LinearLayout implements IOControlData, CompoundButton.OnCheckedChangeListener { public static final String TAG = OBooleanField.class.getSimpleName(); private Context mContext; private OColumn mColumn; private Boolean mEditable = false; private String mLabel = null; private Boolean mValue = false; private OField.WidgetType mWidget = null; private ValueUpdateListener mValueUpdateListener = null; // Controls private TextView txvView = null; private CheckBox mCheckbox = null; private Switch mSwitch = null; private Boolean mReady = false; private float textSize = -1; private int appearance = -1; private int textColor = Color.BLACK; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OBooleanField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OBooleanField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OBooleanField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public OBooleanField(Context context) { super(context); init(context, null, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { } if (mContext.getClass().getSimpleName().contains("BridgeContext")) initControl(); } public void initControl() { mReady = false; LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); removeAllViews(); setOrientation(VERTICAL); if (isEditable()) { if (mWidget != null) { switch (mWidget) { case Switch: mSwitch = new Switch(mContext); mSwitch.setLayoutParams(params); mSwitch.setOnCheckedChangeListener(this); setValue(getValue()); if (mLabel != null) mSwitch.setText(mLabel); if (textSize > -1) { mSwitch.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { mSwitch.setTextAppearance(mContext, appearance); } mSwitch.setTextColor(textColor); addView(mSwitch); break; default: break; } } else { mCheckbox = new CheckBox(mContext); mCheckbox.setLayoutParams(params); mCheckbox.setOnCheckedChangeListener(this); if (mLabel != null) mCheckbox.setText(mLabel); if (textSize > -1) { mCheckbox.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { mCheckbox.setTextAppearance(mContext, appearance); } mCheckbox.setTextColor(textColor); addView(mCheckbox); } } else { txvView = new TextView(mContext); txvView.setLayoutParams(params); txvView.setText(getCheckBoxLabel()); if (mLabel != null) txvView.setText(mLabel); if (textSize > -1) { txvView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvView.setTextAppearance(mContext, appearance); } addView(txvView); } } @Override public void setValue(Object value) { if (value == null) return; mValue = Boolean.parseBoolean(value.toString()); if (isEditable()) { if (mWidget != null) { switch (mWidget) { case Switch: mSwitch.setChecked(Boolean.parseBoolean(getValue() .toString())); break; default: break; } } else { mCheckbox.setChecked(Boolean .parseBoolean(getValue().toString())); } } else { txvView.setText(getCheckBoxLabel()); } if (mValueUpdateListener != null) { mValueUpdateListener.onValueUpdate(value); if (!isEditable() && mValue == false) { mValueUpdateListener.visibleControl(false); } else { mValueUpdateListener.visibleControl(true); } } } @Override public View getFieldView() { if (isEditable()) { if (mWidget != null) { switch (mWidget) { case Switch: return mSwitch; } } return mCheckbox; } else { return txvView; } } @Override public void setError(String error) { if (error != null) Toast.makeText(mContext, error, Toast.LENGTH_LONG).show(); } @Override public Object getValue() { return mValue; } @Override public void setEditable(Boolean editable) { mEditable = editable; initControl(); } @Override public Boolean isEditable() { return mEditable; } public void setWidgetType(OField.WidgetType type) { mWidget = type; initControl(); } @Override public void setLabelText(String label) { mLabel = label; } private String getCheckBoxLabel() { String label = ""; if (getValue() != null && Boolean.parseBoolean(getValue().toString())) { label = "✔ "; } label += getLabel(); return label; } @Override public String getLabel() { if (mLabel != null) return mLabel; if (mColumn != null) return mColumn.getLabel(); return "unknown"; } @Override public void setColumn(OColumn column) { mColumn = column; if (mLabel == null && mColumn != null) mLabel = mColumn.getLabel(); } @Override public void setValueUpdateListener(ValueUpdateListener listener) { mValueUpdateListener = listener; } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { setValue(isChecked); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mReady = true; } @Override public Boolean isControlReady() { return mReady; } @Override public void resetData() { setValue(getValue()); } public void setResource(float textSize, int appearance, int color) { this.textSize = textSize; this.appearance = appearance; this.textColor = color; } } ================================================ FILE: app/src/main/java/odoo/controls/OControlHelper.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:46 PM */ package odoo.controls; import android.graphics.Typeface; public class OControlHelper { public static final String TAG = OControlHelper.class.getSimpleName(); public static Typeface boldFont() { return Typeface.create("sans-serif-condensed", 0); } public static Typeface lightFont() { return Typeface.create("sans-serif-light", 0); } } ================================================ FILE: app/src/main/java/odoo/controls/ODateTimeField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.ODateUtils; import java.util.Date; import static odoo.controls.OField.FieldType; public class ODateTimeField extends LinearLayout implements IOControlData, DateTimePicker.PickerCallBack { public static final String TAG = ODateTimeField.class.getSimpleName(); private Context mContext; private Boolean mEditable = false; private OColumn mColumn; private String mLabel, mHint; private ValueUpdateListener mValueUpdateListener = null; private FieldType mFieldType; private TextView txvText; private Object mValue; private String mParsePattern = ODateUtils.DEFAULT_DATE_FORMAT; private DateTimePicker.Builder builder = null; private String mDate; private Boolean mReady = false; private float textSize = -1; private int appearance = -1; private int textColor = Color.BLACK; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public ODateTimeField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public ODateTimeField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public ODateTimeField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public ODateTimeField(Context context) { super(context); init(context, null, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { } mReady = false; initControl(); } public void setFieldType(FieldType type) { mFieldType = type; if (mFieldType == FieldType.DateTime) { mParsePattern = ODateUtils.DEFAULT_FORMAT; } } @Override public void initControl() { LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); removeAllViews(); setOrientation(VERTICAL); txvText = new TextView(mContext); txvText.setLayoutParams(params); txvText.setOnClickListener(null); if (isEditable()) { txvText.setOnClickListener(onClick); } if (mValue != null && !mValue.toString().equals("false")) { txvText.setText(getDate(mValue.toString(), mParsePattern)); } if (textSize > -1) { txvText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvText.setTextAppearance(mContext, appearance); } txvText.setTextColor(textColor); addView(txvText); } @Override public void setValue(Object value) { mValue = value; if (value == null || value.toString().equals("false")) { txvText.setText("No Value"); return; } txvText.setText(getDate(mValue.toString(), mParsePattern)); if (mValueUpdateListener != null) { mValueUpdateListener.onValueUpdate(value); } } @Override public View getFieldView() { return null; } @Override public void setError(String error) { if (error != null) Toast.makeText(mContext, error, Toast.LENGTH_LONG).show(); } @Override public Object getValue() { if (mValue != null && !TextUtils.isEmpty(mValue.toString())) { if (mFieldType == FieldType.Date) return mValue.toString().replaceAll(" 00:00:00", ""); return mValue; } return null; } @Override public void setEditable(Boolean editable) { if (mEditable != editable) { mEditable = editable; } } @Override public Boolean isEditable() { return mEditable; } @Override public void setLabelText(String label) { mLabel = label; } @Override public void setColumn(OColumn column) { mColumn = column; } @Override public String getLabel() { if (mLabel != null) return mLabel; if (mColumn != null) return mColumn.getLabel(); if (mHint != null) return mHint; return "unknown"; } @Override public void setValueUpdateListener(ValueUpdateListener listener) { mValueUpdateListener = listener; } View.OnClickListener onClick = new OnClickListener() { @Override public void onClick(View v) { builder = new DateTimePicker.Builder(mContext); if (mFieldType == FieldType.Date) { if (getValue() != null && !getValue().toString().equals("false")) builder.setDate(getValue().toString()); builder.setType(DateTimePicker.Type.Date); } else if (mFieldType == FieldType.Time) { if (getValue() != null && !getValue().toString().equals("false")) builder.setTime(getValue().toString()); builder.setType(DateTimePicker.Type.Time); } else { if (getValue() != null && !getValue().toString().equals("false")) builder.setDateTime(getValue().toString()); builder.setType(DateTimePicker.Type.DateTime); } builder.setCallBack(ODateTimeField.this); builder.build().show(); } }; private String getDate(String date, String format) { if (date.contains("now()") || date.contains("NOW()")) { mValue = ODateUtils.getUTCDate((mFieldType == FieldType.Date) ? ODateUtils.DEFAULT_DATE_FORMAT : (mFieldType == FieldType.Time) ? ODateUtils.DEFAULT_TIME_FORMAT : ODateUtils.DEFAULT_FORMAT); return ODateUtils.getDate(format); } else { if (mFieldType == FieldType.Date) { date += " 00:00:00"; } String defaultFormat = ODateUtils.DEFAULT_FORMAT; if (mFieldType == FieldType.Time) { defaultFormat = ODateUtils.DEFAULT_TIME_FORMAT; } return ODateUtils.convertToDefault(date, defaultFormat, format); } } @Override public void onDatePick(String date) { mDate = date; if (mFieldType == FieldType.Date) { setValue(mDate + " 00:00:00"); } } @Override public void onTimePick(String time) { String date; String format; if (mFieldType == FieldType.Time) { date = time; format = ODateUtils.DEFAULT_TIME_FORMAT; } else { date = mDate + " " + time; format = ODateUtils.DEFAULT_FORMAT; } Date dt = ODateUtils.createDateObject(date, format, true); String utc_date = ODateUtils.getUTCDate(dt, format); setValue(utc_date); } public void setParsePattern(String parsePattern) { if (parsePattern != null) mParsePattern = parsePattern; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mReady = true; } @Override public Boolean isControlReady() { return mReady; } @Override public void resetData() { setValue(getValue()); } public void setResource(float textSize, int appearance, int color) { this.textSize = textSize; this.appearance = appearance; this.textColor = color; } } ================================================ FILE: app/src/main/java/odoo/controls/OEditTextField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.ODateUtils; public class OEditTextField extends LinearLayout implements IOControlData, View.OnFocusChangeListener { public static final String TAG = OEditTextField.class.getSimpleName(); private Context mContext; private EditText edtText; private TextView txvText; private Boolean mEditable = false, mReady = false; private OField.WidgetType mWidget = null; private OColumn mColumn; private String mLabel, mHint; private ValueUpdateListener mValueUpdateListener = null; private float textSize = -1; private int appearance = -1; private int textColor = Color.BLACK; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OEditTextField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OEditTextField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OEditTextField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public OEditTextField(Context context) { super(context); init(context, null, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { } mReady = false; if (mContext.getClass().getSimpleName().contains("BridgeContext")) initControl(); } public void initControl() { // Creating control LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); removeAllViews(); setOrientation(VERTICAL); if (mEditable) { edtText = new EditText(mContext); edtText.setTypeface(OControlHelper.lightFont()); edtText.setLayoutParams(params); edtText.setBackgroundColor(Color.TRANSPARENT); edtText.setPadding(0, 10, 10, 10); edtText.setHint(getLabel()); edtText.setOnFocusChangeListener(this); if (textSize > -1) { edtText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { edtText.setTextAppearance(mContext, appearance); } edtText.setTextColor(textColor); addView(edtText); } else { txvText = new TextView(mContext); txvText.setTypeface(OControlHelper.lightFont()); txvText.setLayoutParams(params); txvText.setBackgroundColor(Color.TRANSPARENT); txvText.setPadding(0, 10, 10, 10); if (textSize > -1) { txvText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvText.setTextAppearance(mContext, appearance); } txvText.setTextColor(textColor); addView(txvText); } } public void setWidgetType(OField.WidgetType type) { mWidget = type; initControl(); } @Override public void setValue(Object value) { if (value == null) return; if (value.toString().equals("false")) { value = ""; } else if (mWidget == OField.WidgetType.Duration) { value = ODateUtils.floatToDuration(value.toString()); } if (mEditable) { edtText.setText(value.toString()); } else { txvText.setText(value.toString()); } if (mValueUpdateListener != null) { mValueUpdateListener.onValueUpdate(value); } } @Override public View getFieldView() { if (mEditable) return edtText; return txvText; } @Override public void setError(String error) { if (mEditable) { edtText.setError(error); } } @Override public Object getValue() { Object value = null; if (mEditable) value = edtText.getText(); else if (txvText != null) value = txvText.getText(); if ((value != null || !value.toString().equals("false")) && mWidget == OField.WidgetType.Duration) { value = ODateUtils.durationToFloat(value.toString()); } return value; } @Override public void setEditable(Boolean editable) { if (mEditable != editable) { mEditable = editable; } } @Override public Boolean isEditable() { return mEditable; } public void setHint(String hint) { mHint = hint; } @Override public void setLabelText(String label) { mLabel = label; } @Override public void setColumn(OColumn column) { mColumn = column; } @Override public String getLabel() { if (mLabel != null) return mLabel; if (mColumn != null) return mColumn.getLabel(); if (mHint != null) return mHint; return "unknown"; } @Override public void setValueUpdateListener(ValueUpdateListener listener) { mValueUpdateListener = listener; } @Override public void onFocusChange(View v, boolean hasFocus) { if (!hasFocus && edtText.getText().length() > 0) { setValue(getValue()); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mReady = true; } @Override public Boolean isControlReady() { return mReady; } @Override public void resetData() { setValue(getValue()); } public void setResource(float textSize, int appearance, int color) { this.textSize = textSize; this.appearance = appearance; this.textColor = color; } } ================================================ FILE: app/src/main/java/odoo/controls/OField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OBlob; import com.odoo.core.orm.fields.types.OBoolean; import com.odoo.core.orm.fields.types.ODate; import com.odoo.core.orm.fields.types.ODateTime; import com.odoo.core.orm.fields.types.OFloat; import com.odoo.core.orm.fields.types.OHtml; import com.odoo.core.orm.fields.types.OInteger; import com.odoo.core.orm.fields.types.OSelection; import com.odoo.core.orm.fields.types.OText; import com.odoo.core.orm.fields.types.OTimestamp; import com.odoo.core.orm.fields.types.OVarchar; import com.odoo.R; public class OField extends LinearLayout implements IOControlData.ValueUpdateListener { public static final String TAG = OField.class.getSimpleName(); private Context mContext = null; private FieldType mType = FieldType.Text; private OColumn mColumn = null; private OModel mModel = null; private String mLabel, mField_name; private Object mValue = null; private boolean mEditable = false, showIcon = true, show_label = true; private TextView label_view = null; private int resId, tint_color = Color.BLACK, mValueArrayId = -1; private ImageView img_icon = null; private ViewGroup container = null; private Boolean with_bottom_padding = true, with_top_padding = true; private WidgetType mWidgetType = null; private String mParsePattern = null; private IOnChangeCallback mOnChangeCallback = null; private IOnDomainFilterCallbacks mOnDomainFilterCallbacks = null; private OColumn.ColumnDomain mColumnDomain = null; private float mWidgetImageSize = -1; private Boolean withPadding = true; // Controls private IOControlData mControlData = null; private Boolean useTemplate = true; private Integer defaultImage = -1; // Appearance private int textColor = Color.BLACK; private int labelColor = Color.DKGRAY; private int textAppearance = -1; private int labelAppearance = -1; private float textSize = -1; private float labelSize = -1; private IOnFieldValueChangeListener mValueUpdateListener = null; public enum WidgetType { Switch, RadioGroup, SelectionDialog, Searchable, SearchableLive, Image, ImageCircle, Duration; public static WidgetType getWidgetType(int widget) { switch (widget) { case 0: return WidgetType.Switch; case 1: return WidgetType.RadioGroup; case 2: return WidgetType.SelectionDialog; case 3: return WidgetType.Searchable; case 4: return WidgetType.SearchableLive; case 5: return WidgetType.Image; case 6: return WidgetType.ImageCircle; case 7: return WidgetType.Duration; } return null; } } public enum FieldType { Text, Boolean, ManyToOne, Chips, Selection, Date, Time, DateTime, Blob, RelationType; public static FieldType getTypeValue(int type_val) { switch (type_val) { case 0: return FieldType.Text; case 1: return FieldType.Boolean; case 2: return FieldType.ManyToOne; case 3: return FieldType.Chips; case 4: return FieldType.Selection; case 5: return FieldType.Date; case 6: return FieldType.DateTime; case 7: return FieldType.Blob; case 8: return FieldType.Time; } return FieldType.Text; } } public OField(Context context) { super(context); init(context, null, 0, 0); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { TypedArray types = mContext.obtainStyledAttributes(attrs, R.styleable.OField); mField_name = types.getString(R.styleable.OField_fieldName); resId = types.getResourceId(R.styleable.OField_iconResource, 0); showIcon = types.getBoolean(R.styleable.OField_showIcon, true); tint_color = types.getColor(R.styleable.OField_iconTint, 0); show_label = types.getBoolean(R.styleable.OField_showLabel, true); int type_value = types.getInt(R.styleable.OField_fieldType, 0); mType = FieldType.getTypeValue(type_value); with_bottom_padding = types.getBoolean( R.styleable.OField_withBottomPadding, true); with_top_padding = types.getBoolean( R.styleable.OField_withTopPadding, true); mLabel = types.getString(R.styleable.OField_controlLabel); mValue = types.getString(R.styleable.OField_defaultValue); mParsePattern = types.getString(R.styleable.OField_parsePattern); mValueArrayId = types.getResourceId( R.styleable.OField_valueArray, -1); mWidgetType = WidgetType.getWidgetType(types.getInt( R.styleable.OField_widgetType, -1)); mWidgetImageSize = types.getDimension(R.styleable.OField_widgetImageSize, -1); withPadding = types.getBoolean(R.styleable.OField_withOutSidePadding, true); textColor = types.getColor(R.styleable.OField_fieldTextColor, Color.BLACK); labelColor = types.getColor(R.styleable.OField_fieldLabelColor, Color.DKGRAY); textAppearance = types.getResourceId(R.styleable.OField_fieldTextAppearance, -1); labelAppearance = types.getResourceId(R.styleable.OField_fieldLabelTextAppearance, -1); textSize = types.getDimension(R.styleable.OField_fieldTextSize, -1); labelSize = types.getDimension(R.styleable.OField_fieldLabelSize, -1); defaultImage = types.getResourceId(R.styleable.OField_defaultImage, -1); types.recycle(); } if (mContext.getClass().getSimpleName().contains("BridgeContext")) initControl(); } public void useTemplate(Boolean withTemplate) { useTemplate = withTemplate; } private void initLayout() { removeAllViews(); if (useTemplate) { View layout = LayoutInflater.from(mContext).inflate( R.layout.base_control_template, this, false); if (withPadding) { int top_padding = layout.getPaddingTop(); int right_padding = layout.getPaddingRight(); int bottom_padding = layout.getPaddingBottom(); int left_padding = layout.getPaddingLeft(); if (!with_bottom_padding) { layout.setPadding(left_padding, top_padding, right_padding, 0); } if (!with_top_padding) { layout.setPadding(left_padding, 0, right_padding, bottom_padding); } } else { layout.setPadding(0, 0, 0, 0); } addView(layout); container = (ViewGroup) findViewById(R.id.control_container); img_icon = (ImageView) findViewById(android.R.id.icon); img_icon.setColorFilter(tint_color); setImageIcon(); } else { container = this; } } public void initControl() { initLayout(); View controlView = null; if (show_label) { label_view = getLabelView(); container.addView(label_view); } switch (mType) { case Text: controlView = initTextControl(); break; case Boolean: controlView = initBooleanControl(); break; case Chips: break; case ManyToOne: case Selection: controlView = initSelectionWidget(); break; case Date: case Time: case DateTime: controlView = initDateTimeControl(mType); break; case Blob: controlView = initBlobControl(); break; default: return; } mControlData.setValueUpdateListener(this); mControlData.setEditable(getEditable()); mControlData.initControl(); mControlData.setValue(mValue); container.addView(controlView); } public T getFieldView() { return (T) mControlData.getFieldView(); } public void setIconTintColor(int color) { tint_color = color; if (img_icon != null) { img_icon.setColorFilter(tint_color); } } private void setImageIcon() { if (showIcon) { if (resId != 0) img_icon.setImageResource(resId); if (tint_color != 0) img_icon.setColorFilter(tint_color); } else img_icon.setVisibility(View.GONE); } public void setColumn(OColumn column) { mColumn = column; mType = getType(column.getType()); if (label_view != null) { label_view.setText(getLabelText()); } if (mControlData != null) { mControlData.setColumn(mColumn); } } private FieldType getType(Class type_class) { try { // Varchar if (type_class.isAssignableFrom(OVarchar.class) || type_class.isAssignableFrom(OInteger.class) || type_class.isAssignableFrom(OFloat.class)) { return FieldType.Text; } // boolean if (type_class.isAssignableFrom(OBoolean.class)) { return FieldType.Boolean; } // Blob if (type_class.isAssignableFrom(OBlob.class)) { return FieldType.Blob; } // DateTime if (type_class.isAssignableFrom(ODateTime.class) || type_class.isAssignableFrom(OTimestamp.class)) { return FieldType.DateTime; } // Date if (type_class.isAssignableFrom(ODate.class)) { return FieldType.Date; } // Text if (type_class.isAssignableFrom(OText.class)) { return FieldType.Text; } // FIXME: WebView type if (type_class.isAssignableFrom(OHtml.class)) { return FieldType.Text; } if (type_class.isAssignableFrom(OSelection.class)) { return FieldType.Selection; } // ManyToOne if (mColumn.getRelationType() != null && mColumn.getRelationType() == OColumn.RelationType.ManyToOne) { return FieldType.ManyToOne; } } catch (Exception e) { e.printStackTrace(); } return null; } public String getLabelText() { if (mLabel != null) return mLabel; if (mColumn != null) return mColumn.getLabel(); if (mControlData != null) return mControlData.getLabel(); return getFieldName(); } public void setValue(Object value) { mValue = value; if (mValue != null && mControlData != null) { mControlData.setValue(mValue); } } public Object getValue() { if (mControlData != null) return mControlData.getValue(); return null; } public void setEditable(Boolean editable) { mEditable = editable; if (mControlData != null) { Object value = getValue(); mControlData.setEditable(editable); mControlData.initControl(); if (value != null) mControlData.setValue(value); } } public boolean getEditable() { return mEditable; } public String getFieldName() { return mField_name; } // EditText control (TextView, EditText) private View initTextControl() { setOrientation(VERTICAL); OEditTextField edt = new OEditTextField(mContext); edt.setWidgetType(mWidgetType); mControlData = edt; edt.setResource(textSize, textAppearance, textColor); edt.setColumn(mColumn); edt.setHint(mLabel); return edt; } // Boolean Control (Checkbox, W-Switch) private View initBooleanControl() { OBooleanField bool = new OBooleanField(mContext); mControlData = bool; bool.setResource(textSize, textAppearance, textColor); bool.setColumn(mColumn); bool.setEditable(getEditable()); bool.setLabelText(getLabelText()); bool.setWidgetType(mWidgetType); return bool; } // Selection, Searchable, SearchableLive private View initSelectionWidget() { OSelectionField selection = new OSelectionField(mContext); mControlData = selection; selection.setResource(textSize, textAppearance, textColor); selection.setLabelText(getLabelText()); selection.setModel(mModel); selection.setArrayResourceId(mValueArrayId); selection.setColumn(mColumn); selection.setWidgetType(mWidgetType); return selection; } // Datetime (dialog with date or date time) private View initDateTimeControl(FieldType type) { ODateTimeField datetime = new ODateTimeField(mContext); mControlData = datetime; datetime.setResource(textSize, textAppearance, textColor); datetime.setFieldType(type); datetime.setParsePattern(mParsePattern); datetime.setLabelText(getLabelText()); datetime.setColumn(mColumn); return datetime; } // Blob (file contents) private View initBlobControl() { OBlobField blob = new OBlobField(mContext); mControlData = blob; blob.setDefaultImage(defaultImage); blob.setImageSize(mWidgetImageSize); blob.setLabelText(getLabelText()); blob.setColumn(mColumn); blob.setWidgetType(mWidgetType); return blob; } private TextView getLabelView() { LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); TextView label = new TextView(mContext); if (labelSize > -1) { label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelSize); } if (labelAppearance > -1) { label.setTextAppearance(mContext, labelAppearance); } label.setTextColor(labelColor); label.setLayoutParams(params); label.setGravity(Gravity.LEFT); label.setText(getLabelText()); label.setAllCaps(true); return label; } public void setIcon(int resourceId) { img_icon.setImageResource(resourceId); } public void setError(String error) { mControlData.setError(error); } public int getIcon() { return resId; } public void setModel(OModel model) { mModel = model; } public OModel getModel() { return mModel; } public OColumn getColumn() { return mColumn; } public void resetData() { mControlData.resetData(); } @Override public void onValueUpdate(Object value) { mValue = value; if (mValueUpdateListener != null) { mValueUpdateListener.onFieldValueChange(this, value); } if (value instanceof ODataRow) { mValue = ((ODataRow) value).get(OColumn.ROW_ID); } if (mEditable) { if (mControlData.isControlReady()) { ODataRow row = new ODataRow(); if (mOnChangeCallback != null || mOnDomainFilterCallbacks != null) { if (!(value instanceof ODataRow)) { row.put(mColumn.getName(), value); } else { row = (ODataRow) value; } } if (mOnChangeCallback != null) { mOnChangeCallback.onValueChange(row); } if (mOnDomainFilterCallbacks != null) { mColumnDomain.setValue(row.getInt(OColumn.ROW_ID)); mOnDomainFilterCallbacks.onFieldValueChanged(mColumnDomain); } } } } public void setOnValueChangeListener(IOnFieldValueChangeListener listener) { mValueUpdateListener = listener; } @Override public void visibleControl(boolean isVisible) { if (isVisible) { setVisibility(View.VISIBLE); } else { setVisibility(View.GONE); } } /** * OnChange CallBack for column * * @param callback */ public void setOnChangeCallbackListener(IOnChangeCallback callback) { mOnChangeCallback = callback; } /** * Domain Filters * * @param domain * @param callback */ public void setOnFilterDomainCallBack(OColumn.ColumnDomain domain, IOnDomainFilterCallbacks callback) { mColumnDomain = domain; mOnDomainFilterCallbacks = callback; } public interface IOnFieldValueChangeListener { public void onFieldValueChange(OField field, Object value); } } ================================================ FILE: app/src/main/java/odoo/controls/OForm.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:10 PM */ package odoo.controls; import android.animation.LayoutTransition; import android.annotation.TargetApi; import android.app.ProgressDialog; import android.content.Context; import android.content.res.TypedArray; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.RelativeLayout; import com.odoo.R; import com.odoo.base.addons.mail.widget.MailChatterView; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.OValues; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.utils.OResource; import com.odoo.core.utils.logger.OLog; import java.util.HashMap; import java.util.LinkedHashMap; public class OForm extends LinearLayout { public static final String TAG = OForm.class.getSimpleName(); private Boolean mEditable = false; private String mModel; private OModel model = null; private HashMap mFormFieldControls = new HashMap<>(); private Context mContext = null; private ODataRow mRecord = null; private Boolean autoUIGenerate = true; private int icon_tint_color = 0; private Boolean mFirstModeChange = true; private OValues extraValues = new OValues(); private MailChatterView chatterView = null; private Boolean loadChatter = true; public OForm(Context context) { super(context); init(context, null, 0, 0); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OForm(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OForm(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OForm(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public void setEditable(Boolean editable) { mEditable = editable; if (mEditable) { mFirstModeChange = true; } for (String key : mFormFieldControls.keySet()) { OField control = mFormFieldControls.get(key); control.setEditable(editable); } mFirstModeChange = false; } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mFirstModeChange = true; mContext = context; if (attrs != null) { TypedArray types = mContext.obtainStyledAttributes(attrs, R.styleable.OForm); mModel = types.getString(R.styleable.OForm_modelName); mEditable = types.getBoolean(R.styleable.OForm_editableMode, false); autoUIGenerate = types.getBoolean(R.styleable.OForm_autoUIGenerate, true); icon_tint_color = types.getColor(R.styleable.OForm_controlIconTint, -1); types.recycle(); } initForm(); LayoutTransition transition = new LayoutTransition(); setLayoutTransition(transition); } public boolean getEditable() { return mEditable; } public void setModel(String model) { mModel = model; } public String getModel() { return mModel; } public void setData(ODataRow record) { initForm(record); } public ODataRow getData() { return mRecord; } public void initForm(ODataRow record) { mRecord = new ODataRow(); mRecord = record; initForm(); } public void setIconTintColor(int color) { icon_tint_color = color; } private void initForm() { findAllFields(this); OLog.log("Trying to get model " + mModel); model = OModel.get(mContext, mModel, null); if (model != null) { setOrientation(VERTICAL); for (String key : mFormFieldControls.keySet()) { View v = mFormFieldControls.get(key); if (v instanceof OField) { OField c = (OField) v; c.setEditable(mEditable); c.useTemplate(autoUIGenerate); c.setModel(model); OColumn column = model.getColumn(c.getFieldName()); if (column != null) { c.setColumn(column); // Setting OnChange Event if (column.hasOnChange()) { setOnChangeForControl(column, c); } // Setting domain Filter for column if (column.hasDomainFilterColumn()) { setOnDomainFilterCallBack(column, c); } } c.initControl(); Object val = c.getValue(); if (mRecord != null) { if (mRecord.contains(c.getFieldName())) val = mRecord.get(c.getFieldName()); } if (val != null) c.setValue(val); if (icon_tint_color != -1) { c.setIconTintColor(icon_tint_color); } } } // Adding chatter view if model requested if (loadChatter) { if (model != null && model.hasMailChatter() && mRecord != null && mRecord.contains("id") && mRecord.getInt("id") != 0) { if (chatterView == null) { chatterView = (MailChatterView) LayoutInflater.from(mContext) .inflate(R.layout.base_mail_chatter, this, false); chatterView.setModelName(model.getModelName()); chatterView.setRecordServerId(mRecord.getInt("id")); chatterView.generateView(); addView(chatterView); } } } } } public void loadChatter(boolean loadChatter) { this.loadChatter = loadChatter; } public void loadChatter(Boolean loadChatter) { this.loadChatter = loadChatter; } private void findAllFields(ViewGroup view) { int child = view.getChildCount(); for (int i = 0; i < child; i++) { View v = view.getChildAt(i); if (v instanceof LinearLayout || v instanceof RelativeLayout) { if (v.getVisibility() == View.VISIBLE) findAllFields((ViewGroup) v); } if (v instanceof OField) { OField field = (OField) v; if (field.getVisibility() == View.VISIBLE) mFormFieldControls.put(field.getFieldName(), field); } } } public OValues getValues() { OValues values = new OValues(); for (String key : mFormFieldControls.keySet()) { OField control = mFormFieldControls.get(key); Object val = control.getValue(); OColumn column = control.getColumn(); if (val == null || TextUtils.isEmpty(val.toString()) || val.toString().equals("-1")) { val = false; } if (column != null && column.isRequired()) { if (val.toString().equals("false")) { control.setError(column.getLabel() + " " + OResource.string(mContext, R.string.label_required)); return null; } else { control.setError(null); } } values.put(key, val); } values.addAll(extraValues.toDataRow().getAll()); return values; } // OnDomainFilterCallBack callbacks private void setOnDomainFilterCallBack(final OColumn column, OField field) { LinkedHashMap filterDomain = column .getFilterDomains(); for (String key : filterDomain.keySet()) { OColumn.ColumnDomain domain = filterDomain.get(key); if (domain.getColumn() != null) { OField fld = mFormFieldControls.get(domain.getColumn()); if (fld != null) { setFilterDomainCallback(domain, fld, field, column); } } } } private void setFilterDomainCallback(OColumn.ColumnDomain domain, final OField field, final OField oField, final OColumn column) { field.setOnFilterDomainCallBack(domain, new IOnDomainFilterCallbacks() { @Override public void onFieldValueChanged(OColumn.ColumnDomain dm) { column.addDomain(dm.getColumn(), dm.getOperator(), dm.getValue()); column.setHasDomainFilterColumn(false); oField.setColumn(column); oField.resetData(); } }); } // OnChange event for control column private void setOnChangeForControl(final OColumn column, OField field) { field.setOnChangeCallbackListener(new IOnChangeCallback() { @Override public void onValueChange(final ODataRow row) { if (!mFirstModeChange) { if (!column.isOnChangeBGProcess()) { new Handler().postDelayed(new Runnable() { @Override public void run() { Object value = model.getOnChangeMethodValue(column, row); if (value instanceof ODataRow) fillOnChangeData((ODataRow) value); } }, 300); } else { new Handler().postDelayed(new Runnable() { @Override public void run() { OnChangeBackground bgProcess = new OnChangeBackground( column); bgProcess.execute(row); } }, 300); } } if (mFirstModeChange) { mFirstModeChange = false; } } }); } private void fillOnChangeData(ODataRow values) { if (values != null) { for (String key : values.keys()) { if (mFormFieldControls.containsKey(key)) { OField fld = mFormFieldControls.get(key); fld.setValue(values.get(key)); } else { extraValues.put(key, values.get(key)); } } } } private class OnChangeBackground extends AsyncTask { private ProgressDialog mDialog; private OColumn mCol; public OnChangeBackground(OColumn col) { mCol = col; } @Override protected void onPreExecute() { super.onPreExecute(); mDialog = new ProgressDialog(mContext); mDialog.setTitle(mContext.getString(R.string.title_working)); mDialog.setMessage(mContext.getString(R.string.title_please_wait)); mDialog.setCancelable(false); mDialog.show(); } @Override protected ODataRow doInBackground(ODataRow... params) { try { Thread.sleep(300); return (ODataRow) model.getOnChangeMethodValue(mCol, params[0]); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(ODataRow result) { super.onPostExecute(result); if (result != null) { fillOnChangeData(result); } mDialog.dismiss(); } } } ================================================ FILE: app/src/main/java/odoo/controls/OSelectionField.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:11 PM */ package odoo.controls; import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OM2ORecord; import com.odoo.core.orm.OModel; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.orm.fields.types.OSelection; import com.odoo.core.utils.OControls; import java.util.ArrayList; import java.util.List; public class OSelectionField extends LinearLayout implements IOControlData, AdapterView.OnItemSelectedListener, AdapterView.OnItemClickListener, RadioGroup.OnCheckedChangeListener { public static final String TAG = OSelectionField.class.getSimpleName(); private Context mContext; private Object mValue = null; private Boolean mEditable = false; private OField.WidgetType mWidget = null; private Integer mResourceArray = null; private OColumn mCol; private String mLabel; private OModel mModel; private List items = new ArrayList<>(); private ValueUpdateListener mValueUpdateListener = null; // Controls private Spinner mSpinner = null; private SpinnerAdapter mAdapter; private RadioGroup mRadioGroup = null; private TextView txvView = null; private Boolean mReady = false; private float textSize = -1; private int appearance = -1; private int textColor = Color.BLACK; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public OSelectionField(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } public OSelectionField(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public OSelectionField(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs, 0, 0); } public OSelectionField(Context context) { super(context); init(context, null, 0, 0); } private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; if (attrs != null) { } if (mContext.getClass().getSimpleName().contains("BridgeContext")) initControl(); } private void createRadioGroup() { final LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); if (mRadioGroup == null) { mRadioGroup = new RadioGroup(mContext); mRadioGroup.setLayoutParams(params); } else { removeView(mRadioGroup); } mRadioGroup.removeAllViews(); mRadioGroup.setOnCheckedChangeListener(this); for (ODataRow label : items) { RadioButton rdoBtn = new RadioButton(mContext); rdoBtn.setLayoutParams(params); rdoBtn.setText(label.getString(mModel.getDefaultNameColumn())); if (textSize > -1) { rdoBtn.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { rdoBtn.setTextAppearance(mContext, appearance); } rdoBtn.setTextColor(textColor); mRadioGroup.addView(rdoBtn); } addView(mRadioGroup); } @Override public void initControl() { final LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); removeAllViews(); setOrientation(VERTICAL); createItems(); if (isEditable()) { if (mWidget != null) { switch (mWidget) { case RadioGroup: createRadioGroup(); return; case SelectionDialog: txvView = new TextView(mContext); txvView.setLayoutParams(params); mAdapter = new SpinnerAdapter(mContext, android.R.layout.simple_list_item_1, items); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { AlertDialog dialog = createSelectionDialog( getPos(), items, params); txvView.setTag(dialog); dialog.show(); } }); if (textSize > -1) { txvView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvView.setTextAppearance(mContext, appearance); } txvView.setTextColor(textColor); addView(txvView); return; case Searchable: case SearchableLive: txvView = new TextView(mContext); txvView.setLayoutParams(params); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mContext, SearchableItemActivity.class); intent.putExtra("resource_id", mResourceArray); intent.putExtra("selected_position", getPos()); intent.putExtra(OColumn.ROW_ID, getPos()); intent.putExtra("search_hint", getLabel()); if (mCol != null) { intent.putExtra("column_name", mCol.getName()); } /* * FIXME: What about filter domain. Pass detail for * filter domain */ intent.putExtra("model", mModel.getModelName()); intent.putExtra("live_search", (mWidget == OField.WidgetType.SearchableLive)); try { mContext.unregisterReceiver(valueReceiver); } catch (Exception e) { } mContext.registerReceiver(valueReceiver, new IntentFilter("searchable_value_select")); mContext.startActivity(intent); } }); if (textSize > -1) { txvView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvView.setTextAppearance(mContext, appearance); } txvView.setTextColor(textColor); addView(txvView); return; default: break; } } // Default View mSpinner = new Spinner(mContext); mSpinner.setLayoutParams(params); mAdapter = new SpinnerAdapter(mContext, android.R.layout.simple_list_item_1, items); mSpinner.setAdapter(mAdapter); mSpinner.setOnItemSelectedListener(this); addView(mSpinner); } else { setOnClickListener(null); txvView = new TextView(mContext); if (textSize > -1) { txvView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } if (appearance > -1) { txvView.setTextAppearance(mContext, appearance); } txvView.setTextColor(textColor); addView(txvView); } } private void createItems() { items.clear(); if (!mContext.getClass().getSimpleName().contains("BridgeContext")) { if (mResourceArray != null && mResourceArray != -1) { String[] items_list = mContext.getResources().getStringArray( mResourceArray); ODataRow row = new ODataRow(); row.put(OColumn.ROW_ID, -1); row.put(mModel.getDefaultNameColumn(), "Nothing Selected"); items.add(row); for (int i = 0; i < items_list.length; i++) { row = new ODataRow(); row.put(OColumn.ROW_ID, i); row.put(mModel.getDefaultNameColumn(), items_list[i]); items.add(row); } } else if (mCol.getType().isAssignableFrom(OSelection.class)) { List rows = new ArrayList<>(); Object defaultVal = mCol.getDefaultValue(); for (String key : mCol.getSelectionMap().keySet()) { String val = mCol.getSelectionMap().get(key); ODataRow row = new ODataRow(); row.put("key", key); row.put("name", val); if (defaultVal != null && defaultVal.toString().equals(val)) { rows.add(0, row); } else { rows.add(row); } } items.addAll(rows); } else { items.addAll(getRecordItems(mModel, mCol)); } } } private int getPos() { if (mResourceArray != -1 && mValue != null) { return Integer.parseInt(mValue.toString()); } else if (mCol.getType().isAssignableFrom(OSelection.class)) { if (items.size() <= 0) { createItems(); } for (ODataRow item : items) { int index = items.indexOf(item); if (item.getString("key").equals(mValue.toString())) { return index; } } } else { ODataRow rec = getValueForM2O(); if (rec != null) { return rec.getInt(OColumn.ROW_ID); } } return -1; } @Override public void setValue(Object value) { mValue = value; if (mValue == null || mValue.toString().equals("false")) { mValue = -1; } ODataRow row = new ODataRow(); if (isEditable()) { if (mWidget != null) { switch (mWidget) { case RadioGroup: if (mResourceArray != -1) { ((RadioButton) mRadioGroup.getChildAt(getPos())) .setChecked(true); row = items.get(getPos()); } else { Integer row_id = null; if (mValue instanceof OM2ORecord) { row = ((OM2ORecord) mValue).browse(); row_id = row.getInt(OColumn.ROW_ID); } else row_id = (Integer) mValue; int index = 0; for (int i = 0; i < items.size(); i++) { if (items.get(i).getInt(OColumn.ROW_ID) == row_id) { index = i; break; } } row = items.get(index); ((RadioButton) mRadioGroup.getChildAt(index)) .setChecked(true); } break; case Searchable: case SearchableLive: case SelectionDialog: if (mResourceArray != -1) { row = items.get(getPos()); } else { if (mValue instanceof OM2ORecord) row = ((OM2ORecord) mValue).browse(); else if (mValue instanceof Integer) row = getRecordData((Integer) mValue); } txvView.setText(row.getString(mModel.getDefaultNameColumn())); if (txvView.getTag() != null) { AlertDialog dialog = (AlertDialog) txvView.getTag(); dialog.dismiss(); } break; default: break; } } else { if (mResourceArray != -1) { mSpinner.setSelection(getPos()); row = items.get(getPos()); } else if (mCol.getType().isAssignableFrom(OSelection.class)) { int pos = getPos(); mSpinner.setSelection(pos); } else { Integer row_id = null; if (mValue instanceof OM2ORecord) { row = ((OM2ORecord) mValue).browse(); row_id = row.getInt(OColumn.ROW_ID); } else if (mValue instanceof Integer) row_id = (Integer) mValue; int index = 0; for (int i = 0; i < items.size(); i++) { if (items.get(i).getInt(OColumn.ROW_ID) == row_id) { index = i; break; } } row = items.get(index); mSpinner.setSelection(index); } } } else { if (mResourceArray != -1 || mCol.getType().isAssignableFrom(OSelection.class)) { row = items.get(getPos()); } else { if (mValue instanceof OM2ORecord) { row = ((OM2ORecord) mValue).browse(); if (row == null) { row = new ODataRow(); } } else { if (!(mValue instanceof Boolean) && mValue != null && !mValue.toString().equals("false")) { int row_id = (Integer) mValue; row = getRecordData(row_id); } else { row = new ODataRow(); row.put(mModel.getDefaultNameColumn(), "No " + mCol.getLabel() + " selected"); } } } if (!row.getString(mModel.getDefaultNameColumn()).equals("false")) txvView.setText(row.getString(mModel.getDefaultNameColumn())); } if (mValueUpdateListener != null && mValue != -1) { mValueUpdateListener.onValueUpdate(row); } } @Override public View getFieldView() { return null; } @Override public void setError(String error) { if (error != null) Toast.makeText(mContext, error, Toast.LENGTH_LONG).show(); } private ODataRow getValueForM2O() { if (getValue() != null) { if (getValue() instanceof OM2ORecord) return ((OM2ORecord) getValue()).browse(); else if (getValue() instanceof Integer) return getRecordData((Integer) getValue()); } return null; } @Override public Object getValue() { if (mValue instanceof OM2ORecord) { return ((OM2ORecord) mValue).getId(); } return mValue; } @Override public void setEditable(Boolean editable) { mEditable = editable; initControl(); } @Override public Boolean isEditable() { return mEditable; } public void setWidgetType(OField.WidgetType type) { mWidget = type; initControl(); } public void setArrayResourceId(int res_id) { mResourceArray = res_id; } public void setColumn(OColumn col) { mCol = col; if (mCol != null && mLabel == null) { mLabel = mCol.getLabel(); } } private ODataRow getRecordData(int row_id) { ODataRow row; if (row_id > 0) { OModel rel_model = mModel.createInstance(mCol.getType()); row = rel_model.browse(row_id); } else { row = items.get(0); } return row; } private class SpinnerAdapter extends ArrayAdapter { public SpinnerAdapter(Context context, int resource, List objects) { super(context, resource, objects); } public View getView(int position, View convertView, ViewGroup parent) { return generateView(position, convertView, parent); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return generateView(position, convertView, parent); } private View generateView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) v = LayoutInflater.from(mContext).inflate( android.R.layout.simple_list_item_1, parent, false); ODataRow row = getItem(position); OControls.setText(v, android.R.id.text1, row.getString(mModel.getDefaultNameColumn())); return v; } } @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (mResourceArray != -1) { mValue = position; } else if (mCol.getType().isAssignableFrom(OSelection.class)) { ODataRow row = mAdapter.getItem(position); mValue = row.getString("key"); } else { mValue = items.get(position).get(OColumn.ROW_ID); } setValue(mValue); } @Override public void onNothingSelected(AdapterView parent) { mValue = null; } @Override public void setLabelText(String label) { mLabel = label; } @Override public String getLabel() { if (mLabel != null) return mLabel; if (mCol != null) return mCol.getLabel(); return "unknown"; } private AlertDialog createSelectionDialog(final int selected_position, final List items, LayoutParams params) { final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); ListView dialogView = new ListView(mContext); dialogView.setAdapter(mAdapter); dialogView.setOnItemClickListener(this); dialogView.setLayoutParams(params); builder.setView(dialogView); return builder.create(); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { setValue(position); } BroadcastReceiver valueReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { setValue(intent.getIntExtra("selected_position", -1)); mContext.unregisterReceiver(valueReceiver); } }; public void setModel(OModel model) { mModel = model; } public static List getRecordItems(OModel model, OColumn column) { List items = new ArrayList(); OModel rel_model = model.createInstance(column.getType()); StringBuffer whr = new StringBuffer(); List args_list = new ArrayList(); // Skipping onchange domain filter if (!column.hasDomainFilterColumn()) { for (String key : column.getDomains().keySet()) { OColumn.ColumnDomain domain = column.getDomains().get(key); if (domain.getConditionalOperator() != null) { whr.append(domain.getConditionalOperator()); } else { whr.append(" "); whr.append(domain.getColumn()); whr.append(" "); whr.append(domain.getOperator()); whr.append(" ? "); args_list.add(domain.getValue().toString()); } } } String where = null; String[] args = null; if (args_list.size() > 0) { where = whr.toString(); args = args_list.toArray(new String[args_list.size()]); } List rows = new ArrayList<>(); rows = rel_model.select(new String[]{rel_model.getDefaultNameColumn()}, where, args, rel_model.getDefaultNameColumn()); ODataRow row = new ODataRow(); row.put(OColumn.ROW_ID, -1); row.put(rel_model.getDefaultNameColumn(), "No " + column.getLabel() + " selected"); items.add(row); items.addAll(rows); return items; } @Override public void setValueUpdateListener(ValueUpdateListener listener) { mValueUpdateListener = listener; } @Override public void onCheckedChanged(RadioGroup group, int checkedId) { int index = mRadioGroup.indexOfChild(group.findViewById(checkedId)); ODataRow row = items.get(index); setValue(row.getInt(OColumn.ROW_ID)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mReady = true; } @Override public Boolean isControlReady() { return mReady; } @Override public void resetData() { if (isEditable()) { if (mWidget == null) { if (mAdapter != null) { createItems(); mAdapter.notifyDataSetChanged(); } } else { switch (mWidget) { case SelectionDialog: createItems(); break; case RadioGroup: createItems(); createRadioGroup(); break; case Searchable: case SearchableLive: break; default: break; } } } } public void setResource(float textSize, int appearance, int color) { this.textSize = textSize; this.appearance = appearance; this.textColor = color; } } ================================================ FILE: app/src/main/java/odoo/controls/SearchableItemActivity.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 7/1/15 5:11 PM */ package odoo.controls; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import com.odoo.core.orm.ODataRow; import com.odoo.core.orm.OModel; import com.odoo.core.orm.ServerDataHelper; import com.odoo.core.orm.fields.OColumn; import com.odoo.core.support.OdooFields; import com.odoo.core.support.list.OListAdapter; import com.odoo.core.utils.OControls; import com.odoo.core.utils.OResource; import com.odoo.R; import java.util.ArrayList; import java.util.List; import odoo.ODomain; public class SearchableItemActivity extends ActionBarActivity implements AdapterView.OnItemClickListener, TextWatcher, View.OnClickListener, OListAdapter.OnSearchChange, IOnQuickRecordCreateListener { public static final String TAG = SearchableItemActivity.class.getSimpleName(); private EditText edt_searchable_input; private ListView mList = null; private OListAdapter mAdapter; private List objects = new ArrayList<>(); private int selected_position = -1; private Boolean mLiveSearch = false; private int resource_array_id = -1; private OModel mModel = null; private OModel mRelModel = null; private Integer mRowId = null; private LiveSearch mLiveDataLoader = null; private OColumn mCol = null; private AlertDialog.Builder builder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.base_control_searchable_layout); setResult(RESULT_CANCELED); edt_searchable_input = (EditText) findViewById(R.id.edt_searchable_input); edt_searchable_input.addTextChangedListener(this); Bundle extra = getIntent().getExtras(); if (extra != null) { if (extra.containsKey("resource_id")) { resource_array_id = extra.getInt("resource_id"); } if (extra.containsKey(OColumn.ROW_ID)) { mRowId = extra.getInt(OColumn.ROW_ID); } if (extra.containsKey("model")) { mModel = OModel.get(this, extra.getString("model"), null); } if (extra.containsKey("live_search")) { mLiveSearch = extra.getBoolean("live_search"); } if (extra.containsKey("selected_position")) { selected_position = extra.getInt("selected_position"); } if (extra.containsKey("search_hint")) { edt_searchable_input.setHint("Search " + extra.getString("search_hint")); } if (resource_array_id != -1) { String[] arrays = getResources().getStringArray( resource_array_id); for (int i = 0; i < arrays.length; i++) { ODataRow row = new ODataRow(); row.put(OColumn.ROW_ID, i); row.put(mRelModel.getDefaultNameColumn(), arrays[i]); objects.add(row); } } else { if (extra.containsKey("column_name")) { mCol = mModel.getColumn(extra.getString("column_name")); mRelModel = mModel.createInstance(mCol.getType()); objects.addAll(OSelectionField.getRecordItems(mRelModel, mCol)); } } mList = (ListView) findViewById(R.id.searchable_items); mList.setOnItemClickListener(this); mAdapter = new OListAdapter(this, android.R.layout.simple_expandable_list_item_1, objects) { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) v = getLayoutInflater().inflate(getResource(), parent, false); ODataRow row = (ODataRow) objects.get(position); OControls.setText(v, android.R.id.text1, row.getString(mRelModel.getDefaultNameColumn())); if (row.contains(OColumn.ROW_ID) && selected_position == row.getInt(OColumn.ROW_ID)) { v.setBackgroundColor(getResources().getColor( R.color.control_pressed)); } else { v.setBackgroundColor(Color.TRANSPARENT); } return v; } }; if (mLiveSearch) { mAdapter.setOnSearchChange(this); } mList.setAdapter(mAdapter); } else { finish(); } } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { ODataRow data = (ODataRow) objects.get(position); if (!data.contains(OColumn.ROW_ID)) { QuickCreateRecordProcess quickCreateRecordProcess = new QuickCreateRecordProcess(this); quickCreateRecordProcess.execute(data); } else { onRecordCreated(data); } } @Override public void onRecordCreated(ODataRow row) { Intent intent = new Intent("searchable_value_select"); intent.putExtra("selected_position", row.getInt(OColumn.ROW_ID)); if (mRowId != null) { intent.putExtra("record_id", true); } sendBroadcast(intent); finish(); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { mAdapter.getFilter().filter(s); ImageView imgView = (ImageView) findViewById(R.id.search_icon); if (s.length() > 0) { imgView.setImageResource(R.drawable.ic_action_navigation_close); imgView.setOnClickListener(this); imgView.setClickable(true); } else { imgView.setClickable(false); imgView.setImageResource(R.drawable.ic_action_search); imgView.setOnClickListener(null); } } @Override public void afterTextChanged(Editable s) { } @Override public void onClick(View v) { setResult(RESULT_CANCELED); finish(); } @Override public void onSearchChange(List newRecords) { if (newRecords.size() <= 0) { if (mLiveDataLoader != null) mLiveDataLoader.cancel(true); if (edt_searchable_input.getText().length() >= 3) { mLiveDataLoader = new LiveSearch(); mLiveDataLoader.execute(edt_searchable_input.getText() .toString()); } } } private class LiveSearch extends AsyncTask> { @Override protected void onPreExecute() { super.onPreExecute(); findViewById(R.id.loading_progress).setVisibility(View.VISIBLE); mList.setVisibility(View.GONE); } @Override protected List doInBackground(String... params) { try { ServerDataHelper helper = mRelModel.getServerDataHelper(); ODomain domain = new ODomain(); domain.add(mRelModel.getDefaultNameColumn(), "ilike", params[0]); if (mCol != null) { for (String key : mCol.getDomains().keySet()) { OColumn.ColumnDomain dom = mCol.getDomains().get(key); domain.add(dom.getColumn(), dom.getOperator(), dom.getValue()); } } OdooFields fields = new OdooFields(mRelModel.getColumns()); return helper.searchRecords(fields, domain, 10); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(List result) { super.onPostExecute(result); findViewById(R.id.loading_progress).setVisibility(View.GONE); mList.setVisibility(View.VISIBLE); if (result != null && result.size() > 0) { objects.addAll(result); mAdapter.notifiyDataChange(objects); } } @Override protected void onCancelled() { super.onCancelled(); findViewById(R.id.loading_progress).setVisibility(View.GONE); mList.setVisibility(View.VISIBLE); } } private class QuickCreateRecordProcess extends AsyncTask { private ProgressDialog progressDialog; IOnQuickRecordCreateListener mOnQuickRecordCreateListener = null; public QuickCreateRecordProcess(IOnQuickRecordCreateListener listener) { mOnQuickRecordCreateListener = listener; } @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(SearchableItemActivity.this); progressDialog.setTitle(R.string.title_please_wait); progressDialog.setMessage(OResource.string(SearchableItemActivity.this, R.string.title_working)); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected ODataRow doInBackground(ODataRow... params) { try { Thread.sleep(700); return mRelModel.quickCreateRecord(params[0]); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(ODataRow data) { super.onPostExecute(data); if (data != null && mOnQuickRecordCreateListener != null) { mOnQuickRecordCreateListener.onRecordCreated(data); } progressDialog.dismiss(); } } } ================================================ FILE: app/src/main/java/odoo/controls/fab/DirectionScrollListener.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 2:24 PM */ package odoo.controls.fab; import android.view.View; import android.widget.AbsListView; public class DirectionScrollListener implements AbsListView.OnScrollListener { public static final String TAG = DirectionScrollListener.class.getSimpleName(); private static final int DIRECTION_CHANGE_THRESHOLD = 1; private final FloatingActionButton mFloatingActionButton; private int mPrevPosition; private int mPrevTop; private boolean mUpdated; DirectionScrollListener(FloatingActionButton floatingActionButton) { mFloatingActionButton = floatingActionButton; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { final View topChild = view.getChildAt(0); int firstViewTop = 0; if (topChild != null) { firstViewTop = topChild.getTop(); } boolean goingDown; boolean changed = true; if (mPrevPosition == firstVisibleItem) { final int topDelta = mPrevTop - firstViewTop; goingDown = firstViewTop < mPrevTop; changed = Math.abs(topDelta) > DIRECTION_CHANGE_THRESHOLD; } else { goingDown = firstVisibleItem > mPrevPosition; } if (changed && mUpdated) { mFloatingActionButton.hide(goingDown); } mPrevPosition = firstVisibleItem; mPrevTop = firstViewTop; mUpdated = true; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } } ================================================ FILE: app/src/main/java/odoo/controls/fab/FloatingActionButton.java ================================================ /** * Odoo, Open Source Management Solution * Copyright (C) 2012-today Odoo SA () * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * * Created on 9/1/15 2:25 PM */ package odoo.controls.fab; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.AbsListView; import com.odoo.R; public class FloatingActionButton extends View { public static final String TAG = FloatingActionButton.class.getSimpleName(); private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); private final Paint mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Bitmap mBitmap; private int mColor; private boolean mHidden = false; /** * The FAB button's Y position when it is displayed. */ private float mYDisplayed = -1; /** * The FAB button's Y position when it is hidden. */ private float mYHidden = -1; public FloatingActionButton(Context context) { this(context, null); } public FloatingActionButton(Context context, AttributeSet attributeSet) { this(context, attributeSet, 0); } public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FloatingActionButton); mColor = a.getColor(R.styleable.FloatingActionButton_fabColor, Color.WHITE); mButtonPaint.setStyle(Paint.Style.FILL); mButtonPaint.setColor(mColor); float radius, dx, dy; radius = a.getFloat(R.styleable.FloatingActionButton_shadowRadius, 10.0f); dx = a.getFloat(R.styleable.FloatingActionButton_shadowDx, 0.0f); dy = a.getFloat(R.styleable.FloatingActionButton_shadowDy, 3.5f); int color = a.getInteger(R.styleable.FloatingActionButton_shadowColor, Color.argb(100, 0, 0, 0)); mButtonPaint.setShadowLayer(radius, dx, dy, color); Drawable drawable = a .getDrawable(R.styleable.FloatingActionButton_drawable); if (null != drawable) { mBitmap = ((BitmapDrawable) drawable).getBitmap(); } setWillNotDraw(false); setLayerType(View.LAYER_TYPE_SOFTWARE, null); WindowManager mWindowManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); Display display = mWindowManager.getDefaultDisplay(); Point size = new Point(); display.getSize(size); mYHidden = size.y; a.recycle(); } public void setColor(int color) { mColor = color; mButtonPaint.setColor(mColor); invalidate(); } public void setDrawable(Drawable drawable) { mBitmap = ((BitmapDrawable) drawable).getBitmap(); invalidate(); } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2.6), mButtonPaint); if (null != mBitmap) { canvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2, (getHeight() - mBitmap.getHeight()) / 2, mDrawablePaint); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // Perform the default behavior super.onLayout(changed, left, top, right, bottom); // Store the FAB button's displayed Y position if we are not already // aware of it if (mYDisplayed == -1) { mYDisplayed = this.getY(); } } @Override public boolean onTouchEvent(MotionEvent event) { int color; if (event.getAction() == MotionEvent.ACTION_UP) { color = mColor; } else { color = darkenColor(mColor); } mButtonPaint.setColor(color); invalidate(); return super.onTouchEvent(event); } public void hide(boolean hide) { // If the hidden state is being updated if (mHidden != hide) { // Store the new hidden state mHidden = hide; // Animate the FAB to it's new Y position ObjectAnimator animator = ObjectAnimator.ofFloat(this, "Y", mHidden ? mYHidden : mYDisplayed); animator.setInterpolator(mInterpolator); animator.start(); } } public void listenTo(AbsListView listView) { if (null != listView) { listView.setOnScrollListener(new DirectionScrollListener(this)); } } public static int darkenColor(int color) { float[] hsv = new float[3]; Color.colorToHSV(color, hsv); hsv[2] *= 0.8f; return Color.HSVToColor(hsv); } } ================================================ FILE: app/src/main/res/drawable/circle_mask.xml ================================================ ================================================ FILE: app/src/main/res/drawable/circle_mask_gray.xml ================================================ ================================================ FILE: app/src/main/res/drawable/circle_mask_primary.xml ================================================ ================================================ FILE: app/src/main/res/drawable/circle_mask_secondary.xml ================================================ ================================================ FILE: app/src/main/res/drawable/drawer_background_cover.xml ================================================ ================================================ FILE: app/src/main/res/drawable/icon_bg_oval_blue.xml ================================================ ================================================ FILE: app/src/main/res/drawable/icon_bg_oval_green.xml ================================================ ================================================ FILE: app/src/main/res/drawable/icon_bg_oval_orange.xml ================================================ ================================================ FILE: app/src/main/res/drawable/icon_bg_oval_red.xml ================================================ ================================================ FILE: app/src/main/res/drawable/icon_bg_oval_violet.xml ================================================ ================================================ FILE: app/src/main/res/drawable/login_signup_button.xml ================================================ ================================================ FILE: app/src/main/res/drawable/login_signup_button_clicked.xml ================================================ ================================================ FILE: app/src/main/res/drawable/login_signup_button_normal.xml ================================================ ================================================ FILE: app/src/main/res/drawable/login_signup_control_bg.xml ================================================ ================================================ FILE: app/src/main/res/drawable/tag_background.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_app_intro.xml ================================================ ================================================ FILE: app/src/main/res/layout/base_about.xml ================================================ ================================================ FILE: app/src/main/res/layout/base_account_ask_pass.xml ================================================ ================================================ FILE: app/src/main/res/layout/base_account_item.xml ================================================ ================================================ FILE: app/src/main/res/layout/base_account_quick_manage.xml ================================================