diff --git a/README.md b/README.md
index e989dec1..51130939 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
-
- v2.1.0alpha (Build 5)
+
+ v2.2.0 (Build 6)
-
-Odoo Mobile v2.1.0 (Framework)
+Odoo Mobile v2.2.0 (Framework)
==============================
Odoo Mobile is open-source framework allows you to integrate Odoo into your Android app.
@@ -19,10 +18,21 @@ It has pre-developed services,providers, controls and more other features to mak
**Odoo Mobile** is a part of The Odoo (India)
+CHANGELOG
+=========
+
+- June 2016
+ - Sync performance improved
+ - Easy relation record creation (Fix for: https://github.com/Odoo-mobile/framework/issues/138)
+ - ManyToOne, OneToMany and ManyToMany
+ - Minor bug fixes
+
SUPPORTED ODOO VERSIONS
=======================
-Odoo 7.0, 8.0, 9.0
+- [x] Odoo 7.0
+- [x] Odoo 8.0
+- [x] Odoo 9.0
HOW TO START
============
@@ -41,3 +51,5 @@ GIVE FEEDBACK
- Please report bug or issues [https://github.com/Odoo-mobile/framework/issues](https://github.com/Odoo-mobile/framework/issues)
- You can also write us on **android@odoo.co.in**
+
+Follow us on Twitter: @odoomobile
diff --git a/app/build.gradle b/app/build.gradle
index 04be87e1..a00f2f6e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,17 +1,21 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 23
- buildToolsVersion "23.0.3"
+ compileSdkVersion 24
+ buildToolsVersion "24.0.0"
defaultConfig {
//FIXME: Change application name as your requirement
manifestPlaceholders = [applicationName: "Odoo"]
//FIXME: Please change the application id as your requirement
applicationId "com.odoo"
minSdkVersion 14
- targetSdkVersion 23
- versionCode 5
- versionName "2.1.0alpha"
+ targetSdkVersion 24
+ versionCode 6
+ versionName "2.2.0"
+ multiDexEnabled true
+ }
+ dexOptions {
+ javaMaxHeapSize "2g"
}
buildTypes {
release {
@@ -24,10 +28,10 @@ android {
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.android.support:appcompat-v7:23.3.0'
- compile 'com.android.support:cardview-v7:23.3.0'
- compile 'com.google.android.gms:play-services:8.4.0'
- compile 'com.android.support:design:23.3.0'
+ compile 'com.android.support:appcompat-v7:24.0.0'
+ compile 'com.android.support:cardview-v7:24.0.0'
+ compile 'com.google.android.gms:play-services:9.0.2'
+ compile 'com.android.support:design:24.0.0'
compile project(':intro-slider-lib')
compile project(':odoo-rpc-v3')
}
diff --git a/app/src/main/java/com/odoo/addons/customers/Customers.java b/app/src/main/java/com/odoo/addons/customers/Customers.java
index 086dcc2d..a75759ca 100644
--- a/app/src/main/java/com/odoo/addons/customers/Customers.java
+++ b/app/src/main/java/com/odoo/addons/customers/Customers.java
@@ -63,7 +63,6 @@ public class Customers extends BaseFragment implements ISyncStatusObserverListen
public static final String EXTRA_KEY_TYPE = "extra_key_type";
private View mView;
private String mCurFilter = null;
- private ListView mPartnersList = null;
private OCursorListAdapter mAdapter = null;
private boolean syncRequested = false;
@@ -87,7 +86,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
setHasSwipeRefreshView(view, R.id.swipe_container, this);
mView = view;
mType = Type.valueOf(getArguments().getString(EXTRA_KEY_TYPE));
- mPartnersList = (ListView) view.findViewById(R.id.listview);
+ ListView mPartnersList = (ListView) view.findViewById(R.id.listview);
mAdapter = new OCursorListAdapter(getActivity(), null, R.layout.customer_row_item);
mAdapter.setOnViewBindListener(this);
mAdapter.setHasSectionIndexers(true, "name");
@@ -96,6 +95,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
mPartnersList.setOnItemClickListener(this);
setHasFloatingButton(view, R.id.fabButton, mPartnersList, this);
getLoaderManager().initLoader(0, null, this);
+
}
@Override
diff --git a/app/src/main/java/com/odoo/base/addons/res/ResPartner.java b/app/src/main/java/com/odoo/base/addons/res/ResPartner.java
index af47e4ce..a579d2f1 100644
--- a/app/src/main/java/com/odoo/base/addons/res/ResPartner.java
+++ b/app/src/main/java/com/odoo/base/addons/res/ResPartner.java
@@ -64,6 +64,12 @@ public class ResPartner extends OModel {
.setLocalColumn();
OColumn large_image = new OColumn("Image", OBlob.class).setDefaultValue("false").setLocalColumn();
+ OColumn category_id = new OColumn("Tags", ResPartnerCategory.class,
+ OColumn.RelationType.ManyToMany);
+
+ OColumn child_ids = new OColumn("Contacts", ResPartner.class, OColumn.RelationType.OneToMany)
+ .setRelatedColumn("parent_id");
+
public ResPartner(Context context, OUser user) {
super(context, "res.partner", user);
setHasMailChatter(true);
diff --git a/app/src/main/java/com/odoo/base/addons/res/ResPartnerCategory.java b/app/src/main/java/com/odoo/base/addons/res/ResPartnerCategory.java
new file mode 100644
index 00000000..f9463e0c
--- /dev/null
+++ b/app/src/main/java/com/odoo/base/addons/res/ResPartnerCategory.java
@@ -0,0 +1,17 @@
+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 ResPartnerCategory extends OModel {
+
+ OColumn name = new OColumn("Name", OVarchar.class);
+
+ public ResPartnerCategory(Context context, OUser user) {
+ super(context, "res.partner.category", user);
+ }
+}
diff --git a/app/src/main/java/com/odoo/core/account/OdooAccountQuickManage.java b/app/src/main/java/com/odoo/core/account/OdooAccountQuickManage.java
index 7f2e1584..f76b7194 100644
--- a/app/src/main/java/com/odoo/core/account/OdooAccountQuickManage.java
+++ b/app/src/main/java/com/odoo/core/account/OdooAccountQuickManage.java
@@ -1,20 +1,20 @@
/**
* 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;
@@ -27,7 +27,6 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
@@ -43,7 +42,6 @@
import com.odoo.core.service.OSyncAdapter;
import com.odoo.core.utils.BitmapUtils;
import com.odoo.core.utils.OResource;
-import com.odoo.core.utils.notification.ONotificationBuilder;
import odoo.Odoo;
import odoo.helper.OUser;
@@ -66,8 +64,6 @@ protected void onCreate(Bundle savedInstanceState) {
getSupportActionBar().hide();
action = getIntent().getAction();
mApp = (App) getApplicationContext();
- // 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);
@@ -131,7 +127,6 @@ public void onClick(DialogInterface dialog, int which) {
@Override
public void onClick(View v) {
- ONotificationBuilder.cancelNotification(this, OSyncAdapter.REQUEST_SIGN_IN_ERROR);
switch (v.getId()) {
case R.id.cancel:
finish();
diff --git a/app/src/main/java/com/odoo/core/account/OdooLogin.java b/app/src/main/java/com/odoo/core/account/OdooLogin.java
index 2b564a19..5dd37c52 100644
--- a/app/src/main/java/com/odoo/core/account/OdooLogin.java
+++ b/app/src/main/java/com/odoo/core/account/OdooLogin.java
@@ -54,14 +54,12 @@ public class OdooLogin extends AppCompatActivity implements View.OnClickListener
private Boolean mConnectedToServer = false;
private Boolean mAutoLogin = false;
private Boolean mRequestedForAccount = false;
- private AccountCreater accountCreator = null;
+ private AccountCreator accountCreator = null;
private Spinner databaseSpinner = null;
private List databases = new ArrayList<>();
private TextView mLoginProcessStatus = null;
- private TextView mTermsCondition;
private App mApp;
private Odoo mOdoo;
- private odoo.helper.OUser mUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -90,7 +88,7 @@ protected void onCreate(Bundle savedInstanceState) {
private void init() {
mLoginProcessStatus = (TextView) findViewById(R.id.login_process_status);
- mTermsCondition = (TextView) findViewById(R.id.termsCondition);
+ TextView mTermsCondition = (TextView) findViewById(R.id.termsCondition);
mTermsCondition.setMovementMethod(LinkMovementMethod.getInstance());
findViewById(R.id.btnLogin).setOnClickListener(this);
findViewById(R.id.forgot_password).setOnClickListener(this);
@@ -214,6 +212,8 @@ private void loginUser() {
}
if (databaseSpinner != null && databases.size() > 1 && databaseSpinner.getSelectedItemPosition() == 0) {
Toast.makeText(this, OResource.string(this, R.string.label_select_database), Toast.LENGTH_LONG).show();
+ findViewById(R.id.controls).setVisibility(View.VISIBLE);
+ findViewById(R.id.login_progress).setVisibility(View.GONE);
return;
}
@@ -243,7 +243,6 @@ private void loginUser() {
loginProcess(null, serverURL, databaseName);
} else {
mAutoLogin = true;
- Log.v("", "Testing URL: " + serverURL);
try {
Odoo.createInstance(OdooLogin.this, serverURL).setOnConnect(OdooLogin.this);
} catch (OdooVersionException e) {
@@ -347,7 +346,6 @@ private void loginProcess(final OdooInstance instance, String url, final String
@Override
public void onLoginSuccess(Odoo odoo, odoo.helper.OUser oUser) {
mOdoo = odoo;
- mUser = oUser;
mOdoo.getSaasInstances(new IOdooInstanceListener() {
@Override
public void onInstancesLoad(List odooInstances) {
@@ -409,7 +407,7 @@ public void onLoginSuccess(Odoo odoo, odoo.helper.OUser oUser) {
if (accountCreator != null) {
accountCreator.cancel(true);
}
- accountCreator = new AccountCreater();
+ accountCreator = new AccountCreator();
OUser user = new OUser();
user.setFromBundle(oUser.getAsBundle());
accountCreator.execute(user);
@@ -426,7 +424,7 @@ private void loginFail(OdooError error) {
edtUsername.setError(OResource.string(this, R.string.error_invalid_username_or_password));
}
- private class AccountCreater extends AsyncTask {
+ private class AccountCreator extends AsyncTask {
private OUser mUser;
diff --git a/app/src/main/java/com/odoo/core/auth/OdooAccountManager.java b/app/src/main/java/com/odoo/core/auth/OdooAccountManager.java
index f96814b9..1831de36 100644
--- a/app/src/main/java/com/odoo/core/auth/OdooAccountManager.java
+++ b/app/src/main/java/com/odoo/core/auth/OdooAccountManager.java
@@ -1,20 +1,20 @@
/**
* 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;
@@ -46,7 +46,7 @@ public class OdooAccountManager {
* @return List of OUser instances if any
*/
public static List getAllAccounts(Context context) {
- List users = new ArrayList();
+ List users = new ArrayList<>();
AccountManager aManager = AccountManager.get(context);
for (Account account : aManager.getAccountsByType(KEY_ACCOUNT_TYPE)) {
OUser user = new OUser();
diff --git a/app/src/main/java/com/odoo/core/auth/OdooAuthenticator.java b/app/src/main/java/com/odoo/core/auth/OdooAuthenticator.java
index b5ce7860..a83408b6 100644
--- a/app/src/main/java/com/odoo/core/auth/OdooAuthenticator.java
+++ b/app/src/main/java/com/odoo/core/auth/OdooAuthenticator.java
@@ -1,20 +1,20 @@
/**
* 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;
@@ -29,6 +29,7 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
+import com.odoo.App;
import com.odoo.core.account.OdooLogin;
import com.odoo.core.orm.OSQLite;
import com.odoo.core.support.OUser;
@@ -66,11 +67,12 @@ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Ac
&& !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();
+ App app = (App) mContext.getApplicationContext();
+ app.setOdoo(null, user);
}
}
return result;
diff --git a/app/src/main/java/com/odoo/core/orm/OModel.java b/app/src/main/java/com/odoo/core/orm/OModel.java
index 5c15c8f5..86d9a70f 100644
--- a/app/src/main/java/com/odoo/core/orm/OModel.java
+++ b/app/src/main/java/com/odoo/core/orm/OModel.java
@@ -19,13 +19,13 @@
*/
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.text.TextUtils;
import android.util.Log;
import com.odoo.App;
@@ -53,7 +53,6 @@
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;
@@ -77,8 +76,6 @@ public class OModel implements ISyncServiceListener {
public static final String TAG = OModel.class.getSimpleName();
public String BASE_AUTHORITY = App.APPLICATION_ID + ".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;
@@ -93,21 +90,6 @@ public class OModel implements ISyncServiceListener {
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
@@ -447,7 +429,7 @@ public String getModelName() {
}
public List getManyToManyColumns(OColumn column, OModel relation_model) {
- List cols = new ArrayList();
+ List cols = new ArrayList<>();
_write_date.setName("_write_date");
cols.add(_write_date);
_is_dirty.setName("_is_dirty");
@@ -712,7 +694,7 @@ public List select(String[] projection, String where, String[] args, S
} while (cr.moveToNext());
}
} finally {
- cr.close();
+ if (cr != null) cr.close();
}
return rows;
}
@@ -722,7 +704,7 @@ public Object getFunctionalMethodValue(OColumn column, Object record) {
Method method = column.getFunctionalMethod();
OModel model = this;
try {
- return method.invoke(model, new Object[]{record});
+ return method.invoke(model, record);
} catch (Exception e) {
e.printStackTrace();
}
@@ -734,7 +716,7 @@ public Object getOnChangeMethodValue(OColumn column, Object record) {
Method method = column.getOnChangeMethod();
OModel model = this;
try {
- return method.invoke(model, new Object[]{record});
+ return method.invoke(model, record);
} catch (Exception e) {
e.printStackTrace();
}
@@ -885,14 +867,14 @@ public boolean delete(int row_id) {
public boolean delete(int row_id, boolean permanently) {
int count = 0;
if (permanently)
- count = mContext.getContentResolver().delete(uri().withAppendedPath(uri(), row_id + ""), null, null);
+ 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;
+ return count > 0;
}
public int update(String selection, String[] args, OValues values) {
@@ -900,9 +882,9 @@ public int update(String selection, String[] args, OValues values) {
}
public boolean update(int row_id, OValues values) {
- int count = mContext.getContentResolver().update(uri().withAppendedPath(uri(), row_id + ""),
+ int count = mContext.getContentResolver().update(Uri.withAppendedPath(uri(), row_id + ""),
values.toContentValues(), null, null);
- return (count > 0) ? true : false;
+ return count > 0;
}
@@ -941,56 +923,141 @@ public int count(String selection, String[] args) {
}
- 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";
+ /**
+ * Handle record values for insert, update, delete operation with relation columns
+ * Each record have different behaviour, appending, deleting, unlink reference and
+ * replacing with new list
+ *
+ * @param record_id Main record id on which relation values affected
+ * @param column column object of the record (for relation column only)
+ * @param values values list with commands (@see RelCommands)
+ */
+ public void handleRelationValues(int record_id, OColumn column, RelValues values) {
+ OModel relModel = createInstance(column.getType());
+ HashMap> columnValues = values.getColumnValues();
+ for (RelCommands commands : columnValues.keySet()) {
+ switch (column.getRelationType()) {
+ case OneToMany:
+ handleOneToManyRecords(column, commands, relModel, record_id, columnValues);
+ break;
+ case ManyToMany:
+ handleManyToManyRecords(column, commands, relModel, record_id, columnValues);
+ break;
+ }
+ }
+ }
- 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();
+ private void handleOneToManyRecords(OColumn column, RelCommands commands, OModel relModel,
+ int record_id, HashMap> values) {
+ if (commands == RelCommands.Replace) {
+ // Force to unlink record even no any other record values available.
+ OValues old_values = new OValues();
+ old_values.put(column.getRelatedColumn(), 0);
+ int count = relModel.update(column.getRelatedColumn() + " = ?", new String[]{record_id + ""},
+ old_values);
+ Log.i(TAG, String.format("#%d references removed " + relModel.getModelName(), count));
+ }
+ for (Object rowObj : values.get(commands)) {
+ switch (commands) {
+ case Append:
+ OValues value;
+ if (rowObj instanceof OValues) {
+ value = (OValues) rowObj;
+ value.put(column.getRelatedColumn(), record_id);
+ relModel.insert(value);
+ } else {
+ int rec_id = (int) rowObj;
+ value = new OValues();
+ value.put(column.getRelatedColumn(), record_id);
+ relModel.update(rec_id, value);
+ }
+ break;
+ case Replace:
+ if (rowObj instanceof OValues) {
+ value = (OValues) rowObj;
+ value.put(column.getRelatedColumn(), record_id);
+ relModel.insert(value);
+ } else {
+ int rec_id = (int) rowObj;
+ value = new OValues();
+ value.put(column.getRelatedColumn(), record_id);
+ relModel.update(rec_id, value);
+ }
+ break;
+ case Delete:
+ relModel.delete((int) rowObj);
+ break;
+ case Unlink:
+ // Removing all older references
+ OValues old_update = new OValues();
+ old_update.put(column.getRelatedColumn(), 0);
+ relModel.update((int) rowObj, old_update);
+ break;
}
- } else {
- throw new InvalidObjectException("Column [" + column_name + "] not found in " + getModelName() + " model.");
+ }
+ }
+ private void handleManyToManyRecords(OColumn column, RelCommands command, OModel relModel,
+ int record_id, HashMap> values) {
+
+ String table = column.getRelTableName() != null ? column.getRelTableName() :
+ getTableName() + "_" + relModel.getTableName() + "_rel";
+ String base_column = column.getRelBaseColumn() != null ? column.getRelBaseColumn() :
+ getTableName() + "_id";
+ String rel_column = column.getRelRelationColumn() != null ? column.getRelRelationColumn() :
+ relModel.getTableName() + "_id";
+ SQLiteDatabase db = getWritableDatabase();
+ switch (command) {
+ case Append:
+ // Inserting each relation id with base record id to relation table
+ List