From cf27c2c9d3937587898cd485bfae3341c4d21ce8 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 19 Sep 2016 14:38:24 +0200 Subject: [PATCH 01/99] add database for synced folders --- src/com/owncloud/android/db/ProviderMeta.java | 15 ++++++++-- .../providers/FileContentProvider.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 6d7adbe4bee7..10fb9a55f5df 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -33,7 +33,7 @@ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 15; + public static final int DB_VERSION = 16; private ProviderMeta() { } @@ -43,6 +43,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final String CAPABILITIES_TABLE_NAME = "capabilities"; public static final String UPLOADS_TABLE_NAME = "list_of_uploads"; + public static final String SYNCED_FOLDERS_TABLE_NAME = "synced_folders"; public static final Uri CONTENT_URI = Uri.parse("content://" + MainApp.getAuthority() + "/"); public static final Uri CONTENT_URI_FILE = Uri.parse("content://" @@ -149,8 +150,16 @@ static public class ProviderTableMeta implements BaseColumns { public static final String UPLOADS_UPLOAD_END_TIMESTAMP = "upload_end_timestamp"; public static final String UPLOADS_LAST_RESULT = "last_result"; public static final String UPLOADS_CREATED_BY = "created_by"; - public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc"; + // Columns of synced folder table + public static final String SYNCED_FOLDER_LOCAL_PATH = "local_path"; + public static final String SYNCED_FOLDER_REMOTE_PATH = "remote_path"; + public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only"; + public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only"; + public static final String SYNCED_FOLDER_ENABLED = "enabled"; + public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; + public static final String SYNCED_FOLDER_ACCOUNT = "account"; + public static final String SYNCED_FOLDER_UPLOAD_OPTION = "upload_option"; } -} +} \ No newline at end of file diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 2a6584a6d6e2..06ccb0e53f8d 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -535,6 +535,8 @@ public void onCreate(SQLiteDatabase db) { // Create uploads table createUploadsTable(db); + // Create synced folders table + createSyncedFoldersTable(db); } @Override @@ -779,6 +781,19 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } + if (oldVersion < 16 && newVersion >= 16) { + Log_OC.i("SQL", "Entering in the #16 ADD synced folders table"); + db.beginTransaction(); + try { + // Create synced folders table + createSyncedFoldersTable(db); + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); @@ -892,6 +907,20 @@ private void createUploadsTable(SQLiteDatabase db){ */ } + private void createSyncedFoldersTable(SQLiteDatabase db){ + db.execSQL("CREATE TABLE " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " // id + + ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + " TEXT, " // local path + + ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " TEXT, " // remote path + + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + " INTEGER, " // wifi_only + + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only + + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action + ); + } + /** * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names * structure to include in it the path to the server instance. Updating the account names and path to local files From 30669c96778ba0f9b6df42a9b87afdb2335d2da2 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 20 Sep 2016 01:04:48 +0200 Subject: [PATCH 02/99] initial navigation setup for menu and activity --- AndroidManifest.xml | 2 + res/drawable-hdpi/ic_cloud_check.png | Bin 0 -> 820 bytes res/drawable-hdpi/ic_uploads.png | Bin 0 -> 284 bytes res/drawable-mdpi/ic_cloud_check.png | Bin 0 -> 608 bytes res/drawable-xhdpi/ic_cloud_check.png | Bin 0 -> 1032 bytes res/drawable-xxhdpi/ic_cloud_check.png | Bin 0 -> 1537 bytes res/drawable-xxxhdpi/ic_cloud_check.png | Bin 0 -> 2090 bytes res/layout/folder_sync_layout.xml | 65 +++++++++++++++++ res/menu/drawer_menu.xml | 5 ++ res/values/strings.xml | 1 + .../android/ui/activity/DrawerActivity.java | 4 ++ .../ui/activity/FolderSyncActivity.java | 68 ++++++++++++++++++ 12 files changed, 145 insertions(+) create mode 100644 res/drawable-hdpi/ic_cloud_check.png create mode 100644 res/drawable-hdpi/ic_uploads.png create mode 100644 res/drawable-mdpi/ic_cloud_check.png create mode 100644 res/drawable-xhdpi/ic_cloud_check.png create mode 100644 res/drawable-xxhdpi/ic_cloud_check.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_check.png create mode 100644 res/layout/folder_sync_layout.xml create mode 100644 src/com/owncloud/android/ui/activity/FolderSyncActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b55dc60f545d..68e07d655f9f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -74,8 +74,10 @@ + diff --git a/res/drawable-hdpi/ic_cloud_check.png b/res/drawable-hdpi/ic_cloud_check.png new file mode 100644 index 0000000000000000000000000000000000000000..50cf89679167342ba0acbf21c7d6c198838f82e3 GIT binary patch literal 820 zcmV-41Izr0P)_Iq^w?aan^2`v+2Nd--foH%kz-*RfSJE{7b5n()DC%!C z8s7om09)4UtE#U={s#dFit=GxCZETaS{0x7@*75_zvBBTKi)`GIO5i9TbrB~&h#?-f*;RWe~1mYj3*Kxv)K4|$$XCQ0%;EWf~qMP%-O zl3A{b$kRN}57g`R-;9WS+3t)Yaz<5W-zAe}*;qF+0IK?tG3II%MaBRlZHA&+t#-;e zH{L3xs0Hocd9eSH@J(lmYDlv34M=iK;4Wm=dtEFT-* z$2RBOd>DqMR;hwz0EWf){YSvwHUmr6+R6(c-djNRIto~=XqCFjuq3> z(_a{4ZUEa0oTOcz1N-ARUR?RwCrR=>Fbv%NFHJ<`fwlItIF1)#je}PZ1V?}gRUH9# y173j+j^*E_2da9xzrX)-6h-aqg*|%oD1StCl`zW0000|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3$$*xu=U`NW|f{SI=@DQ4nZ*n4heinc6L+ChD?b zMXqWuhb8N`4Ksd5C!N@nKG8jptz!|R(xRCiY5jZSfkJW4ZEOV&CE PbTWgdtDnm{r-UW|a57)t literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_cloud_check.png b/res/drawable-mdpi/ic_cloud_check.png new file mode 100644 index 0000000000000000000000000000000000000000..c48e3a91cb121ed8b0f2a522d646ca8088655301 GIT binary patch literal 608 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 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/drawer_menu.xml b/res/menu/drawer_menu.xml index 0679e5e987a1..0493f7680161 100644 --- a/res/menu/drawer_menu.xml +++ b/res/menu/drawer_menu.xml @@ -37,6 +37,11 @@ android:id="@+id/nav_uploads" android:icon="@drawable/ic_uploads" android:title="@string/drawer_item_uploads_list"/> + + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see . +--> diff --git a/res/layout/drawer_header.xml b/res/layout/drawer_header.xml index 8721fcdbad52..1e8f842587c9 100644 --- a/res/layout/drawer_header.xml +++ b/res/layout/drawer_header.xml @@ -1,21 +1,23 @@ + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see . +--> Date: Wed, 21 Sep 2016 15:26:41 +0200 Subject: [PATCH 04/99] sync config icons --- res/drawable-hdpi/ic_cloud_sync_off.png | Bin 0 -> 1047 bytes res/drawable-hdpi/ic_cloud_sync_on.png | Bin 0 -> 619 bytes res/drawable-mdpi/ic_cloud_sync_off.png | Bin 0 -> 722 bytes res/drawable-mdpi/ic_cloud_sync_on.png | Bin 0 -> 437 bytes res/drawable-xhdpi/ic_cloud_sync_off.png | Bin 0 -> 1357 bytes res/drawable-xhdpi/ic_cloud_sync_on.png | Bin 0 -> 793 bytes res/drawable-xxhdpi/ic_cloud_sync_off.png | Bin 0 -> 2010 bytes res/drawable-xxhdpi/ic_cloud_sync_on.png | Bin 0 -> 1189 bytes res/drawable-xxxhdpi/ic_cloud_sync_off.png | Bin 0 -> 2898 bytes res/drawable-xxxhdpi/ic_cloud_sync_on.png | Bin 0 -> 1539 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/drawable-hdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-hdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-mdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-mdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xhdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xxhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xxhdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_sync_on.png diff --git a/res/drawable-hdpi/ic_cloud_sync_off.png b/res/drawable-hdpi/ic_cloud_sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c0ec010e510eba45b119d5333d8abefcfffaa5 GIT binary patch literal 1047 zcmV+y1nB#TP)O=w(I6vuz}zL!XnBCQ%!M2TNetii;El180kjEHfg5NImhiOZ5W@6O9C z#Bnvr%)G=86w#e7s^Hf~{3xw1l2mD{)u0x-&?qV;R4F)u;k|oY%$xf1*_TO^RsO4Y z&;9z{bKX7Y!nU@xt<9BM$>{rjr?vJ4*LC+7i^ch+{UakISGle`1Uw9M1J?i+I0wuE zpN3)h&gA6epBo0sX0vx_t-k=;fmzpe`;t(e=RK>G$^$KHEi58`E2Um-YioPHP$>M9 z7Dz8OG&eWTi^%VBvoj3CqxpQkJ+aYAz`5i}mD&O1s@3W#&-3nV7^qk*&Wp%H!0DBs zf*|-9cpUf|ct&g8rM2EEB0H?Ldw@eqsc#ZX?p8`2Gsbi^6tcSV`TW&U6de(f?%1>} zd~2<1j5#PGuf(1L_{DLYuCcMPzt-iUgs09sj?wec52=KKDAQ51az z>`QU*^@IZ0y!sffK;4*uJ2YdJQ;it(~n@Dpi1dKHnaO;ZdM79+xV7 zg+jrtR;%ZMD`V5oB63n|eYjLAeE`T(pwAPY`3-oiTrQtpY4Jv(j4|IO<|K98T00a3 z!5o0j<#K%qn18j_1FOM6El;_w+efWLqz<2mTv(;6$8ntFp6A^RwS4gg@KoISa=BbS zL<%+PslmZPr=z2z19%b`B`!fqsZ*`3t@kKn%#YM=07|KzQmOQ18la6p)#dyCUTf_U zpfxt_*T4;lG2f?!fyTpE*ZBDOjEIyHrXihX;$}j*uKQM^d9S8+tLoab)lk)HHM##T z4NTQrB647>J=N0E(vfKXp_Ni^Ck#*fzJJ#iLmfV;a85)rl}hEOMeiYF%W$9qcfR;cV(^~fhLGZ&??!T=~(SJ$W)e=GN RCguPD002ovPDHLkV1iy(`o#bM literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_cloud_sync_on.png b/res/drawable-hdpi/ic_cloud_sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..24eae27f77a8ec8b17d4e46a72d6a85c8c1e21dd GIT binary patch literal 619 zcmV-x0+juUP)eqGO@E6X zRGB>p-W_1LyL;9JoR`OrJdX}!YblIQ0fXIPmivg4li1ADje{oUhX|VU!0sL~?cOr* zJ;jyWQl~*?-Dz6Q>k(i8_R<`<(ftak3fF4Kf%7Pb37?tX-p1c^IPU;9z3_J{Wc^_( zwHcHtlncQA2r=RHOmM05fOZu>hrvfU8X@2h$*GPIQAw4U9F!$-Tjy_?cO7dQOdqDs5=ug24EM;ZLAst=07V8=~3tqDi!12!9(kOIZW@E^fuD1`WugxD~7VWKgNbtyoFw;l^00O@khbn}c#L z{Fb@kBJd?4y!9T{#;mi?A>6ft9 zzfSqdIEFk*4rC9oEkZyGWD#@@sNynJtZb&eFe&Lj?+1PLh@{u_`D6RZRklSXaQ2xmmlTov#~qlJii#f!I#*tlq8 z-aBkW!A=`3QkaO4#E6y>usyL$Q1KryFGlXTwaB{=&*XB3fEIqMnc4UDGdnZ8@Lzvh zvf8oM7QFXYwbo~ybBh4RnB71L7y|YJ4PXwK^4>qJR4Q}r0BW`4y}tqsB}p>woa;`* zqrgpIKhOne-~dn-k&nh0+nVUQ0Il^o;1y74Lgwe^FN(+wrPP2@YNz*pn^I~3cnoBK zQEP3bqY?pxVb~P}!E+HA1ZK3>#b&u$WvxB!y&nUz-uq&uQkhuy1kSmvwf0&jlX)v5 z18KO>Qm$VVMbQgj1VBXYu9dJ#GwDf^WY&9sA2CcN=>*7WbrPQa?+yNX1Catx%0mK;d4mb)7$8kKiRvDag-AR&+1BG^tx7PM}@6Q8w zfGluIu{_@_EiFy6jygR_6h&X-IF5i@X=p^zp*n>C)M~XSsd-odRe*dxKe$zpAP8!y zSy#Z*)VUXi;jXQK1VL~iHQ!6ARLbRYxmgi8#PXP47m=yvB((vp+4yp~d`v_pfo!AE zI4J;O81BhrGR?cNS(}6TtKSPDj(q#+`j1HbIjv*e$-%i=<#q22Jc7AS#KroBWR$-l~ z4I4W3C%CypZxmNdSUC0QtTlmxEjj5AnQvq}T)x@y)6u2-N+nyj2-mx<5vD2%?~cXh ze42Xr>71GU4-aYlm;G6~sKmTiQlDY<0i6emB>^iHtJ>zi<+R>B^9-My)?xucVINdHUdE#QN7|LYMD4?d1(ITKM2! zsDHp-sWqqCW$v9{(3w4@HQ>U*ihD{+mmaTkd|B3XBt=&Bv-*zt;tM)Ex^u5G@ThH- z6qHfmeG<6$V5ZCu{eoGgEio=l|2VYS679q+8a5>}=$kP{uq~Bce85;jFG<8k;!^PQ z1I0QOqSrUre($P`cdO_UJ16M6`c^di{nbv_?d!v`IC8`@7Cs8vvTEZNBd-@V4EuIO V=Ec9gX8;UX22WQ%mvv4FO#snbtDFD; literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_cloud_sync_off.png b/res/drawable-xhdpi/ic_cloud_sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..a831bfa611fa411e022d0281ff3aa00af790286a GIT binary patch literal 1357 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_cloud_sync_on.png b/res/drawable-xxxhdpi/ic_cloud_sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..98f3503afa73adc67a69a27afe66ada1d93a4d43 GIT binary patch literal 1539 zcmV+e2K@PnP)y{(f}p|MFUZz2?8MmBJyT}kWkpxRg4KrftVPZAV?KyX>947&xf@sl-_7(=Cqyj zd)wLVcfR>f&+N>c*#nN_IF92uj^j9v<2a7vIF93tbjo%DecE?z6n7ivV00l!5%3H! z4HyHA2YjFjXhLW}$*-X6Q7TQcwU!MFn{4kwZ-!z zw-Y8|1(iid!U`W=VRqI|qMa+ibC=e-lX8aNwd$o`klyPPD^{2sza zV65$0Skg{Ho@R6EW>5k9cNXNOXQP|b2-}Ho@d0x1_0k{%_T2$hwM)>d4wz$m?}vzG zK?dvn5VS458d3X*;+Dnm(TZsqk5~L}5W#vbY_|9x;ELbXC=ySkf(q9C2&hWGic%*T zY3K66xdEyk%5?XE&Job=Z^9KnXbL5p&~-<8n%>Ng0PoUxbldN^4hYjpX8pE|k`n*H zjoA^9XnPY^{ID(Im+8B^Zp?Oou-tZbv7mUDZ5J0myvaL16`k9FyXcC_ZA@}T$F+h+ zC7;I?KT4C)tyLW_G|J4kol*R#x7m@I0-!<7w4F{2C22QnA^Ro#tnF-KNy$n)(HfX! zJC~T6hEC7bxVxSfC5qi-i&bW>Ci^32#|EWlYT$3%dBm15RfAl!okxs~)u8KbXAx%_ zOs(xCVysDH{4;>q&Lox;H#FXYTR@%dTw+O7eaCgcA=|mcR3_U2IUE7*+0G_*v^ksY zfU=@SgoC#8iJ1o4i|RUWMdT!Q1ARV7rDpE%u+|=`gOnKqQAf>pG5D;~WrxK6+Q};e2R*^c0$Wq*Q z!eE`o=r<@WxZm}SzNaKBQ(vK^0C#pYFv0;$%M!eTmhRr}{}XWsGP&IH-$AIrJ;>W1 zw-MI$wbsz%Ka!YzmUKN(ihH7>J8dAJU~($)3U2rH;Xy3cabY4lu>x2L6b>rZ$k2fB zB_ba_&LZK#6wRy2L**8uNP)~EyTp7R*%1=@!gmpC1MY!p0Is6U_lTUqq`vFvXGo6Y pIF92uj^j9v<2a7vIF94Q+kZgfjW%65jYR+e002ovPDHLkV1nEe;vN71 literal 0 HcmV?d00001 From c1c7d232aa0bfb30ac48bff3efeb19d8b911cf0a Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 21 Sep 2016 15:43:41 +0200 Subject: [PATCH 05/99] initial add for recycler view implementation with headers (WIP) --- res/layout/folder_sync_item_header.xml | 57 ++++++ res/layout/folder_sync_layout.xml | 27 +-- .../SectionedRecyclerViewAdapter.java | 184 ++++++++++++++++++ .../ui/activity/FolderSyncActivity.java | 46 +++++ .../android/ui/adapter/FolderSyncAdapter.java | 114 +++++++++++ 5 files changed, 409 insertions(+), 19 deletions(-) create mode 100644 res/layout/folder_sync_item_header.xml create mode 100644 src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java create mode 100644 src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml new file mode 100644 index 000000000000..152a713f6fe4 --- /dev/null +++ b/res/layout/folder_sync_item_header.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index ff763062dc10..28e861579c03 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -34,25 +34,14 @@ - - - - - - - - + diff --git a/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java b/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java new file mode 100644 index 000000000000..73965abb8ece --- /dev/null +++ b/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java @@ -0,0 +1,184 @@ +/** + * Copyright 2016 Aidan Follestad + * + * 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 com.afollestad.sectionedrecyclerview; + +import android.support.annotation.IntRange; +import android.support.annotation.Nullable; +import android.support.v4.util.ArrayMap; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.ViewGroup; + +import java.util.List; + +/** + * @author Aidan Follestad (afollestad) + */ +public abstract class SectionedRecyclerViewAdapter extends RecyclerView.Adapter { + + protected final static int VIEW_TYPE_HEADER = -2; + protected final static int VIEW_TYPE_ITEM = -1; + + private final ArrayMap mHeaderLocationMap; + private GridLayoutManager mLayoutManager; + private ArrayMap mSpanMap; + private boolean mShowHeadersForEmptySections; + + public SectionedRecyclerViewAdapter() { + mHeaderLocationMap = new ArrayMap<>(); + } + + public abstract int getSectionCount(); + + public abstract int getItemCount(int section); + + public abstract void onBindHeaderViewHolder(VH holder, int section); + + public abstract void onBindViewHolder(VH holder, int section, int relativePosition, int absolutePosition); + + public final boolean isHeader(int position) { + return mHeaderLocationMap.get(position) != null; + } + + /** + * Instructs the list view adapter to whether show headers for empty sections or not. + * + * @param show flag indicating whether headers for empty sections ought to be shown. + */ + public final void shouldShowHeadersForEmptySections(boolean show) { + mShowHeadersForEmptySections = show; + } + + public final void setLayoutManager(@Nullable GridLayoutManager lm) { + mLayoutManager = lm; + if (lm == null) return; + lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (isHeader(position)) + return mLayoutManager.getSpanCount(); + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + final int absPos = position - (sectionAndPos[0] + 1); + return getRowSpan(mLayoutManager.getSpanCount(), + sectionAndPos[0], sectionAndPos[1], absPos); + } + }); + } + + @SuppressWarnings("UnusedParameters") + protected int getRowSpan(int fullSpanSize, int section, int relativePosition, int absolutePosition) { + return 1; + } + + // returns section along with offsetted position + private int[] getSectionIndexAndRelativePosition(int itemPosition) { + synchronized (mHeaderLocationMap) { + Integer lastSectionIndex = -1; + for (final Integer sectionIndex : mHeaderLocationMap.keySet()) { + if (itemPosition > sectionIndex) { + lastSectionIndex = sectionIndex; + } else { + break; + } + } + return new int[]{mHeaderLocationMap.get(lastSectionIndex), itemPosition - lastSectionIndex - 1}; + } + } + + @Override + public final int getItemCount() { + int count = 0; + mHeaderLocationMap.clear(); + for (int s = 0; s < getSectionCount(); s++) { + int itemCount = getItemCount(s); + if (mShowHeadersForEmptySections || (itemCount > 0)) { + mHeaderLocationMap.put(count, s); + count += itemCount + 1; + } + } + return count; + } + + /** + * @hide + * @deprecated + */ + @Override + @Deprecated + public final int getItemViewType(int position) { + if (isHeader(position)) { + return getHeaderViewType(mHeaderLocationMap.get(position)); + } else { + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + return getItemViewType(sectionAndPos[0], + // offset section view positions + sectionAndPos[1], + position - (sectionAndPos[0] + 1)); + } + } + + @SuppressWarnings("UnusedParameters") + @IntRange(from = 0, to = Integer.MAX_VALUE) + public int getHeaderViewType(int section) { + //noinspection ResourceType + return VIEW_TYPE_HEADER; + } + + @SuppressWarnings("UnusedParameters") + @IntRange(from = 0, to = Integer.MAX_VALUE) + public int getItemViewType(int section, int relativePosition, int absolutePosition) { + //noinspection ResourceType + return VIEW_TYPE_ITEM; + } + + /** + * @hide + * @deprecated + */ + @Override + @Deprecated + public final void onBindViewHolder(VH holder, int position) { + StaggeredGridLayoutManager.LayoutParams layoutParams = null; + if (holder.itemView.getLayoutParams() instanceof GridLayoutManager.LayoutParams) + layoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + else if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) + layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); + if (isHeader(position)) { + if (layoutParams != null) layoutParams.setFullSpan(true); + onBindHeaderViewHolder(holder, mHeaderLocationMap.get(position)); + } else { + if (layoutParams != null) layoutParams.setFullSpan(false); + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + final int absPos = position - (sectionAndPos[0] + 1); + onBindViewHolder(holder, sectionAndPos[0], + // offset section view positions + sectionAndPos[1], absPos); + } + if (layoutParams != null) + holder.itemView.setLayoutParams(layoutParams); + } + + /** + * @hide + * @deprecated + */ + @Deprecated + @Override + public final void onBindViewHolder(VH holder, int position, List payloads) { + super.onBindViewHolder(holder, position, payloads); + } +} diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 21c305e6ae48..3cf1b29ec7d1 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -1,17 +1,45 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.activity; +import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; +import android.view.View; import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); + private RecyclerView mRecyclerView; + private FolderSyncAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { @@ -31,6 +59,24 @@ protected void onCreate(Bundle savedInstanceState) { private void setupContent() { // TODO setup/initialize UI + mRecyclerView = (RecyclerView) findViewById(android.R.id.list); + + final int gridWidth = 4; + mAdapter = new FolderSyncAdapter(this, gridWidth, new FolderSyncAdapter.ClickListener() { + @Override + public void onClick(View view, int section, int relative, int absolute) { + selectItem(FolderSyncActivity.this); + } + }, mRecyclerView); + + final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); + mAdapter.setLayoutManager(lm); + mRecyclerView.setLayoutManager(lm); + mRecyclerView.setAdapter(mAdapter); + } + + public static void selectItem(final Activity context) { + return; } @Override diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java new file mode 100644 index 000000000000..cde7473a15a8 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -0,0 +1,114 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; +import com.owncloud.android.R; + +import java.util.ArrayList; + +/** + * Adapter to display all auto-synced folders and/or instant upload media folders. + */ +public class FolderSyncAdapter extends SectionedRecyclerViewAdapter + implements View.OnClickListener, View.OnTouchListener { + + private static final String TAG = FolderSyncAdapter.class.getSimpleName(); + + private final Context mContext; + private final int mGridWidth; + private final ClickListener mListener; + private final ArrayList mCategories; + private final RecyclerView mRecyclerView; + + public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { + mContext = context; + mGridWidth = gridWidth * 2; + mListener = listener; + mCategories = new ArrayList<>(); + mRecyclerView = recyclerView; + } + + @Override + public void onClick(View v) { + + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + return false; + } + + @Override + public int getSectionCount() { + return 0; + } + + @Override + public int getItemCount(int section) { + return 0; + } + + @Override + public void onBindHeaderViewHolder(MainViewHolder holder, int section) { + + } + + @Override + public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { + + } + + @Override + public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return null; + } + + public interface ClickListener { + void onClick(View view, int section, int relative, int absolute); + } + + public static class MainViewHolder extends RecyclerView.ViewHolder { + + public MainViewHolder(View itemView) { + super(itemView); + image = (ImageView) itemView.findViewById(R.id.image); + title = (TextView) itemView.findViewById(R.id.title); + menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); + mSyncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + } + + final ImageView image; + final TextView title; + final ImageButton menuButton; + final ImageButton mSyncStatusButton; + } +} From 36280b92d687d585a195c4e7e41168cefa377809 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 16:59:52 +0200 Subject: [PATCH 06/99] rudimentary media loader implementation (file thumbnails and any action TBD) --- .../android/datamodel/MediaFolder.java | 14 +++ .../android/datamodel/MediaProvider.java | 93 +++++++++++++++++++ .../ui/activity/FolderSyncActivity.java | 54 +++++++++++ .../android/ui/adapter/FolderSyncAdapter.java | 93 ++++++++++++++++--- 4 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/MediaFolder.java create mode 100644 src/com/owncloud/android/datamodel/MediaProvider.java diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java new file mode 100644 index 000000000000..be41d8f84675 --- /dev/null +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -0,0 +1,14 @@ +package com.owncloud.android.datamodel; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Created by scherzia on 22.09.2016. + */ + +public class MediaFolder { + public String folder; + public String path; + public Collection filePaths = new ArrayList<>(); +} diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java new file mode 100644 index 000000000000..bf1d66d39686 --- /dev/null +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -0,0 +1,93 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; + +import android.app.Activity; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * media queries to gain access to media lists for the device. + */ +public class MediaProvider { + private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + /** + * TODO rewrite + * Getting All Images Path + * + * @param activity + * @return List with images folders + */ + public static List getAllShownImagesPath(Activity activity) { + Cursor cursor; + int column_index_data, column_index_folder_name, column_index_data_image; + ArrayList listOfAllImages = new ArrayList<>(); + String absolutePathOfImage = null; + String folderName = null; + + String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + //String[] projection = {MediaStore.Images.Media.BUCKET_DISPLAY_NAME,MediaStore.Images.Media.BUCKET_ID}; + String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore + .MediaColumns.DATA}; + String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; + String folderSelection = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " IS NOT NULL) GROUP BY (" + MediaStore + .Images.Media + .BUCKET_DISPLAY_NAME; + String fileSelection = MediaStore.MediaColumns.DATA + " LIKE "; + String folderSortOrder = "MAX(" + MediaStore.Images.Media.DISPLAY_NAME + ") DESC"; + String fileSortOrder = MediaStore.MediaColumns.DATA + " DESC LIMIT 8"; // LIMIT 8 + + cursor = activity.getContentResolver().query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); + + column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + column_index_folder_name = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + + List mediaFolders = new ArrayList<>(); + while (cursor.moveToNext()) { + MediaFolder mediaFolder = new MediaFolder(); + absolutePathOfImage = cursor.getString(column_index_data); + folderName = cursor.getString(column_index_folder_name); + mediaFolder.path = folderName; + mediaFolder.folder = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + folderName.length()); + mediaFolder.filePaths = new ArrayList<>(); + + Cursor cursorImages = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + fileSortOrder); + column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + Log.e("READ IMAGES", "Reading images for --> " + mediaFolder.folder); + while (cursorImages.moveToNext()) { + mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); + } + + mediaFolders.add(mediaFolder); + } + + return mediaFolders; + } +} diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 3cf1b29ec7d1..fe26e36d1718 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -24,15 +24,24 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import java.util.List; +import java.util.TimerTask; + /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ @@ -40,6 +49,8 @@ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; + private ProgressBar mProgress; + private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { @@ -57,6 +68,7 @@ protected void onCreate(Bundle savedInstanceState) { setupContent(); } + private void setupContent() { // TODO setup/initialize UI mRecyclerView = (RecyclerView) findViewById(android.R.id.list); @@ -73,12 +85,54 @@ public void onClick(View view, int section, int relative, int absolute) { mAdapter.setLayoutManager(lm); mRecyclerView.setLayoutManager(lm); mRecyclerView.setAdapter(mAdapter); + + load(); } public static void selectItem(final Activity context) { + // TODO implement selectItem() return; } + private void load() { + if (mAdapter.getItemCount() > 0) return; + setListShown(false); + final Handler mHandler = new Handler(); + new Thread(new Runnable() { + @Override + public void run() { + final List mediaFolders = MediaProvider.getAllShownImagesPath(FolderSyncActivity.this); + + for (MediaFolder mediaFolder : mediaFolders) { + Log.d(TAG, mediaFolder.path); + } + + mHandler.post(new TimerTask() { + @Override + public void run() { + mAdapter.setMediaFolders(mediaFolders); + setListShown(true); + } + }); + } + }).start(); + } + + void setListShown(boolean shown) { + if (mRecyclerView != null) { + mRecyclerView.setVisibility(shown ? + View.VISIBLE : View.GONE); + + // TODO show/hide loading visuals + /** + mProgress.setVisibility(shown ? + View.GONE : View.VISIBLE); + mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? + View.VISIBLE : View.GONE); + **/ + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { boolean retval; diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index cde7473a15a8..b83c92bda10b 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -23,6 +23,8 @@ import android.content.Context; import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -32,8 +34,10 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; import java.util.ArrayList; +import java.util.List; /** * Adapter to display all auto-synced folders and/or instant upload media folders. @@ -46,20 +50,26 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mCategories; + private final List mMediaFolders; private final RecyclerView mRecyclerView; public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { mContext = context; mGridWidth = gridWidth * 2; mListener = listener; - mCategories = new ArrayList<>(); + mMediaFolders = new ArrayList<>(); mRecyclerView = recyclerView; } + public void setMediaFolders(List mediaFolders) { + mMediaFolders.clear(); + mMediaFolders.addAll(mediaFolders); + notifyDataSetChanged(); + } + @Override public void onClick(View v) { - + Log.d(TAG, v.getTag().toString()); } @Override @@ -69,46 +79,107 @@ public boolean onTouch(View v, MotionEvent event) { @Override public int getSectionCount() { - return 0; + return mMediaFolders.size(); } @Override public int getItemCount(int section) { - return 0; + return mMediaFolders.get(section).filePaths.size(); } @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - + holder.title.setText(mMediaFolders.get(section).folder); + holder.syncStatusButton.setVisibility(View.VISIBLE); + holder.syncStatusButton.setTag(section); + holder.syncStatusButton.setOnTouchListener(this); + holder.menuButton.setVisibility(View.VISIBLE); + holder.menuButton.setTag(section); + holder.menuButton.setOnTouchListener(this); } @Override public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { + final Context c = holder.itemView.getContext(); + + /** + if (BitmapUtils.isImage(file)){ + // Thumbnail in Cache? + Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + String.valueOf(file.hashCode()) + ); + if (thumbnail != null){ + fileIcon.setImageBitmap(thumbnail); + } else { + + // generate new Thumbnail + if (allowedToCreateNewThumbnail) { + final ThumbnailsCacheManager.ThumbnailGenerationTask task = + new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon); + if (thumbnail == null) { + if (BitmapUtils.isVideo(file)) { + thumbnail = ThumbnailsCacheManager.mDefaultVideo; + } else { + thumbnail = ThumbnailsCacheManager.mDefaultImg; + } + } + final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncThumbnailDrawable( + mContext.getResources(), + thumbnail, + task + ); + fileIcon.setImageDrawable(asyncDrawable); + task.execute(file); + Log_OC.v(TAG, "Executing task to generate a new thumbnail"); + + } // else, already being generated, don't restart it + } + } else { + fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); + } + */ + holder.image.setImageResource(R.drawable.file_image); + + /** + if (res == 0) { + holder.image.setBackgroundColor(Color.parseColor("#40000000")); + } else { + Glide.with(c) + .fromResource() + .load(res) + .into(holder.image); + } + */ + //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); + holder.itemView.setOnClickListener(this); } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return null; + View v = LayoutInflater.from(parent.getContext()).inflate( + viewType == VIEW_TYPE_HEADER ? R.layout.folder_sync_item_header : R.layout.grid_image, parent, false); + return new MainViewHolder(v); } public interface ClickListener { void onClick(View view, int section, int relative, int absolute); } - public static class MainViewHolder extends RecyclerView.ViewHolder { + static class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View itemView) { super(itemView); - image = (ImageView) itemView.findViewById(R.id.image); + image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); - mSyncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); } final ImageView image; final TextView title; final ImageButton menuButton; - final ImageButton mSyncStatusButton; + final ImageButton syncStatusButton; } } From 4c31ca1ebe7a731009be8510ab113ab308eef64b Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 17:48:06 +0200 Subject: [PATCH 07/99] initial v1 for grid view --- res/drawable-hdpi/ic_dots_vertical.png | Bin 0 -> 260 bytes res/drawable-mdpi/ic_dots_vertical.png | Bin 0 -> 204 bytes res/drawable-xhdpi/ic_dots_vertical.png | Bin 0 -> 330 bytes res/drawable-xxhdpi/ic_dots_vertical.png | Bin 0 -> 486 bytes res/drawable-xxxhdpi/ic_dots_vertical.png | Bin 0 -> 597 bytes res/layout/folder_sync_item_header.xml | 28 ++++--- res/layout/folder_sync_layout.xml | 1 - res/layout/grid_sync_item.xml | 74 ++++++++++++++++++ .../android/ui/adapter/FolderSyncAdapter.java | 9 ++- 9 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 res/drawable-hdpi/ic_dots_vertical.png create mode 100644 res/drawable-mdpi/ic_dots_vertical.png create mode 100644 res/drawable-xhdpi/ic_dots_vertical.png create mode 100644 res/drawable-xxhdpi/ic_dots_vertical.png create mode 100644 res/drawable-xxxhdpi/ic_dots_vertical.png create mode 100644 res/layout/grid_sync_item.xml diff --git a/res/drawable-hdpi/ic_dots_vertical.png b/res/drawable-hdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..0e50d191680a12dcc3150ca486dbda9a3df53d72 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3$%m-_yl0B;xSfD;v3*9Rye|SWYzg-Q85L7{Oxx zL$ztYMQ`hLHP@ib7=4!+;d*zBWKJ_E1%-9llzo>zDd_N7>TFtB`S+(U&RGd{JYjbD zZuZJ!9mB81)~h`hObObZ{%x~^Y75iTIm>M~=eOjYIKwtc)ULm((}6TtKSPDj(q%wSxTlL_h{y5d1PNAVUWv}oh^VMnM-Ln@U_N#B z#k+U++Wu5b(;bMqOD7Ra%V4cCN it`0_J6H(q;28Itw(vk(IqZa|KVDNPHb6Mw<&;$VAb2mKz literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_dots_vertical.png b/res/drawable-xhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..d92e2e76066e0d8e604224dc0b98af9696ac45e6 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCwj^(N7a$D;Kb?2i11Zh|kH}&m z?E%JaC$sH9f@KAc=|CE+pW)oQo^T-Ns;7%%NXEUl*EaGVG7w?8@cSFvN@nc~Y0N7m zk9tTdam6*y<6=9(aE76j%S+|7$x5eLF7_53)l2=0YJ5u=1O@*Z&wL#8{LZPUYL*SJ z`}7oBn!o?s_k$@l@|oc=>4r7pTa`AgT=i<w=Vmty?|gLckLrF zuULjVPA|C&xO)%kx_o)OqxIL0)*x|)?E$}-?l>hEa0?1v^si=)vaA;sd2sD9(1Q$~ Lu6{1-oD!M<$c1%M literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_dots_vertical.png b/res/drawable-xxhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..205a9a7ab4613b20f62f91f93cdf3d4b9c1a32c3 GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Y)RhkE=uJQvb0|R5Br;B4q#jUqD4E>k_MOq(zo^nFua=>nusHt+oZhtfX^|y|VvMKN-=9BKkNudi>}Bq>)YbLY7cxw&^0r3xPdZm`IdlE> z*QZaWe0uiPZF^(e;l$l{^Xyv%&Y9gkDj}X48TX_zu4>Qc1L<;_o|E>O_Nf4+CaJuv z-g`M$!Sg!r-D{_c7ff!;%YJHk!S932i>(W9M^*Bb+`o{!_70m?SCUk6OhmciG1owm z;PvIZc&9wCj0_F+zf^Ph=AKon=3TE>@U;5!YlF(!w9V)9wpVYDT6@o#L)KH)Gk(&% z!W(>#~2=K5tL{1o1sx{an^LB{Ts5m}S3> literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_dots_vertical.png b/res/drawable-xxxhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..dabff13651ceca58f90a0981eb59e124fc6b8e18 GIT binary patch literal 597 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE(}8pX7+2iB`GtXj@qwp{V@SoVx3>)am>eaJe%$VzWE>)Q;6`@h zL%}J>oFgQ-y?Hs`Zc~wdYjUK5fj{A?n9M<@>c^4`CT>W67^Ws6cDT;?^#5=nJEJqV zb<;kn0L=!23uT>OnO;fpu(78`#(7yy+!9~=M{%Xc3e+e0k8ad~)i|>0f7s3%W8~eYXF_^Of<{+Z zZ=I3r^?}jjmhr4y7aqnfQ9O(_QfJMSelSc@sf5T)N&Tskx$)SGMYDLLJ}^G1wEK9k z|JAw5_wEblGcMs^ny@5qd$sP$6^21TtD~PcT;B1qV$X}ORp&qdtO>ugaoYR`-k#sj z?ffk9u5k&+I;Fbo+odMJP-u92*zrt+3F8HBBNzP>5_=V#zcj58_s~BfwpXG0OVb8R zLly`96GD3xlD{4=+PSmoIEf;CcqLfi!1 zD#vdxSQCqrI2z(6s8%^1d%-HXSX_hw%wk=Vd;KCf27ttZzxx=QN==Mbl + android:layout_height="wrap_content" + android:padding="@dimen/standard_padding"> + @@ -48,10 +54,12 @@ android:id="@+id/settingsButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_marginBottom="@dimen/standard_half_margin" + android:layout_marginLeft="@dimen/standard_half_margin" android:layout_marginTop="@dimen/standard_half_margin" + android:layout_marginBottom="@dimen/standard_half_margin" + android:background="@color/transparent" android:clickable="true" - android:src="@drawable/ic_settings"/> + android:src="@drawable/ic_dots_vertical"/> + \ No newline at end of file diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index 28e861579c03..da581448d028 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -39,7 +39,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" - android:padding="@dimen/standard_padding" android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical"/> diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml new file mode 100644 index 000000000000..7641367fc557 --- /dev/null +++ b/res/layout/grid_sync_item.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index b83c92bda10b..9588688e7913 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -23,7 +23,6 @@ import android.content.Context; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -69,7 +68,7 @@ public void setMediaFolders(List mediaFolders) { @Override public void onClick(View v) { - Log.d(TAG, v.getTag().toString()); + } @Override @@ -89,7 +88,8 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - holder.title.setText(mMediaFolders.get(section).folder); + holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder + .lastIndexOf("/")+1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); holder.syncStatusButton.setOnTouchListener(this); @@ -159,7 +159,8 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate( - viewType == VIEW_TYPE_HEADER ? R.layout.folder_sync_item_header : R.layout.grid_image, parent, false); + viewType == VIEW_TYPE_HEADER ? + R.layout.folder_sync_item_header : R.layout.grid_sync_item, parent, false); return new MainViewHolder(v); } From c0afddd5d813f06a9d65bf64e6d9187487a08fcb Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 18:46:40 +0200 Subject: [PATCH 08/99] proper thumbnails, image counter, darkening etc. --- res/layout/grid_sync_item.xml | 13 +++- .../android/datamodel/MediaFolder.java | 5 +- .../android/datamodel/MediaProvider.java | 10 ++- .../android/ui/adapter/FolderSyncAdapter.java | 69 ++++++++++++------- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml index 7641367fc557..988bd1c03b9c 100644 --- a/res/layout/grid_sync_item.xml +++ b/res/layout/grid_sync_item.xml @@ -40,6 +40,15 @@ android:scaleType="centerCrop" android:src="@drawable/ic_menu_archive"/> + + @@ -65,7 +74,7 @@ android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:text=">" - android:textColor="#cccccccc" + android:textColor="#dedede" android:textSize="22dp" android:textStyle="bold"/> diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index be41d8f84675..3997210862ba 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -1,7 +1,7 @@ package com.owncloud.android.datamodel; import java.util.ArrayList; -import java.util.Collection; +import java.util.List; /** * Created by scherzia on 22.09.2016. @@ -10,5 +10,6 @@ public class MediaFolder { public String folder; public String path; - public Collection filePaths = new ArrayList<>(); + public List filePaths = new ArrayList<>(); + public long numberOfFiles; } diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index bf1d66d39686..76d5bdd3af52 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -76,8 +76,11 @@ public static List getAllShownImagesPath(Activity activity) { mediaFolder.folder = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + folderName.length()); mediaFolder.filePaths = new ArrayList<>(); + // TODO: This can be done with one query, no limit, but only adding the 8 to the list and still get the + // total count + Cursor cursorImages = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + "'" + - absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, fileSortOrder); column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); Log.e("READ IMAGES", "Reading images for --> " + mediaFolder.folder); @@ -85,6 +88,11 @@ public static List getAllShownImagesPath(Activity activity) { mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); } + mediaFolder.numberOfFiles = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + null).getCount(); + mediaFolders.add(mediaFolder); } diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 9588688e7913..b8fd81845a15 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -22,6 +22,7 @@ package com.owncloud.android.ui.adapter; import android.content.Context; +import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -29,12 +30,18 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.MimetypeIconUtil; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -89,7 +96,7 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder - .lastIndexOf("/")+1, mMediaFolders.get(section).folder.length())); + .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); holder.syncStatusButton.setOnTouchListener(this); @@ -102,20 +109,35 @@ public void onBindHeaderViewHolder(MainViewHolder holder, int section) { public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { final Context c = holder.itemView.getContext(); - /** - if (BitmapUtils.isImage(file)){ + File file = new File(mMediaFolders.get(section).filePaths.get(relativePosition)); + + /** Cancellation needs do be checked and done before changing the drawable in fileIcon, or + * {@link ThumbnailsCacheManager#cancelPotentialThumbnailWork} will NEVER cancel any task. + **/ + boolean allowedToCreateNewThumbnail = (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, holder.image)); + + if (!file.isDirectory()) { + holder.image.setImageResource(R.drawable.file); + } else { + holder.image.setImageResource(R.drawable.ic_menu_archive); + } + // set proper tag + holder.image.setTag(file.hashCode()); + + // get Thumbnail if file is image + if (BitmapUtils.isImage(file)) { // Thumbnail in Cache? Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( String.valueOf(file.hashCode()) ); - if (thumbnail != null){ - fileIcon.setImageBitmap(thumbnail); + if (thumbnail != null) { + holder.image.setImageBitmap(thumbnail); } else { // generate new Thumbnail if (allowedToCreateNewThumbnail) { final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon); + new ThumbnailsCacheManager.ThumbnailGenerationTask(holder.image); if (thumbnail == null) { if (BitmapUtils.isVideo(file)) { thumbnail = ThumbnailsCacheManager.mDefaultVideo; @@ -129,28 +151,24 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos thumbnail, task ); - fileIcon.setImageDrawable(asyncDrawable); + holder.image.setImageDrawable(asyncDrawable); task.execute(file); Log_OC.v(TAG, "Executing task to generate a new thumbnail"); } // else, already being generated, don't restart it } } else { - fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); + holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); } - */ - holder.image.setImageResource(R.drawable.file_image); - /** - if (res == 0) { - holder.image.setBackgroundColor(Color.parseColor("#40000000")); + if(mMediaFolders.get(section).numberOfFiles > 8 && relativePosition >= 8-1) { + holder.counterValue.setText(Long.toString(mMediaFolders.get(section).numberOfFiles-8)); + holder.counterBar.setVisibility(View.VISIBLE); + holder.thumbnailDarkener.setVisibility(View.VISIBLE); } else { - Glide.with(c) - .fromResource() - .load(res) - .into(holder.image); + holder.counterBar.setVisibility(View.GONE); + holder.thumbnailDarkener.setVisibility(View.GONE); } - */ //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); holder.itemView.setOnClickListener(this); @@ -169,6 +187,13 @@ public interface ClickListener { } static class MainViewHolder extends RecyclerView.ViewHolder { + final ImageView image; + final TextView title; + final ImageButton menuButton; + final ImageButton syncStatusButton; + final LinearLayout counterBar; + final TextView counterValue; + final ImageView thumbnailDarkener; public MainViewHolder(View itemView) { super(itemView); @@ -176,11 +201,9 @@ public MainViewHolder(View itemView) { title = (TextView) itemView.findViewById(R.id.title); menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); + counterValue = (TextView) itemView.findViewById(R.id.counter); + thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); } - - final ImageView image; - final TextView title; - final ImageButton menuButton; - final ImageButton syncStatusButton; } } From 038f3167ab58ba2701167bef5272a52a711363eb Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 19:11:41 +0200 Subject: [PATCH 09/99] initial link of menu buttons --- res/layout/folder_sync_item_header.xml | 27 ++++++++++--------- res/layout/grid_sync_item.xml | 16 +++++------ .../android/ui/adapter/FolderSyncAdapter.java | 25 ++++++++++++++--- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index 52216afc0016..49f7593bc9b3 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -21,7 +21,9 @@ + android:paddingLeft="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding" + android:paddingBottom="@dimen/standard_padding"> - + \ No newline at end of file diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml index 988bd1c03b9c..332a212d87bc 100644 --- a/res/layout/grid_sync_item.xml +++ b/res/layout/grid_sync_item.xml @@ -58,23 +58,21 @@ android:orientation="horizontal"> diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index b8fd81845a15..99fe8b40a6e0 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,6 +32,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; @@ -95,14 +96,30 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { + final int sectionId = section; holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnTouchListener(this); + holder.syncStatusButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + Toast.makeText(mContext, "Sync Status Clicked for " + mMediaFolders.get(sectionId).folder, Toast + .LENGTH_SHORT) + .show(); + return true; + } + }); holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); - holder.menuButton.setOnTouchListener(this); + holder.menuButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + Toast.makeText(mContext, "Menu Clicked for " + mMediaFolders.get(sectionId).folder, Toast.LENGTH_SHORT) + .show(); + return true; + } + }); } @Override @@ -199,8 +216,8 @@ public MainViewHolder(View itemView) { super(itemView); image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); - menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); - syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); counterValue = (TextView) itemView.findViewById(R.id.counter); thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); From 27cb68bee3e475ea03324c1f484ca8dea1bc5537 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 20 Sep 2016 01:04:48 +0200 Subject: [PATCH 10/99] initial navigation setup for menu and activity --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index fe26e36d1718..d914563f1101 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -30,8 +30,6 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -49,8 +47,6 @@ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; - private ProgressBar mProgress; - private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { From d2aa22f6c025ca0a76165d32df418adcb8d6f5a9 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 21 Sep 2016 15:43:41 +0200 Subject: [PATCH 11/99] initial add for recycler view implementation with headers (WIP) --- .../owncloud/android/ui/activity/FolderSyncActivity.java | 3 +++ src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index d914563f1101..5641ee1433c4 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -28,6 +28,8 @@ import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; @@ -39,6 +41,7 @@ import java.util.List; import java.util.TimerTask; +import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 99fe8b40a6e0..d96e87da0093 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,17 +32,10 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; -import com.owncloud.android.datamodel.MediaFolder; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.utils.BitmapUtils; -import com.owncloud.android.utils.MimetypeIconUtil; -import java.io.File; import java.util.ArrayList; import java.util.List; From 12e4532a0df3dab8df607ca0452d3f35f65972ab Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 22 Sep 2016 20:14:25 +0200 Subject: [PATCH 12/99] wip --- AndroidManifest.xml | 1 + src/com/owncloud/android/MainApp.java | 6 ++ .../providers/FileContentProvider.java | 12 ++++ .../observer/SyncedFolderObserver.java | 49 +++++++++++++++ .../observer/SyncedFolderObserverService.java | 59 +++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 src/com/owncloud/android/services/observer/SyncedFolderObserver.java create mode 100644 src/com/owncloud/android/services/observer/SyncedFolderObserverService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 68e07d655f9f..058270823dca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -119,6 +119,7 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> + diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index c9e2342cd076..64e59541bb97 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; @@ -33,6 +34,7 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.services.observer.SyncedFolderObserverService; /** @@ -85,6 +87,10 @@ public void onCreate(){ Log_OC.d("Debug", "start logging"); } + Log_OC.d("SyncedFolderObserverService", "Start service SyncedFolderObserverService"); + Intent i = new Intent(this, SyncedFolderObserverService.class); + startService(i); + // register global protection with pass code registerActivityLifecycleCallbacks( new ActivityLifecycleCallbacks() { diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 06ccb0e53f8d..d4d89920ec93 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -919,6 +919,18 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action ); + + // TODO Tobi remove after testing + db.execSQL("INSERT INTO " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + "(" + + ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + ", " // local path + + ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + ", " // remote path + + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + ", " // wifi_only + + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + ", " // charging only + + ProviderTableMeta.SYNCED_FOLDER_ENABLED + ", " // enabled + + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action + + "VALUES ('/sdcard/DCIM/', 'syncTest', 0, 0, 1, 1, 'tobi', 1)"); } /** diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java new file mode 100644 index 000000000000..fc44335797f6 --- /dev/null +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -0,0 +1,49 @@ +package com.owncloud.android.services.observer; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.os.FileObserver; +import android.util.Log; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.RecursiveFileObserver; + +import java.io.File; + +public class SyncedFolderObserver extends RecursiveFileObserver { + + File dirToWatch; + String remoteFolder; + Context context; + + public static final int MY_BACKGROUND_JOB = 0; + + + public SyncedFolderObserver(String path, String remoteFolder) { + super(path, FileObserver.CREATE + FileObserver.MOVED_TO); + + context = MainApp.getAppContext(); + dirToWatch = new File(path); + this.remoteFolder = remoteFolder; + Log_OC.d("SyncedFolderObserver", "Started watching: "+ path); + } + + + + @Override + public void onEvent(int event, String path) { + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); +// JobInfo job = new JobInfo.Builder( +// MY_BACKGROUND_JOB, +// new ComponentName(context, MyJobService.class)) +// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) +// .setRequiresCharging(true) +// .build(); +// js.schedule(job); + + Log.d("SyncedFolder", "Event: " + event + " Path: " + path); + } +} diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java new file mode 100644 index 000000000000..7f172dc35efb --- /dev/null +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -0,0 +1,59 @@ +package com.owncloud.android.services.observer; + +import java.io.File; +import java.util.HashSet; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.widget.Toast; + +public class SyncedFolderObserverService extends Service { + private SyncedFolderObserver fileOb; + private static final String TAG = "InstantUploadFolderObserverService"; + + @Override + public void onCreate() { + File sdcard = new File("/mnt/sdcard/DCIM/"); + Log_OC.d("SyncedFolderObserverService", "watching file: " + sdcard.getAbsolutePath()); + fileOb = new SyncedFolderObserver(sdcard.getAbsolutePath(), "WhatsApp"); + fileOb.startWatching(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // TODO Auto-generated method stub + Log_OC.d("SyncedFolderObserverService", "start"); + return Service.START_NOT_STICKY; + //return super.onStartCommand(intent, flags, startId); + + } + + @Override + public void onStart(Intent intent, int startid) { + Log_OC.d("SyncedFolderObserverService", "start"); + fileOb.startWatching(); + /*for (int i = 0; i < fileOb_list.size(); ++i) { + fileOb_list.get(i).startWatching(); + }*/ + Toast.makeText(this.getApplicationContext(), "start monitoring file modification", Toast.LENGTH_SHORT).show(); + } + @Override + public void onDestroy() { + fileOb.stopWatching(); + /*for (int i = 0; i < fileOb_list.size(); ++i) { + fileOb_list.get(i).stopWatching(); + }*/ + Toast.makeText(this.getApplicationContext(), "stop monitoring file modification", Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } +} From 59da136b9b087e092bc56eba80caf9861e2cf3e9 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 22 Sep 2016 20:43:20 +0200 Subject: [PATCH 13/99] wip --- src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index d96e87da0093..99fe8b40a6e0 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,10 +32,17 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.MimetypeIconUtil; +import java.io.File; import java.util.ArrayList; import java.util.List; From 281f97086954a21f78bbd47b533f46cd32a4c421 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Sun, 25 Sep 2016 21:51:13 +0200 Subject: [PATCH 14/99] wip --- AndroidManifest.xml | 4 + .../android/datamodel/SyncedFolder.java | 83 +++++++++++++++ .../android/db/PreferenceManager.java | 2 +- src/com/owncloud/android/db/ProviderMeta.java | 2 + .../files/InstantUploadBroadcastReceiver.java | 8 +- .../providers/FileContentProvider.java | 16 ++- .../services/SyncedFolderJobService.java | 97 +++++++++++++++++ .../observer/SyncedFolderObserver.java | 48 ++++++--- .../observer/SyncedFolderObserverService.java | 100 ++++++++++++------ .../ui/activity/FolderSyncActivity.java | 2 +- .../android/utils/FileStorageUtils.java | 12 +-- 11 files changed, 312 insertions(+), 62 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/SyncedFolder.java create mode 100644 src/com/owncloud/android/services/SyncedFolderJobService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 058270823dca..ab0c601feb09 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -131,6 +131,10 @@ android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter_files" /> + . + */ + +package com.owncloud.android.datamodel; + +public class SyncedFolder { + private long id; + private String localPath; + private String remotePath; + private Boolean wifiOnly; + private Boolean chargingOnly; + private Boolean subfolderByDate; + private String account; + private Integer uploadAction; + private boolean enabled; + + public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { + this.id = id; + this.localPath = localPath; + this.remotePath = remotePath; + this.wifiOnly = wifiOnly; + this.chargingOnly = chargingOnly; + this.subfolderByDate = subfolderByDate; + this.account = account; + this.uploadAction = uploadAction; + this.enabled = enabled; + } + + public long getId() { + return id; + } + + public String getLocalPath() { + return localPath; + } + + public String getRemotePath() { + return remotePath; + } + + public Boolean getWifiOnly() { + return wifiOnly; + } + + public Boolean getChargingOnly() { + return chargingOnly; + } + + public Boolean getSubfolderByDate() { + return subfolderByDate; + } + + public String getAccount() { + return account; + } + + public Integer getUploadAction() { + return uploadAction; + } + + public boolean isEnabled() { + return enabled; + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/db/PreferenceManager.java b/src/com/owncloud/android/db/PreferenceManager.java index fd0ce8a9828f..4c2de4ebcb69 100644 --- a/src/com/owncloud/android/db/PreferenceManager.java +++ b/src/com/owncloud/android/db/PreferenceManager.java @@ -174,7 +174,7 @@ private static void saveIntPreference(Context context, String key, int value) { appPreferences.apply(); } - private static SharedPreferences getDefaultSharedPreferences(Context context) { + public static SharedPreferences getDefaultSharedPreferences(Context context) { return android.preference.PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); } } diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 10fb9a55f5df..0a33735dfd25 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -56,6 +56,8 @@ static public class ProviderTableMeta implements BaseColumns { + MainApp.getAuthority() + "/capabilities"); public static final Uri CONTENT_URI_UPLOADS = Uri.parse("content://" + MainApp.getAuthority() + "/uploads"); + public static final Uri CONTENT_URI_SYNCED_FOLDERS = Uri.parse("content://" + + MainApp.getAuthority() + "/synced_folders"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index c1bf93f3be64..cb0d13c22023 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -32,6 +32,7 @@ import android.provider.MediaStore.Video; import android.support.v4.content.ContextCompat; +import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.db.PreferenceManager; import com.owncloud.android.files.services.FileUploader; @@ -131,12 +132,17 @@ private void handleNewPictureAction(Context context, Intent intent) { new FileUploader.UploadRequester(); int behaviour = getUploadBehaviour(context); + Boolean subfolderByDate = com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context); + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); + String uploadPathdef = context.getString(R.string.instant_upload_path); + String uploadPath = pref.getString("instant_upload_path", uploadPathdef); + FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); requester.uploadNewFile( context, account, file_path, - FileStorageUtils.getInstantUploadFilePath(context, file_name, date_taken), + FileStorageUtils.getInstantUploadFilePath(uploadPath, file_name, date_taken, subfolderByDate), behaviour, mime_type, true, // create parent folder if not existent diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index d4d89920ec93..93905d4d2866 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -69,6 +69,7 @@ public class FileContentProvider extends ContentProvider { private static final int SHARES = 4; private static final int CAPABILITIES = 5; private static final int UPLOADS = 6; + private static final int SYNCED_FOLDERS = 7; private static final String TAG = FileContentProvider.class.getSimpleName(); @@ -335,6 +336,7 @@ public boolean onCreate() { mUriMatcher.addURI(authority, "capabilities/#", CAPABILITIES); mUriMatcher.addURI(authority, "uploads/", UPLOADS); mUriMatcher.addURI(authority, "uploads/#", UPLOADS); + mUriMatcher.addURI(authority, "synced_folders", SYNCED_FOLDERS); return true; } @@ -409,6 +411,13 @@ private Cursor query( + uri.getPathSegments().get(1)); } break; + case SYNCED_FOLDERS: + sqlQuery.setTables(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME); + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; default: throw new IllegalArgumentException("Unknown uri id: " + uri); } @@ -425,6 +434,9 @@ private Cursor query( case UPLOADS: order = ProviderTableMeta.UPLOADS_DEFAULT_SORT_ORDER; break; + case SYNCED_FOLDERS: + order = ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH; + break; default: // Files order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER; break; @@ -916,7 +928,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date - + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action ); @@ -930,7 +942,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action - + "VALUES ('/sdcard/DCIM/', 'syncTest', 0, 0, 1, 1, 'tobi', 1)"); + + "VALUES ('/sdcard/DCIM/', '/syncTest', 0, 0, 1, 1, 'tobi', 1)"); } /** diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java new file mode 100644 index 000000000000..f571cc566a61 --- /dev/null +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -0,0 +1,97 @@ +/** + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2016 Tobias Kaminsky + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.services; + +import android.accounts.Account; +import android.annotation.TargetApi; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Message; +import android.os.Messenger; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.util.Log; + +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.utils.FileStorageUtils; + +import java.io.File; +import java.util.Date; + +/** + * Created by tobi on 25.09.16. + */ + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class SyncedFolderJobService extends JobService { + private static final String TAG = "SyncedFolderJobService"; + private Context mContext; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // TODO Tobi why is this null? + mContext = MainApp.getAppContext(); + return START_NOT_STICKY; + } + + @Override + public boolean onStartJob(JobParameters params) { + Log_OC.d(TAG, "startJob: " + params.getJobId()); + + // TODO Tobi just for testing! + Context context = MainApp.getAppContext(); + Account account = AccountUtils.getCurrentOwnCloudAccount(context); + + PersistableBundle bundle = params.getExtras(); + String filePath = bundle.getString("filePath"); + String remoteFolder = bundle.getString("remoteFolder"); + Long dateTaken = bundle.getLong("dateTaken"); + Boolean subfolderByDate = bundle.getInt("subfolderByDate") == 1; + + File file = new File(filePath); + + FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); + requester.uploadNewFile( + context, + account, + filePath, + FileStorageUtils.getInstantUploadFilePath(remoteFolder, file.getName(), dateTaken, subfolderByDate), + FileUploader.LOCAL_BEHAVIOUR_FORGET, + "image/jpg", + true, // create parent folder if not existent + UploadFileOperation.CREATED_AS_INSTANT_PICTURE + ); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index fc44335797f6..cd7911a077ab 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -1,49 +1,63 @@ package com.owncloud.android.services.observer; +import android.annotation.TargetApi; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; +import android.os.Build; import android.os.FileObserver; +import android.os.PersistableBundle; import android.util.Log; import com.owncloud.android.MainApp; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.services.SyncedFolderJobService; import com.owncloud.android.utils.RecursiveFileObserver; import java.io.File; +import java.util.Date; -public class SyncedFolderObserver extends RecursiveFileObserver { +class SyncedFolderObserver extends RecursiveFileObserver { - File dirToWatch; - String remoteFolder; - Context context; + private Context context; - public static final int MY_BACKGROUND_JOB = 0; + private static final int MY_BACKGROUND_JOB = 0; + public static final String TAG = "SyncedFolderObserver"; + private String remoteFolder; public SyncedFolderObserver(String path, String remoteFolder) { super(path, FileObserver.CREATE + FileObserver.MOVED_TO); context = MainApp.getAppContext(); - dirToWatch = new File(path); this.remoteFolder = remoteFolder; - Log_OC.d("SyncedFolderObserver", "Started watching: "+ path); + Log_OC.d("SyncedFolderObserver", "Started watching: " + path); } - + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onEvent(int event, String path) { + PersistableBundle bundle = new PersistableBundle(); + bundle.putString("filePath", path); + bundle.putString("remoteFolder", remoteFolder); + bundle.putLong("dateTaken", new Date().getTime()); + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); -// JobInfo job = new JobInfo.Builder( -// MY_BACKGROUND_JOB, -// new ComponentName(context, MyJobService.class)) -// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) -// .setRequiresCharging(true) -// .build(); -// js.schedule(job); - - Log.d("SyncedFolder", "Event: " + event + " Path: " + path); + JobInfo job = new JobInfo.Builder( + MY_BACKGROUND_JOB, + new ComponentName(context, SyncedFolderJobService.class)) + .setRequiresCharging(false) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setExtras(bundle) + .build(); + + Integer result = js.schedule(job); + if (result <= 0) { + Log_OC.d(TAG, "Job failed to start: " + result); + } + + Log.d(TAG, "Event: " + event + " Path: " + path); } } diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 7f172dc35efb..1001f1c31214 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -1,59 +1,95 @@ package com.owncloud.android.services.observer; -import java.io.File; -import java.util.HashSet; +import android.app.Service; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.os.IBinder; import com.owncloud.android.MainApp; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.IBinder; -import android.widget.Toast; +import java.util.ArrayList; public class SyncedFolderObserverService extends Service { - private SyncedFolderObserver fileOb; - private static final String TAG = "InstantUploadFolderObserverService"; + private static final String TAG = "SyncedFolderObserverService"; + private ContentResolver database; + private ArrayList syncedFolderObservers = new ArrayList<>(); @Override public void onCreate() { - File sdcard = new File("/mnt/sdcard/DCIM/"); - Log_OC.d("SyncedFolderObserverService", "watching file: " + sdcard.getAbsolutePath()); - fileOb = new SyncedFolderObserver(sdcard.getAbsolutePath(), "WhatsApp"); - fileOb.startWatching(); + database = MainApp.getAppContext().getContentResolver(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - // TODO Auto-generated method stub - Log_OC.d("SyncedFolderObserverService", "start"); - return Service.START_NOT_STICKY; - //return super.onStartCommand(intent, flags, startId); + for (SyncedFolder syncedFolder : getSyncedFolders()) { + SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder.getLocalPath(), + syncedFolder.getRemotePath()); - } + observer.startWatching(); + syncedFolderObservers.add(observer); + } - @Override - public void onStart(Intent intent, int startid) { - Log_OC.d("SyncedFolderObserverService", "start"); - fileOb.startWatching(); - /*for (int i = 0; i < fileOb_list.size(); ++i) { - fileOb_list.get(i).startWatching(); - }*/ - Toast.makeText(this.getApplicationContext(), "start monitoring file modification", Toast.LENGTH_SHORT).show(); + return Service.START_NOT_STICKY; } + @Override public void onDestroy() { - fileOb.stopWatching(); - /*for (int i = 0; i < fileOb_list.size(); ++i) { - fileOb_list.get(i).stopWatching(); - }*/ - Toast.makeText(this.getApplicationContext(), "stop monitoring file modification", Toast.LENGTH_SHORT).show(); + for (SyncedFolderObserver observer : syncedFolderObservers) { + observer.stopWatching(); + syncedFolderObservers.remove(observer); + } } @Override public IBinder onBind(Intent arg0) { return null; } + + private SyncedFolder[] getSyncedFolders() { + Cursor c = database.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + "1=1", + null, + null + ); + SyncedFolder[] list = new SyncedFolder[c.getCount()]; + if (c.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[c.getPosition()] = syncedFolder; + } + } while (c.moveToNext()); + + } + c.close(); + + return list; + } + + private SyncedFolder createSyncedFolderFromCursor(Cursor c) { + SyncedFolder syncedFolder = null; + if (c != null) { + long id = c.getLong(c.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); + String localPath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); + String remotePath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); + Boolean wifiOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; + Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; + Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; + String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); + Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION)); + Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; + + syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, + accountName, uploadAction, enabled); + } + return syncedFolder; + } } diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 5641ee1433c4..f2ba8198c9cb 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -46,7 +46,7 @@ /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends DrawerActivity { +public class FolderSyncActivity extends FileActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index cc4aaa877b2a..0e227df7a17b 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -151,21 +151,17 @@ private static String getSubpathFromDate(long date) { /** * Returns the InstantUploadFilePath on the owncloud instance * - * @param context * @param fileName * @param dateTaken: Time in milliseconds since 1970 when the picture was taken. * @return */ - public static String getInstantUploadFilePath(Context context, String fileName, long dateTaken) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - String uploadPathdef = context.getString(R.string.instant_upload_path); - String uploadPath = pref.getString("instant_upload_path", uploadPathdef); + public static String getInstantUploadFilePath(String remotePath, String fileName, long dateTaken, + Boolean subfolderByDate) { String subPath = ""; - if (com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context)) { + if (subfolderByDate) { subPath = getSubpathFromDate(dateTaken); } - return uploadPath + OCFile.PATH_SEPARATOR + subPath - + (fileName == null ? "" : fileName); + return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName); } /** From 25117bc0e35954419370a6c07312bb2689c26426 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 26 Sep 2016 17:58:37 +0200 Subject: [PATCH 15/99] ignore gradle.properties for git --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5654a3c8dbec..0d18cdf6bb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ tests/proguard-project.txt .gradle .idea *.iml -build \ No newline at end of file +build +/gradle.properties From dc1b7b3958ceee6cdf7d64b701a01ae466b65f5e Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 26 Sep 2016 23:12:20 +0200 Subject: [PATCH 16/99] layout tweaking, better listener support, loading visualization --- res/layout/folder_sync_item_header.xml | 42 ++++----- res/layout/folder_sync_layout.xml | 54 ++++++++++-- res/values/strings.xml | 2 + .../android/datamodel/MediaFolder.java | 4 +- .../android/datamodel/MediaProvider.java | 9 +- .../ui/activity/FolderSyncActivity.java | 86 +++++++++---------- .../android/ui/adapter/FolderSyncAdapter.java | 43 +++------- 7 files changed, 133 insertions(+), 107 deletions(-) diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index 49f7593bc9b3..be6b16a5cf0e 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -21,9 +21,9 @@ + android:paddingTop="@dimen/standard_half_padding" + android:paddingBottom="@dimen/standard_half_padding" + android:paddingLeft="@dimen/standard_padding"> + android:textColor="?android:textColorPrimary" + android:textStyle="bold"/> - - + android:layout_alignParentRight="true"> + + diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index da581448d028..dc124ff60277 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -34,13 +34,55 @@ - + android:layout_height="match_parent"> + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index f4d0c0c97a0e..6e27d690486c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -494,6 +494,8 @@ Contribute as a developer, see <a href="https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> for details Move to… Copy to… + Loading folders… + No results " + mediaFolder.folder); + Log.d(TAG, "Reading images for --> " + mediaFolder.absolutePath); while (cursorImages.moveToNext()) { mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); } diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index f2ba8198c9cb..080b57e83d92 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -1,37 +1,37 @@ /** - * Nextcloud Android client application + * Nextcloud Android client application * - * @author Andy Scherzinger - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or 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 . + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.activity; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -41,15 +41,16 @@ import java.util.List; import java.util.TimerTask; -import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends FileActivity { +public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; + private LinearLayout mProgress; + private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,16 +70,13 @@ protected void onCreate(Bundle savedInstanceState) { private void setupContent() { - // TODO setup/initialize UI mRecyclerView = (RecyclerView) findViewById(android.R.id.list); + mProgress = (LinearLayout) findViewById(android.R.id.progress); + mEmpty = (TextView) findViewById(android.R.id.empty); + final int gridWidth = 4; - mAdapter = new FolderSyncAdapter(this, gridWidth, new FolderSyncAdapter.ClickListener() { - @Override - public void onClick(View view, int section, int relative, int absolute) { - selectItem(FolderSyncActivity.this); - } - }, mRecyclerView); + mAdapter = new FolderSyncAdapter(this, gridWidth, this, mRecyclerView); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); mAdapter.setLayoutManager(lm); @@ -88,11 +86,6 @@ public void onClick(View view, int section, int relative, int absolute) { load(); } - public static void selectItem(final Activity context) { - // TODO implement selectItem() - return; - } - private void load() { if (mAdapter.getItemCount() > 0) return; setListShown(false); @@ -103,7 +96,7 @@ public void run() { final List mediaFolders = MediaProvider.getAllShownImagesPath(FolderSyncActivity.this); for (MediaFolder mediaFolder : mediaFolders) { - Log.d(TAG, mediaFolder.path); + Log.d(TAG, mediaFolder.absolutePath); } mHandler.post(new TimerTask() { @@ -119,16 +112,9 @@ public void run() { void setListShown(boolean shown) { if (mRecyclerView != null) { - mRecyclerView.setVisibility(shown ? - View.VISIBLE : View.GONE); - - // TODO show/hide loading visuals - /** - mProgress.setVisibility(shown ? - View.GONE : View.VISIBLE); - mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? - View.VISIBLE : View.GONE); - **/ + mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE); + mProgress.setVisibility(shown ? View.GONE : View.VISIBLE); + mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } } @@ -164,4 +150,14 @@ public void showFiles(boolean onDeviceOnly) { fileDisplayActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(fileDisplayActivity); } + + @Override + public void onSyncStatusToggleClick(int section, MediaFolder mediaFolder) { + Toast.makeText(this,"Sync Status Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder) { + Toast.makeText(this,"Menu Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + } } diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 99fe8b40a6e0..f7a1ed523685 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -25,14 +25,12 @@ import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; @@ -49,8 +47,7 @@ /** * Adapter to display all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncAdapter extends SectionedRecyclerViewAdapter - implements View.OnClickListener, View.OnTouchListener { +public class FolderSyncAdapter extends SectionedRecyclerViewAdapter { private static final String TAG = FolderSyncAdapter.class.getSimpleName(); @@ -74,16 +71,6 @@ public void setMediaFolders(List mediaFolders) { notifyDataSetChanged(); } - @Override - public void onClick(View v) { - - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - return false; - } - @Override public int getSectionCount() { return mMediaFolders.size(); @@ -95,29 +82,22 @@ public int getItemCount(int section) { } @Override - public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - final int sectionId = section; - holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder - .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); + public void onBindHeaderViewHolder(MainViewHolder holder, final int section) { + holder.title.setText(mMediaFolders.get(section).folderName); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnTouchListener(new View.OnTouchListener() { + holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - Toast.makeText(mContext, "Sync Status Clicked for " + mMediaFolders.get(sectionId).folder, Toast - .LENGTH_SHORT) - .show(); - return true; + public void onClick(View v) { + mListener.onSyncStatusToggleClick(section,mMediaFolders.get(section)); } }); holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); - holder.menuButton.setOnTouchListener(new View.OnTouchListener() { + holder.menuButton.setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - Toast.makeText(mContext, "Menu Clicked for " + mMediaFolders.get(sectionId).folder, Toast.LENGTH_SHORT) - .show(); - return true; + public void onClick(View v) { + mListener.onSyncFolderSettingsClick(section,mMediaFolders.get(section)); } }); } @@ -188,7 +168,7 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos } //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); - holder.itemView.setOnClickListener(this); + //holder.itemView.setOnClickListener(this); } @Override @@ -200,7 +180,8 @@ public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } public interface ClickListener { - void onClick(View view, int section, int relative, int absolute); + void onSyncStatusToggleClick(int section, MediaFolder mediaFolder); + void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder); } static class MainViewHolder extends RecyclerView.ViewHolder { From dd35c494848fb7e7f8f8feba9866427e70b1d6a9 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 01:06:52 +0200 Subject: [PATCH 17/99] renamed upload action column, extended SyncFolder POJO, createdd a draft implementation of a SyncFolderProvider for DB operations --- .../android/datamodel/MediaFolder.java | 21 ++- .../android/datamodel/SyncedFolder.java | 4 + .../datamodel/SyncedFolderProvider.java | 168 ++++++++++++++++++ src/com/owncloud/android/db/ProviderMeta.java | 2 +- .../providers/FileContentProvider.java | 4 +- .../observer/SyncedFolderObserverService.java | 2 +- 6 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/SyncedFolderProvider.java diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index c23c2541a3d7..db7137629506 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -1,3 +1,23 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; import java.util.ArrayList; @@ -6,7 +26,6 @@ /** * Created by scherzia on 22.09.2016. */ - public class MediaFolder { public String folderName; public String absolutePath; diff --git a/src/com/owncloud/android/datamodel/SyncedFolder.java b/src/com/owncloud/android/datamodel/SyncedFolder.java index 6082b8eeac1b..a0e5d648ef4b 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolder.java +++ b/src/com/owncloud/android/datamodel/SyncedFolder.java @@ -80,4 +80,8 @@ public Integer getUploadAction() { public boolean isEnabled() { return enabled; } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } \ No newline at end of file diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java new file mode 100644 index 000000000000..99eb429d69b8 --- /dev/null +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -0,0 +1,168 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.lib.common.utils.Log_OC; + +/** + * Database provider for handling the persistence aspects of synced folders. + */ +public class SyncedFolderProvider { + static private final String TAG = SyncedFolderProvider.class.getSimpleName(); + + private ContentResolver mContentResolver; + + public SyncedFolderProvider(ContentResolver contentResolver) { + if (contentResolver == null) { + throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); + } + mContentResolver = contentResolver; + } + + /** + * Stores an media folder sync object in database. + * + * @param syncedFolder synced folder to store + * @return upload id, -1 if the insert process fails. + */ + public long storeFolderSync(SyncedFolder syncedFolder) { + Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); + + ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); + + Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); + + if (result != null) { + return Long.parseLong(result.getPathSegments().get(1)); + } else { + Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); + return -1; + } + } + + /** + * Update upload status of file uniquely referenced by id. + * + * @param id folder sync id. + * @param enabled new status. + * @return the number of rows updated. + */ + public int updateFolderSyncEnabled(long id, Boolean enabled) { + Log_OC.v(TAG, "Storing sync folder id" + id + " with enabled=" + enabled); + + int result = 0; + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(id)}, + null + ); + + if (cursor == null || cursor.getCount() != 1) { + Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in UploadDb. Expected 1. Failed to " + + "update upload db."); + } else { + while (cursor.moveToNext()) { + // read sync folder object and update + SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); + + syncedFolder.setEnabled(enabled); + + // update sync folder object in db + result = updateSyncFolder(syncedFolder); + } + } + cursor.close(); + return result; + } + + public SyncedFolder findByLocalPath(String localPath) { + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + "==" + localPath, + null, + null + ); + + if (cursor == null || cursor.getCount() != 1) { + Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + " available in sync folder db. " + + "Expected 1. Failed to update sync folder db."); + return null; + } else { + return createSyncedFolderFromCursor(cursor); + } + } + + private int updateSyncFolder(SyncedFolder syncedFolder) { + Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); + + ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); + + return mContentResolver.update( + ProviderMeta.ProviderTableMeta.CONTENT_URI_UPLOADS, + cv, + ProviderMeta.ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(syncedFolder.getId())} + ); + } + + private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { + SyncedFolder syncedFolder = null; + if (cursor != null) { + long id = cursor.getLong(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); + String localPath = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); + String remotePath = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); + Boolean wifiOnly = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; + Boolean chargingOnly = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; + Boolean subfolderByDate = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; + String accountName = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); + Integer uploadAction = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); + Boolean enabled = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; + + syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, + accountName, uploadAction, enabled); + } + return syncedFolder; + } + + @NonNull + private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) { + ContentValues cv = new ContentValues(); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH, syncedFolder.getLocalPath()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.getRemotePath()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); + return cv; + } +} diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 0a33735dfd25..533de14c9df3 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -162,6 +162,6 @@ static public class ProviderTableMeta implements BaseColumns { public static final String SYNCED_FOLDER_ENABLED = "enabled"; public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; public static final String SYNCED_FOLDER_ACCOUNT = "account"; - public static final String SYNCED_FOLDER_UPLOAD_OPTION = "upload_option"; + public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option"; } } \ No newline at end of file diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 93905d4d2866..9ff8b898be2e 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -929,7 +929,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER );" // upload action ); // TODO Tobi remove after testing @@ -941,7 +941,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ENABLED + ", " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + ") " // upload action + "VALUES ('/sdcard/DCIM/', '/syncTest', 0, 0, 1, 1, 'tobi', 1)"); } diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 1001f1c31214..051bb74626c3 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -84,7 +84,7 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor c) { Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); - Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION)); + Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, From a883dfcee7c595f89cffbbb0469c817278d1003a Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 01:14:02 +0200 Subject: [PATCH 18/99] make SyncedFolderObserverService use SyncedFolderProvider (todo also move the Observable to the Provider implementation) --- .../datamodel/SyncedFolderProvider.java | 25 +++++++++ .../observer/SyncedFolderObserverService.java | 55 ++----------------- 2 files changed, 29 insertions(+), 51 deletions(-) diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 99eb429d69b8..5843ff181e7a 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -65,6 +65,31 @@ public long storeFolderSync(SyncedFolder syncedFolder) { } } + public SyncedFolder[] getSyncedFolders() { + Cursor c = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + "1=1", + null, + null + ); + SyncedFolder[] list = new SyncedFolder[c.getCount()]; + if (c.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[c.getPosition()] = syncedFolder; + } + } while (c.moveToNext()); + + } + c.close(); + + return list; + } + /** * Update upload status of file uniquely referenced by id. * diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 051bb74626c3..f97af750b2f9 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -1,31 +1,28 @@ package com.owncloud.android.services.observer; import android.app.Service; -import android.content.ContentResolver; import android.content.Intent; -import android.database.Cursor; import android.os.IBinder; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.db.ProviderMeta; -import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.datamodel.SyncedFolderProvider; import java.util.ArrayList; public class SyncedFolderObserverService extends Service { private static final String TAG = "SyncedFolderObserverService"; - private ContentResolver database; + private SyncedFolderProvider mProvider; private ArrayList syncedFolderObservers = new ArrayList<>(); @Override public void onCreate() { - database = MainApp.getAppContext().getContentResolver(); + mProvider = new SyncedFolderProvider(MainApp.getAppContext().getContentResolver()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - for (SyncedFolder syncedFolder : getSyncedFolders()) { + for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) { SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder.getLocalPath(), syncedFolder.getRemotePath()); @@ -48,48 +45,4 @@ public void onDestroy() { public IBinder onBind(Intent arg0) { return null; } - - private SyncedFolder[] getSyncedFolders() { - Cursor c = database.query( - ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, - null, - "1=1", - null, - null - ); - SyncedFolder[] list = new SyncedFolder[c.getCount()]; - if (c.moveToFirst()) { - do { - SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); - if (syncedFolder == null) { - Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); - } else { - list[c.getPosition()] = syncedFolder; - } - } while (c.moveToNext()); - - } - c.close(); - - return list; - } - - private SyncedFolder createSyncedFolderFromCursor(Cursor c) { - SyncedFolder syncedFolder = null; - if (c != null) { - long id = c.getLong(c.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); - String localPath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); - String remotePath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); - Boolean wifiOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; - Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; - Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; - String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); - Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); - Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; - - syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, - accountName, uploadAction, enabled); - } - return syncedFolder; - } } From 512b7ad2294b1cbf46e21c0ae59cf6dbc9ee91f0 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 10:48:08 +0200 Subject: [PATCH 19/99] hardening provider implementation + javadoc --- .../datamodel/SyncedFolderProvider.java | 155 +++++++++++++----- 1 file changed, 113 insertions(+), 42 deletions(-) diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 5843ff181e7a..b10e82c7fe19 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -1,22 +1,21 @@ /** - * Nextcloud Android client application + * Nextcloud Android client application * - * @author Andy Scherzinger - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud - *

- * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or 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 . + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; @@ -29,14 +28,21 @@ import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; +import java.util.Observable; + /** * Database provider for handling the persistence aspects of synced folders. */ -public class SyncedFolderProvider { +public class SyncedFolderProvider extends Observable { static private final String TAG = SyncedFolderProvider.class.getSimpleName(); private ContentResolver mContentResolver; + /** + * constructor. + * + * @param contentResolver the ContentResolver to work with. + */ public SyncedFolderProvider(ContentResolver contentResolver) { if (contentResolver == null) { throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); @@ -58,6 +64,7 @@ public long storeFolderSync(SyncedFolder syncedFolder) { Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); if (result != null) { + notifyFolderSyncObservers(); return Long.parseLong(result.getPathSegments().get(1)); } else { Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); @@ -65,29 +72,40 @@ public long storeFolderSync(SyncedFolder syncedFolder) { } } + /** + * get all synced folder entries. + * + * @return all synced folder entries, empty if none have been found + */ public SyncedFolder[] getSyncedFolders() { - Cursor c = mContentResolver.query( + Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, "1=1", null, null ); - SyncedFolder[] list = new SyncedFolder[c.getCount()]; - if (c.moveToFirst()) { - do { - SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); - if (syncedFolder == null) { - Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); - } else { - list[c.getPosition()] = syncedFolder; - } - } while (c.moveToNext()); + if (cursor != null) { + SyncedFolder[] list = new SyncedFolder[cursor.getCount()]; + if (cursor.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[cursor.getPosition()] = syncedFolder; + } + } while (cursor.moveToNext()); + + } + cursor.close(); + return list; + } else { + Log_OC.e(TAG, "DB error creating read all cursor for synced folders."); } - c.close(); - return list; + return new SyncedFolder[0]; } /** @@ -109,10 +127,7 @@ public int updateFolderSyncEnabled(long id, Boolean enabled) { null ); - if (cursor == null || cursor.getCount() != 1) { - Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in UploadDb. Expected 1. Failed to " + - "update upload db."); - } else { + if (cursor != null && cursor.getCount() == 1) { while (cursor.moveToNext()) { // read sync folder object and update SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); @@ -121,13 +136,29 @@ public int updateFolderSyncEnabled(long id, Boolean enabled) { // update sync folder object in db result = updateSyncFolder(syncedFolder); + + cursor.close(); + } + } else { + if (cursor == null) { + Log_OC.e(TAG, "Sync folder db cursor for ID=" + id + " in NULL."); + } else { + Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in sync folder database. " + + "Expected 1. Failed to update sync folder db."); } } - cursor.close(); + return result; } + /** + * find a synced folder by local path. + * + * @param localPath the local path of the local folder + * @return the synced folder if found, else null + */ public SyncedFolder findByLocalPath(String localPath) { + SyncedFolder result = null; Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, @@ -136,28 +167,53 @@ public SyncedFolder findByLocalPath(String localPath) { null ); - if (cursor == null || cursor.getCount() != 1) { - Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + " available in sync folder db. " + - "Expected 1. Failed to update sync folder db."); - return null; + if (cursor != null && cursor.getCount() == 1) { + result = createSyncedFolderFromCursor(cursor); + cursor.close(); } else { - return createSyncedFolderFromCursor(cursor); + if (cursor == null) { + Log_OC.e(TAG, "Sync folder db cursor for local path=" + localPath + " in NULL."); + } else { + Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + + " available in sync folder db. Expected 1. Failed to update sync folder db."); + } } + + return result; + } + /** + * update given synced folder. + * + * @param syncedFolder the synced folder to be updated. + * @return the number of rows updated. + */ private int updateSyncFolder(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); - return mContentResolver.update( + int result = mContentResolver.update( ProviderMeta.ProviderTableMeta.CONTENT_URI_UPLOADS, cv, ProviderMeta.ProviderTableMeta._ID + "=?", new String[]{String.valueOf(syncedFolder.getId())} ); + + if (result > 0) { + notifyFolderSyncObservers(); + } + + return result; } + /** + * maps a cursor into a SyncedFolder object. + * + * @param cursor the cursor + * @return the mapped SyncedFolder, null if cursor is null + */ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { SyncedFolder syncedFolder = null; if (cursor != null) { @@ -177,6 +233,12 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { return syncedFolder; } + /** + * create ContentValues object based on given SyncedFolder. + * + * @param syncedFolder the synced folder + * @return the corresponding ContentValues object + */ @NonNull private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) { ContentValues cv = new ContentValues(); @@ -190,4 +252,13 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); return cv; } + + /** + * Inform all observers about data change. + */ + private void notifyFolderSyncObservers() { + Log_OC.d(TAG, "notifying folder sync data observers"); + setChanged(); + notifyObservers(); + } } From 9f67ced1dd6e055fb199690256d989b4e5a0ff5d Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 19 Sep 2016 14:38:24 +0200 Subject: [PATCH 20/99] add database for synced folders --- src/com/owncloud/android/db/ProviderMeta.java | 15 ++++++++-- .../providers/FileContentProvider.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 6d7adbe4bee7..10fb9a55f5df 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -33,7 +33,7 @@ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 15; + public static final int DB_VERSION = 16; private ProviderMeta() { } @@ -43,6 +43,7 @@ static public class ProviderTableMeta implements BaseColumns { public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final String CAPABILITIES_TABLE_NAME = "capabilities"; public static final String UPLOADS_TABLE_NAME = "list_of_uploads"; + public static final String SYNCED_FOLDERS_TABLE_NAME = "synced_folders"; public static final Uri CONTENT_URI = Uri.parse("content://" + MainApp.getAuthority() + "/"); public static final Uri CONTENT_URI_FILE = Uri.parse("content://" @@ -149,8 +150,16 @@ static public class ProviderTableMeta implements BaseColumns { public static final String UPLOADS_UPLOAD_END_TIMESTAMP = "upload_end_timestamp"; public static final String UPLOADS_LAST_RESULT = "last_result"; public static final String UPLOADS_CREATED_BY = "created_by"; - public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc"; + // Columns of synced folder table + public static final String SYNCED_FOLDER_LOCAL_PATH = "local_path"; + public static final String SYNCED_FOLDER_REMOTE_PATH = "remote_path"; + public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only"; + public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only"; + public static final String SYNCED_FOLDER_ENABLED = "enabled"; + public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; + public static final String SYNCED_FOLDER_ACCOUNT = "account"; + public static final String SYNCED_FOLDER_UPLOAD_OPTION = "upload_option"; } -} +} \ No newline at end of file diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 2a6584a6d6e2..06ccb0e53f8d 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -535,6 +535,8 @@ public void onCreate(SQLiteDatabase db) { // Create uploads table createUploadsTable(db); + // Create synced folders table + createSyncedFoldersTable(db); } @Override @@ -779,6 +781,19 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } + if (oldVersion < 16 && newVersion >= 16) { + Log_OC.i("SQL", "Entering in the #16 ADD synced folders table"); + db.beginTransaction(); + try { + // Create synced folders table + createSyncedFoldersTable(db); + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); @@ -892,6 +907,20 @@ private void createUploadsTable(SQLiteDatabase db){ */ } + private void createSyncedFoldersTable(SQLiteDatabase db){ + db.execSQL("CREATE TABLE " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " // id + + ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + " TEXT, " // local path + + ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " TEXT, " // remote path + + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + " INTEGER, " // wifi_only + + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only + + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action + ); + } + /** * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names * structure to include in it the path to the server instance. Updating the account names and path to local files From cf4498d17cb1261700b761e6adc8bf065a76aa77 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 20 Sep 2016 01:04:48 +0200 Subject: [PATCH 21/99] initial navigation setup for menu and activity --- AndroidManifest.xml | 2 + res/drawable-hdpi/ic_cloud_check.png | Bin 0 -> 820 bytes res/drawable-hdpi/ic_uploads.png | Bin 0 -> 284 bytes res/drawable-mdpi/ic_cloud_check.png | Bin 0 -> 608 bytes res/drawable-xhdpi/ic_cloud_check.png | Bin 0 -> 1032 bytes res/drawable-xxhdpi/ic_cloud_check.png | Bin 0 -> 1537 bytes res/drawable-xxxhdpi/ic_cloud_check.png | Bin 0 -> 2090 bytes res/layout/folder_sync_layout.xml | 65 +++++++++++++++++ res/menu/drawer_menu.xml | 5 ++ res/values/strings.xml | 1 + .../android/ui/activity/DrawerActivity.java | 4 ++ .../ui/activity/FolderSyncActivity.java | 68 ++++++++++++++++++ 12 files changed, 145 insertions(+) create mode 100644 res/drawable-hdpi/ic_cloud_check.png create mode 100644 res/drawable-hdpi/ic_uploads.png create mode 100644 res/drawable-mdpi/ic_cloud_check.png create mode 100644 res/drawable-xhdpi/ic_cloud_check.png create mode 100644 res/drawable-xxhdpi/ic_cloud_check.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_check.png create mode 100644 res/layout/folder_sync_layout.xml create mode 100644 src/com/owncloud/android/ui/activity/FolderSyncActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b55dc60f545d..68e07d655f9f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -74,8 +74,10 @@ + diff --git a/res/drawable-hdpi/ic_cloud_check.png b/res/drawable-hdpi/ic_cloud_check.png new file mode 100644 index 0000000000000000000000000000000000000000..50cf89679167342ba0acbf21c7d6c198838f82e3 GIT binary patch literal 820 zcmV-41Izr0P)_Iq^w?aan^2`v+2Nd--foH%kz-*RfSJE{7b5n()DC%!C z8s7om09)4UtE#U={s#dFit=GxCZETaS{0x7@*75_zvBBTKi)`GIO5i9TbrB~&h#?-f*;RWe~1mYj3*Kxv)K4|$$XCQ0%;EWf~qMP%-O zl3A{b$kRN}57g`R-;9WS+3t)Yaz<5W-zAe}*;qF+0IK?tG3II%MaBRlZHA&+t#-;e zH{L3xs0Hocd9eSH@J(lmYDlv34M=iK;4Wm=dtEFT-* z$2RBOd>DqMR;hwz0EWf){YSvwHUmr6+R6(c-djNRIto~=XqCFjuq3> z(_a{4ZUEa0oTOcz1N-ARUR?RwCrR=>Fbv%NFHJ<`fwlItIF1)#je}PZ1V?}gRUH9# y173j+j^*E_2da9xzrX)-6h-aqg*|%oD1StCl`zW0000|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3$$*xu=U`NW|f{SI=@DQ4nZ*n4heinc6L+ChD?b zMXqWuhb8N`4Ksd5C!N@nKG8jptz!|R(xRCiY5jZSfkJW4ZEOV&CE PbTWgdtDnm{r-UW|a57)t literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_cloud_check.png b/res/drawable-mdpi/ic_cloud_check.png new file mode 100644 index 0000000000000000000000000000000000000000..c48e3a91cb121ed8b0f2a522d646ca8088655301 GIT binary patch literal 608 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 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/drawer_menu.xml b/res/menu/drawer_menu.xml index 0679e5e987a1..0493f7680161 100644 --- a/res/menu/drawer_menu.xml +++ b/res/menu/drawer_menu.xml @@ -37,6 +37,11 @@ android:id="@+id/nav_uploads" android:icon="@drawable/ic_uploads" android:title="@string/drawer_item_uploads_list"/> + + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see . +--> diff --git a/res/layout/drawer_header.xml b/res/layout/drawer_header.xml index 8721fcdbad52..1e8f842587c9 100644 --- a/res/layout/drawer_header.xml +++ b/res/layout/drawer_header.xml @@ -1,21 +1,23 @@ + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see . +--> Date: Wed, 21 Sep 2016 15:26:41 +0200 Subject: [PATCH 23/99] sync config icons --- res/drawable-hdpi/ic_cloud_sync_off.png | Bin 0 -> 1047 bytes res/drawable-hdpi/ic_cloud_sync_on.png | Bin 0 -> 619 bytes res/drawable-mdpi/ic_cloud_sync_off.png | Bin 0 -> 722 bytes res/drawable-mdpi/ic_cloud_sync_on.png | Bin 0 -> 437 bytes res/drawable-xhdpi/ic_cloud_sync_off.png | Bin 0 -> 1357 bytes res/drawable-xhdpi/ic_cloud_sync_on.png | Bin 0 -> 793 bytes res/drawable-xxhdpi/ic_cloud_sync_off.png | Bin 0 -> 2010 bytes res/drawable-xxhdpi/ic_cloud_sync_on.png | Bin 0 -> 1189 bytes res/drawable-xxxhdpi/ic_cloud_sync_off.png | Bin 0 -> 2898 bytes res/drawable-xxxhdpi/ic_cloud_sync_on.png | Bin 0 -> 1539 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/drawable-hdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-hdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-mdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-mdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xhdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xxhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xxhdpi/ic_cloud_sync_on.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_sync_off.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_sync_on.png diff --git a/res/drawable-hdpi/ic_cloud_sync_off.png b/res/drawable-hdpi/ic_cloud_sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c0ec010e510eba45b119d5333d8abefcfffaa5 GIT binary patch literal 1047 zcmV+y1nB#TP)O=w(I6vuz}zL!XnBCQ%!M2TNetii;El180kjEHfg5NImhiOZ5W@6O9C z#Bnvr%)G=86w#e7s^Hf~{3xw1l2mD{)u0x-&?qV;R4F)u;k|oY%$xf1*_TO^RsO4Y z&;9z{bKX7Y!nU@xt<9BM$>{rjr?vJ4*LC+7i^ch+{UakISGle`1Uw9M1J?i+I0wuE zpN3)h&gA6epBo0sX0vx_t-k=;fmzpe`;t(e=RK>G$^$KHEi58`E2Um-YioPHP$>M9 z7Dz8OG&eWTi^%VBvoj3CqxpQkJ+aYAz`5i}mD&O1s@3W#&-3nV7^qk*&Wp%H!0DBs zf*|-9cpUf|ct&g8rM2EEB0H?Ldw@eqsc#ZX?p8`2Gsbi^6tcSV`TW&U6de(f?%1>} zd~2<1j5#PGuf(1L_{DLYuCcMPzt-iUgs09sj?wec52=KKDAQ51az z>`QU*^@IZ0y!sffK;4*uJ2YdJQ;it(~n@Dpi1dKHnaO;ZdM79+xV7 zg+jrtR;%ZMD`V5oB63n|eYjLAeE`T(pwAPY`3-oiTrQtpY4Jv(j4|IO<|K98T00a3 z!5o0j<#K%qn18j_1FOM6El;_w+efWLqz<2mTv(;6$8ntFp6A^RwS4gg@KoISa=BbS zL<%+PslmZPr=z2z19%b`B`!fqsZ*`3t@kKn%#YM=07|KzQmOQ18la6p)#dyCUTf_U zpfxt_*T4;lG2f?!fyTpE*ZBDOjEIyHrXihX;$}j*uKQM^d9S8+tLoab)lk)HHM##T z4NTQrB647>J=N0E(vfKXp_Ni^Ck#*fzJJ#iLmfV;a85)rl}hEOMeiYF%W$9qcfR;cV(^~fhLGZ&??!T=~(SJ$W)e=GN RCguPD002ovPDHLkV1iy(`o#bM literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_cloud_sync_on.png b/res/drawable-hdpi/ic_cloud_sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..24eae27f77a8ec8b17d4e46a72d6a85c8c1e21dd GIT binary patch literal 619 zcmV-x0+juUP)eqGO@E6X zRGB>p-W_1LyL;9JoR`OrJdX}!YblIQ0fXIPmivg4li1ADje{oUhX|VU!0sL~?cOr* zJ;jyWQl~*?-Dz6Q>k(i8_R<`<(ftak3fF4Kf%7Pb37?tX-p1c^IPU;9z3_J{Wc^_( zwHcHtlncQA2r=RHOmM05fOZu>hrvfU8X@2h$*GPIQAw4U9F!$-Tjy_?cO7dQOdqDs5=ug24EM;ZLAst=07V8=~3tqDi!12!9(kOIZW@E^fuD1`WugxD~7VWKgNbtyoFw;l^00O@khbn}c#L z{Fb@kBJd?4y!9T{#;mi?A>6ft9 zzfSqdIEFk*4rC9oEkZyGWD#@@sNynJtZb&eFe&Lj?+1PLh@{u_`D6RZRklSXaQ2xmmlTov#~qlJii#f!I#*tlq8 z-aBkW!A=`3QkaO4#E6y>usyL$Q1KryFGlXTwaB{=&*XB3fEIqMnc4UDGdnZ8@Lzvh zvf8oM7QFXYwbo~ybBh4RnB71L7y|YJ4PXwK^4>qJR4Q}r0BW`4y}tqsB}p>woa;`* zqrgpIKhOne-~dn-k&nh0+nVUQ0Il^o;1y74Lgwe^FN(+wrPP2@YNz*pn^I~3cnoBK zQEP3bqY?pxVb~P}!E+HA1ZK3>#b&u$WvxB!y&nUz-uq&uQkhuy1kSmvwf0&jlX)v5 z18KO>Qm$VVMbQgj1VBXYu9dJ#GwDf^WY&9sA2CcN=>*7WbrPQa?+yNX1Catx%0mK;d4mb)7$8kKiRvDag-AR&+1BG^tx7PM}@6Q8w zfGluIu{_@_EiFy6jygR_6h&X-IF5i@X=p^zp*n>C)M~XSsd-odRe*dxKe$zpAP8!y zSy#Z*)VUXi;jXQK1VL~iHQ!6ARLbRYxmgi8#PXP47m=yvB((vp+4yp~d`v_pfo!AE zI4J;O81BhrGR?cNS(}6TtKSPDj(q#+`j1HbIjv*e$-%i=<#q22Jc7AS#KroBWR$-l~ z4I4W3C%CypZxmNdSUC0QtTlmxEjj5AnQvq}T)x@y)6u2-N+nyj2-mx<5vD2%?~cXh ze42Xr>71GU4-aYlm;G6~sKmTiQlDY<0i6emB>^iHtJ>zi<+R>B^9-My)?xucVINdHUdE#QN7|LYMD4?d1(ITKM2! zsDHp-sWqqCW$v9{(3w4@HQ>U*ihD{+mmaTkd|B3XBt=&Bv-*zt;tM)Ex^u5G@ThH- z6qHfmeG<6$V5ZCu{eoGgEio=l|2VYS679q+8a5>}=$kP{uq~Bce85;jFG<8k;!^PQ z1I0QOqSrUre($P`cdO_UJ16M6`c^di{nbv_?d!v`IC8`@7Cs8vvTEZNBd-@V4EuIO V=Ec9gX8;UX22WQ%mvv4FO#snbtDFD; literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_cloud_sync_off.png b/res/drawable-xhdpi/ic_cloud_sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..a831bfa611fa411e022d0281ff3aa00af790286a GIT binary patch literal 1357 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_cloud_sync_on.png b/res/drawable-xxxhdpi/ic_cloud_sync_on.png new file mode 100644 index 0000000000000000000000000000000000000000..98f3503afa73adc67a69a27afe66ada1d93a4d43 GIT binary patch literal 1539 zcmV+e2K@PnP)y{(f}p|MFUZz2?8MmBJyT}kWkpxRg4KrftVPZAV?KyX>947&xf@sl-_7(=Cqyj zd)wLVcfR>f&+N>c*#nN_IF92uj^j9v<2a7vIF93tbjo%DecE?z6n7ivV00l!5%3H! z4HyHA2YjFjXhLW}$*-X6Q7TQcwU!MFn{4kwZ-!z zw-Y8|1(iid!U`W=VRqI|qMa+ibC=e-lX8aNwd$o`klyPPD^{2sza zV65$0Skg{Ho@R6EW>5k9cNXNOXQP|b2-}Ho@d0x1_0k{%_T2$hwM)>d4wz$m?}vzG zK?dvn5VS458d3X*;+Dnm(TZsqk5~L}5W#vbY_|9x;ELbXC=ySkf(q9C2&hWGic%*T zY3K66xdEyk%5?XE&Job=Z^9KnXbL5p&~-<8n%>Ng0PoUxbldN^4hYjpX8pE|k`n*H zjoA^9XnPY^{ID(Im+8B^Zp?Oou-tZbv7mUDZ5J0myvaL16`k9FyXcC_ZA@}T$F+h+ zC7;I?KT4C)tyLW_G|J4kol*R#x7m@I0-!<7w4F{2C22QnA^Ro#tnF-KNy$n)(HfX! zJC~T6hEC7bxVxSfC5qi-i&bW>Ci^32#|EWlYT$3%dBm15RfAl!okxs~)u8KbXAx%_ zOs(xCVysDH{4;>q&Lox;H#FXYTR@%dTw+O7eaCgcA=|mcR3_U2IUE7*+0G_*v^ksY zfU=@SgoC#8iJ1o4i|RUWMdT!Q1ARV7rDpE%u+|=`gOnKqQAf>pG5D;~WrxK6+Q};e2R*^c0$Wq*Q z!eE`o=r<@WxZm}SzNaKBQ(vK^0C#pYFv0;$%M!eTmhRr}{}XWsGP&IH-$AIrJ;>W1 zw-MI$wbsz%Ka!YzmUKN(ihH7>J8dAJU~($)3U2rH;Xy3cabY4lu>x2L6b>rZ$k2fB zB_ba_&LZK#6wRy2L**8uNP)~EyTp7R*%1=@!gmpC1MY!p0Is6U_lTUqq`vFvXGo6Y pIF92uj^j9v<2a7vIF94Q+kZgfjW%65jYR+e002ovPDHLkV1nEe;vN71 literal 0 HcmV?d00001 From 20dbdb872fe79d4f235f9a3a2a8f9dd65b047558 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 21 Sep 2016 15:43:41 +0200 Subject: [PATCH 24/99] initial add for recycler view implementation with headers (WIP) --- res/layout/folder_sync_item_header.xml | 57 ++++++ res/layout/folder_sync_layout.xml | 27 +-- .../SectionedRecyclerViewAdapter.java | 184 ++++++++++++++++++ .../ui/activity/FolderSyncActivity.java | 46 +++++ .../android/ui/adapter/FolderSyncAdapter.java | 114 +++++++++++ 5 files changed, 409 insertions(+), 19 deletions(-) create mode 100644 res/layout/folder_sync_item_header.xml create mode 100644 src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java create mode 100644 src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml new file mode 100644 index 000000000000..152a713f6fe4 --- /dev/null +++ b/res/layout/folder_sync_item_header.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index ff763062dc10..28e861579c03 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -34,25 +34,14 @@ - - - - - - - - + diff --git a/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java b/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java new file mode 100644 index 000000000000..73965abb8ece --- /dev/null +++ b/src/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java @@ -0,0 +1,184 @@ +/** + * Copyright 2016 Aidan Follestad + * + * 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 com.afollestad.sectionedrecyclerview; + +import android.support.annotation.IntRange; +import android.support.annotation.Nullable; +import android.support.v4.util.ArrayMap; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.ViewGroup; + +import java.util.List; + +/** + * @author Aidan Follestad (afollestad) + */ +public abstract class SectionedRecyclerViewAdapter extends RecyclerView.Adapter { + + protected final static int VIEW_TYPE_HEADER = -2; + protected final static int VIEW_TYPE_ITEM = -1; + + private final ArrayMap mHeaderLocationMap; + private GridLayoutManager mLayoutManager; + private ArrayMap mSpanMap; + private boolean mShowHeadersForEmptySections; + + public SectionedRecyclerViewAdapter() { + mHeaderLocationMap = new ArrayMap<>(); + } + + public abstract int getSectionCount(); + + public abstract int getItemCount(int section); + + public abstract void onBindHeaderViewHolder(VH holder, int section); + + public abstract void onBindViewHolder(VH holder, int section, int relativePosition, int absolutePosition); + + public final boolean isHeader(int position) { + return mHeaderLocationMap.get(position) != null; + } + + /** + * Instructs the list view adapter to whether show headers for empty sections or not. + * + * @param show flag indicating whether headers for empty sections ought to be shown. + */ + public final void shouldShowHeadersForEmptySections(boolean show) { + mShowHeadersForEmptySections = show; + } + + public final void setLayoutManager(@Nullable GridLayoutManager lm) { + mLayoutManager = lm; + if (lm == null) return; + lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (isHeader(position)) + return mLayoutManager.getSpanCount(); + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + final int absPos = position - (sectionAndPos[0] + 1); + return getRowSpan(mLayoutManager.getSpanCount(), + sectionAndPos[0], sectionAndPos[1], absPos); + } + }); + } + + @SuppressWarnings("UnusedParameters") + protected int getRowSpan(int fullSpanSize, int section, int relativePosition, int absolutePosition) { + return 1; + } + + // returns section along with offsetted position + private int[] getSectionIndexAndRelativePosition(int itemPosition) { + synchronized (mHeaderLocationMap) { + Integer lastSectionIndex = -1; + for (final Integer sectionIndex : mHeaderLocationMap.keySet()) { + if (itemPosition > sectionIndex) { + lastSectionIndex = sectionIndex; + } else { + break; + } + } + return new int[]{mHeaderLocationMap.get(lastSectionIndex), itemPosition - lastSectionIndex - 1}; + } + } + + @Override + public final int getItemCount() { + int count = 0; + mHeaderLocationMap.clear(); + for (int s = 0; s < getSectionCount(); s++) { + int itemCount = getItemCount(s); + if (mShowHeadersForEmptySections || (itemCount > 0)) { + mHeaderLocationMap.put(count, s); + count += itemCount + 1; + } + } + return count; + } + + /** + * @hide + * @deprecated + */ + @Override + @Deprecated + public final int getItemViewType(int position) { + if (isHeader(position)) { + return getHeaderViewType(mHeaderLocationMap.get(position)); + } else { + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + return getItemViewType(sectionAndPos[0], + // offset section view positions + sectionAndPos[1], + position - (sectionAndPos[0] + 1)); + } + } + + @SuppressWarnings("UnusedParameters") + @IntRange(from = 0, to = Integer.MAX_VALUE) + public int getHeaderViewType(int section) { + //noinspection ResourceType + return VIEW_TYPE_HEADER; + } + + @SuppressWarnings("UnusedParameters") + @IntRange(from = 0, to = Integer.MAX_VALUE) + public int getItemViewType(int section, int relativePosition, int absolutePosition) { + //noinspection ResourceType + return VIEW_TYPE_ITEM; + } + + /** + * @hide + * @deprecated + */ + @Override + @Deprecated + public final void onBindViewHolder(VH holder, int position) { + StaggeredGridLayoutManager.LayoutParams layoutParams = null; + if (holder.itemView.getLayoutParams() instanceof GridLayoutManager.LayoutParams) + layoutParams = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + else if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) + layoutParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams(); + if (isHeader(position)) { + if (layoutParams != null) layoutParams.setFullSpan(true); + onBindHeaderViewHolder(holder, mHeaderLocationMap.get(position)); + } else { + if (layoutParams != null) layoutParams.setFullSpan(false); + final int[] sectionAndPos = getSectionIndexAndRelativePosition(position); + final int absPos = position - (sectionAndPos[0] + 1); + onBindViewHolder(holder, sectionAndPos[0], + // offset section view positions + sectionAndPos[1], absPos); + } + if (layoutParams != null) + holder.itemView.setLayoutParams(layoutParams); + } + + /** + * @hide + * @deprecated + */ + @Deprecated + @Override + public final void onBindViewHolder(VH holder, int position, List payloads) { + super.onBindViewHolder(holder, position, payloads); + } +} diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 21c305e6ae48..3cf1b29ec7d1 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -1,17 +1,45 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.activity; +import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; +import android.view.View; import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); + private RecyclerView mRecyclerView; + private FolderSyncAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { @@ -31,6 +59,24 @@ protected void onCreate(Bundle savedInstanceState) { private void setupContent() { // TODO setup/initialize UI + mRecyclerView = (RecyclerView) findViewById(android.R.id.list); + + final int gridWidth = 4; + mAdapter = new FolderSyncAdapter(this, gridWidth, new FolderSyncAdapter.ClickListener() { + @Override + public void onClick(View view, int section, int relative, int absolute) { + selectItem(FolderSyncActivity.this); + } + }, mRecyclerView); + + final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); + mAdapter.setLayoutManager(lm); + mRecyclerView.setLayoutManager(lm); + mRecyclerView.setAdapter(mAdapter); + } + + public static void selectItem(final Activity context) { + return; } @Override diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java new file mode 100644 index 000000000000..cde7473a15a8 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -0,0 +1,114 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; +import com.owncloud.android.R; + +import java.util.ArrayList; + +/** + * Adapter to display all auto-synced folders and/or instant upload media folders. + */ +public class FolderSyncAdapter extends SectionedRecyclerViewAdapter + implements View.OnClickListener, View.OnTouchListener { + + private static final String TAG = FolderSyncAdapter.class.getSimpleName(); + + private final Context mContext; + private final int mGridWidth; + private final ClickListener mListener; + private final ArrayList mCategories; + private final RecyclerView mRecyclerView; + + public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { + mContext = context; + mGridWidth = gridWidth * 2; + mListener = listener; + mCategories = new ArrayList<>(); + mRecyclerView = recyclerView; + } + + @Override + public void onClick(View v) { + + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + return false; + } + + @Override + public int getSectionCount() { + return 0; + } + + @Override + public int getItemCount(int section) { + return 0; + } + + @Override + public void onBindHeaderViewHolder(MainViewHolder holder, int section) { + + } + + @Override + public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { + + } + + @Override + public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return null; + } + + public interface ClickListener { + void onClick(View view, int section, int relative, int absolute); + } + + public static class MainViewHolder extends RecyclerView.ViewHolder { + + public MainViewHolder(View itemView) { + super(itemView); + image = (ImageView) itemView.findViewById(R.id.image); + title = (TextView) itemView.findViewById(R.id.title); + menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); + mSyncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + } + + final ImageView image; + final TextView title; + final ImageButton menuButton; + final ImageButton mSyncStatusButton; + } +} From 9874b09502f9cc45eac9c284eb462c77dbd99026 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 16:59:52 +0200 Subject: [PATCH 25/99] rudimentary media loader implementation (file thumbnails and any action TBD) --- .../android/datamodel/MediaFolder.java | 14 +++ .../android/datamodel/MediaProvider.java | 93 +++++++++++++++++++ .../ui/activity/FolderSyncActivity.java | 54 +++++++++++ .../android/ui/adapter/FolderSyncAdapter.java | 93 ++++++++++++++++--- 4 files changed, 243 insertions(+), 11 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/MediaFolder.java create mode 100644 src/com/owncloud/android/datamodel/MediaProvider.java diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java new file mode 100644 index 000000000000..be41d8f84675 --- /dev/null +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -0,0 +1,14 @@ +package com.owncloud.android.datamodel; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Created by scherzia on 22.09.2016. + */ + +public class MediaFolder { + public String folder; + public String path; + public Collection filePaths = new ArrayList<>(); +} diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java new file mode 100644 index 000000000000..bf1d66d39686 --- /dev/null +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -0,0 +1,93 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; + +import android.app.Activity; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * media queries to gain access to media lists for the device. + */ +public class MediaProvider { + private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + + /** + * TODO rewrite + * Getting All Images Path + * + * @param activity + * @return List with images folders + */ + public static List getAllShownImagesPath(Activity activity) { + Cursor cursor; + int column_index_data, column_index_folder_name, column_index_data_image; + ArrayList listOfAllImages = new ArrayList<>(); + String absolutePathOfImage = null; + String folderName = null; + + String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + //String[] projection = {MediaStore.Images.Media.BUCKET_DISPLAY_NAME,MediaStore.Images.Media.BUCKET_ID}; + String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore + .MediaColumns.DATA}; + String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; + String folderSelection = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " IS NOT NULL) GROUP BY (" + MediaStore + .Images.Media + .BUCKET_DISPLAY_NAME; + String fileSelection = MediaStore.MediaColumns.DATA + " LIKE "; + String folderSortOrder = "MAX(" + MediaStore.Images.Media.DISPLAY_NAME + ") DESC"; + String fileSortOrder = MediaStore.MediaColumns.DATA + " DESC LIMIT 8"; // LIMIT 8 + + cursor = activity.getContentResolver().query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); + + column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + column_index_folder_name = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + + List mediaFolders = new ArrayList<>(); + while (cursor.moveToNext()) { + MediaFolder mediaFolder = new MediaFolder(); + absolutePathOfImage = cursor.getString(column_index_data); + folderName = cursor.getString(column_index_folder_name); + mediaFolder.path = folderName; + mediaFolder.folder = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + folderName.length()); + mediaFolder.filePaths = new ArrayList<>(); + + Cursor cursorImages = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + fileSortOrder); + column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + Log.e("READ IMAGES", "Reading images for --> " + mediaFolder.folder); + while (cursorImages.moveToNext()) { + mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); + } + + mediaFolders.add(mediaFolder); + } + + return mediaFolders; + } +} diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 3cf1b29ec7d1..fe26e36d1718 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -24,15 +24,24 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import java.util.List; +import java.util.TimerTask; + /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ @@ -40,6 +49,8 @@ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; + private ProgressBar mProgress; + private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { @@ -57,6 +68,7 @@ protected void onCreate(Bundle savedInstanceState) { setupContent(); } + private void setupContent() { // TODO setup/initialize UI mRecyclerView = (RecyclerView) findViewById(android.R.id.list); @@ -73,12 +85,54 @@ public void onClick(View view, int section, int relative, int absolute) { mAdapter.setLayoutManager(lm); mRecyclerView.setLayoutManager(lm); mRecyclerView.setAdapter(mAdapter); + + load(); } public static void selectItem(final Activity context) { + // TODO implement selectItem() return; } + private void load() { + if (mAdapter.getItemCount() > 0) return; + setListShown(false); + final Handler mHandler = new Handler(); + new Thread(new Runnable() { + @Override + public void run() { + final List mediaFolders = MediaProvider.getAllShownImagesPath(FolderSyncActivity.this); + + for (MediaFolder mediaFolder : mediaFolders) { + Log.d(TAG, mediaFolder.path); + } + + mHandler.post(new TimerTask() { + @Override + public void run() { + mAdapter.setMediaFolders(mediaFolders); + setListShown(true); + } + }); + } + }).start(); + } + + void setListShown(boolean shown) { + if (mRecyclerView != null) { + mRecyclerView.setVisibility(shown ? + View.VISIBLE : View.GONE); + + // TODO show/hide loading visuals + /** + mProgress.setVisibility(shown ? + View.GONE : View.VISIBLE); + mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? + View.VISIBLE : View.GONE); + **/ + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { boolean retval; diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index cde7473a15a8..b83c92bda10b 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -23,6 +23,8 @@ import android.content.Context; import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -32,8 +34,10 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; import java.util.ArrayList; +import java.util.List; /** * Adapter to display all auto-synced folders and/or instant upload media folders. @@ -46,20 +50,26 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mCategories; + private final List mMediaFolders; private final RecyclerView mRecyclerView; public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { mContext = context; mGridWidth = gridWidth * 2; mListener = listener; - mCategories = new ArrayList<>(); + mMediaFolders = new ArrayList<>(); mRecyclerView = recyclerView; } + public void setMediaFolders(List mediaFolders) { + mMediaFolders.clear(); + mMediaFolders.addAll(mediaFolders); + notifyDataSetChanged(); + } + @Override public void onClick(View v) { - + Log.d(TAG, v.getTag().toString()); } @Override @@ -69,46 +79,107 @@ public boolean onTouch(View v, MotionEvent event) { @Override public int getSectionCount() { - return 0; + return mMediaFolders.size(); } @Override public int getItemCount(int section) { - return 0; + return mMediaFolders.get(section).filePaths.size(); } @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - + holder.title.setText(mMediaFolders.get(section).folder); + holder.syncStatusButton.setVisibility(View.VISIBLE); + holder.syncStatusButton.setTag(section); + holder.syncStatusButton.setOnTouchListener(this); + holder.menuButton.setVisibility(View.VISIBLE); + holder.menuButton.setTag(section); + holder.menuButton.setOnTouchListener(this); } @Override public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { + final Context c = holder.itemView.getContext(); + + /** + if (BitmapUtils.isImage(file)){ + // Thumbnail in Cache? + Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + String.valueOf(file.hashCode()) + ); + if (thumbnail != null){ + fileIcon.setImageBitmap(thumbnail); + } else { + + // generate new Thumbnail + if (allowedToCreateNewThumbnail) { + final ThumbnailsCacheManager.ThumbnailGenerationTask task = + new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon); + if (thumbnail == null) { + if (BitmapUtils.isVideo(file)) { + thumbnail = ThumbnailsCacheManager.mDefaultVideo; + } else { + thumbnail = ThumbnailsCacheManager.mDefaultImg; + } + } + final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncThumbnailDrawable( + mContext.getResources(), + thumbnail, + task + ); + fileIcon.setImageDrawable(asyncDrawable); + task.execute(file); + Log_OC.v(TAG, "Executing task to generate a new thumbnail"); + + } // else, already being generated, don't restart it + } + } else { + fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); + } + */ + holder.image.setImageResource(R.drawable.file_image); + + /** + if (res == 0) { + holder.image.setBackgroundColor(Color.parseColor("#40000000")); + } else { + Glide.with(c) + .fromResource() + .load(res) + .into(holder.image); + } + */ + //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); + holder.itemView.setOnClickListener(this); } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return null; + View v = LayoutInflater.from(parent.getContext()).inflate( + viewType == VIEW_TYPE_HEADER ? R.layout.folder_sync_item_header : R.layout.grid_image, parent, false); + return new MainViewHolder(v); } public interface ClickListener { void onClick(View view, int section, int relative, int absolute); } - public static class MainViewHolder extends RecyclerView.ViewHolder { + static class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View itemView) { super(itemView); - image = (ImageView) itemView.findViewById(R.id.image); + image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); - mSyncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); } final ImageView image; final TextView title; final ImageButton menuButton; - final ImageButton mSyncStatusButton; + final ImageButton syncStatusButton; } } From 0e6d3951fb71fc9fd99692c25e77ff29f0929df1 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 17:48:06 +0200 Subject: [PATCH 26/99] initial v1 for grid view --- res/drawable-hdpi/ic_dots_vertical.png | Bin 0 -> 260 bytes res/drawable-mdpi/ic_dots_vertical.png | Bin 0 -> 204 bytes res/drawable-xhdpi/ic_dots_vertical.png | Bin 0 -> 330 bytes res/drawable-xxhdpi/ic_dots_vertical.png | Bin 0 -> 486 bytes res/drawable-xxxhdpi/ic_dots_vertical.png | Bin 0 -> 597 bytes res/layout/folder_sync_item_header.xml | 28 ++++--- res/layout/folder_sync_layout.xml | 1 - res/layout/grid_sync_item.xml | 74 ++++++++++++++++++ .../android/ui/adapter/FolderSyncAdapter.java | 9 ++- 9 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 res/drawable-hdpi/ic_dots_vertical.png create mode 100644 res/drawable-mdpi/ic_dots_vertical.png create mode 100644 res/drawable-xhdpi/ic_dots_vertical.png create mode 100644 res/drawable-xxhdpi/ic_dots_vertical.png create mode 100644 res/drawable-xxxhdpi/ic_dots_vertical.png create mode 100644 res/layout/grid_sync_item.xml diff --git a/res/drawable-hdpi/ic_dots_vertical.png b/res/drawable-hdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..0e50d191680a12dcc3150ca486dbda9a3df53d72 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3$%m-_yl0B;xSfD;v3*9Rye|SWYzg-Q85L7{Oxx zL$ztYMQ`hLHP@ib7=4!+;d*zBWKJ_E1%-9llzo>zDd_N7>TFtB`S+(U&RGd{JYjbD zZuZJ!9mB81)~h`hObObZ{%x~^Y75iTIm>M~=eOjYIKwtc)ULm((}6TtKSPDj(q%wSxTlL_h{y5d1PNAVUWv}oh^VMnM-Ln@U_N#B z#k+U++Wu5b(;bMqOD7Ra%V4cCN it`0_J6H(q;28Itw(vk(IqZa|KVDNPHb6Mw<&;$VAb2mKz literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_dots_vertical.png b/res/drawable-xhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..d92e2e76066e0d8e604224dc0b98af9696ac45e6 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCwj^(N7a$D;Kb?2i11Zh|kH}&m z?E%JaC$sH9f@KAc=|CE+pW)oQo^T-Ns;7%%NXEUl*EaGVG7w?8@cSFvN@nc~Y0N7m zk9tTdam6*y<6=9(aE76j%S+|7$x5eLF7_53)l2=0YJ5u=1O@*Z&wL#8{LZPUYL*SJ z`}7oBn!o?s_k$@l@|oc=>4r7pTa`AgT=i<w=Vmty?|gLckLrF zuULjVPA|C&xO)%kx_o)OqxIL0)*x|)?E$}-?l>hEa0?1v^si=)vaA;sd2sD9(1Q$~ Lu6{1-oD!M<$c1%M literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_dots_vertical.png b/res/drawable-xxhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..205a9a7ab4613b20f62f91f93cdf3d4b9c1a32c3 GIT binary patch literal 486 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Y)RhkE=uJQvb0|R5Br;B4q#jUqD4E>k_MOq(zo^nFua=>nusHt+oZhtfX^|y|VvMKN-=9BKkNudi>}Bq>)YbLY7cxw&^0r3xPdZm`IdlE> z*QZaWe0uiPZF^(e;l$l{^Xyv%&Y9gkDj}X48TX_zu4>Qc1L<;_o|E>O_Nf4+CaJuv z-g`M$!Sg!r-D{_c7ff!;%YJHk!S932i>(W9M^*Bb+`o{!_70m?SCUk6OhmciG1owm z;PvIZc&9wCj0_F+zf^Ph=AKon=3TE>@U;5!YlF(!w9V)9wpVYDT6@o#L)KH)Gk(&% z!W(>#~2=K5tL{1o1sx{an^LB{Ts5m}S3> literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_dots_vertical.png b/res/drawable-xxxhdpi/ic_dots_vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..dabff13651ceca58f90a0981eb59e124fc6b8e18 GIT binary patch literal 597 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE(}8pX7+2iB`GtXj@qwp{V@SoVx3>)am>eaJe%$VzWE>)Q;6`@h zL%}J>oFgQ-y?Hs`Zc~wdYjUK5fj{A?n9M<@>c^4`CT>W67^Ws6cDT;?^#5=nJEJqV zb<;kn0L=!23uT>OnO;fpu(78`#(7yy+!9~=M{%Xc3e+e0k8ad~)i|>0f7s3%W8~eYXF_^Of<{+Z zZ=I3r^?}jjmhr4y7aqnfQ9O(_QfJMSelSc@sf5T)N&Tskx$)SGMYDLLJ}^G1wEK9k z|JAw5_wEblGcMs^ny@5qd$sP$6^21TtD~PcT;B1qV$X}ORp&qdtO>ugaoYR`-k#sj z?ffk9u5k&+I;Fbo+odMJP-u92*zrt+3F8HBBNzP>5_=V#zcj58_s~BfwpXG0OVb8R zLly`96GD3xlD{4=+PSmoIEf;CcqLfi!1 zD#vdxSQCqrI2z(6s8%^1d%-HXSX_hw%wk=Vd;KCf27ttZzxx=QN==Mbl + android:layout_height="wrap_content" + android:padding="@dimen/standard_padding"> + @@ -48,10 +54,12 @@ android:id="@+id/settingsButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_marginBottom="@dimen/standard_half_margin" + android:layout_marginLeft="@dimen/standard_half_margin" android:layout_marginTop="@dimen/standard_half_margin" + android:layout_marginBottom="@dimen/standard_half_margin" + android:background="@color/transparent" android:clickable="true" - android:src="@drawable/ic_settings"/> + android:src="@drawable/ic_dots_vertical"/> + \ No newline at end of file diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index 28e861579c03..da581448d028 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -39,7 +39,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" - android:padding="@dimen/standard_padding" android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical"/> diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml new file mode 100644 index 000000000000..7641367fc557 --- /dev/null +++ b/res/layout/grid_sync_item.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index b83c92bda10b..9588688e7913 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -23,7 +23,6 @@ import android.content.Context; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -69,7 +68,7 @@ public void setMediaFolders(List mediaFolders) { @Override public void onClick(View v) { - Log.d(TAG, v.getTag().toString()); + } @Override @@ -89,7 +88,8 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - holder.title.setText(mMediaFolders.get(section).folder); + holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder + .lastIndexOf("/")+1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); holder.syncStatusButton.setOnTouchListener(this); @@ -159,7 +159,8 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate( - viewType == VIEW_TYPE_HEADER ? R.layout.folder_sync_item_header : R.layout.grid_image, parent, false); + viewType == VIEW_TYPE_HEADER ? + R.layout.folder_sync_item_header : R.layout.grid_sync_item, parent, false); return new MainViewHolder(v); } From ef53c97bdac7372b5150b4343c31fa36606fab54 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 18:46:40 +0200 Subject: [PATCH 27/99] proper thumbnails, image counter, darkening etc. --- res/layout/grid_sync_item.xml | 13 +++- .../android/datamodel/MediaFolder.java | 5 +- .../android/datamodel/MediaProvider.java | 10 ++- .../android/ui/adapter/FolderSyncAdapter.java | 69 ++++++++++++------- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml index 7641367fc557..988bd1c03b9c 100644 --- a/res/layout/grid_sync_item.xml +++ b/res/layout/grid_sync_item.xml @@ -40,6 +40,15 @@ android:scaleType="centerCrop" android:src="@drawable/ic_menu_archive"/> + + @@ -65,7 +74,7 @@ android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:text=">" - android:textColor="#cccccccc" + android:textColor="#dedede" android:textSize="22dp" android:textStyle="bold"/> diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index be41d8f84675..3997210862ba 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -1,7 +1,7 @@ package com.owncloud.android.datamodel; import java.util.ArrayList; -import java.util.Collection; +import java.util.List; /** * Created by scherzia on 22.09.2016. @@ -10,5 +10,6 @@ public class MediaFolder { public String folder; public String path; - public Collection filePaths = new ArrayList<>(); + public List filePaths = new ArrayList<>(); + public long numberOfFiles; } diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index bf1d66d39686..76d5bdd3af52 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -76,8 +76,11 @@ public static List getAllShownImagesPath(Activity activity) { mediaFolder.folder = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + folderName.length()); mediaFolder.filePaths = new ArrayList<>(); + // TODO: This can be done with one query, no limit, but only adding the 8 to the list and still get the + // total count + Cursor cursorImages = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + "'" + - absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, fileSortOrder); column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); Log.e("READ IMAGES", "Reading images for --> " + mediaFolder.folder); @@ -85,6 +88,11 @@ public static List getAllShownImagesPath(Activity activity) { mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); } + mediaFolder.numberOfFiles = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + null).getCount(); + mediaFolders.add(mediaFolder); } diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 9588688e7913..b8fd81845a15 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -22,6 +22,7 @@ package com.owncloud.android.ui.adapter; import android.content.Context; +import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -29,12 +30,18 @@ import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.MimetypeIconUtil; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -89,7 +96,7 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder - .lastIndexOf("/")+1, mMediaFolders.get(section).folder.length())); + .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); holder.syncStatusButton.setOnTouchListener(this); @@ -102,20 +109,35 @@ public void onBindHeaderViewHolder(MainViewHolder holder, int section) { public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { final Context c = holder.itemView.getContext(); - /** - if (BitmapUtils.isImage(file)){ + File file = new File(mMediaFolders.get(section).filePaths.get(relativePosition)); + + /** Cancellation needs do be checked and done before changing the drawable in fileIcon, or + * {@link ThumbnailsCacheManager#cancelPotentialThumbnailWork} will NEVER cancel any task. + **/ + boolean allowedToCreateNewThumbnail = (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, holder.image)); + + if (!file.isDirectory()) { + holder.image.setImageResource(R.drawable.file); + } else { + holder.image.setImageResource(R.drawable.ic_menu_archive); + } + // set proper tag + holder.image.setTag(file.hashCode()); + + // get Thumbnail if file is image + if (BitmapUtils.isImage(file)) { // Thumbnail in Cache? Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( String.valueOf(file.hashCode()) ); - if (thumbnail != null){ - fileIcon.setImageBitmap(thumbnail); + if (thumbnail != null) { + holder.image.setImageBitmap(thumbnail); } else { // generate new Thumbnail if (allowedToCreateNewThumbnail) { final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon); + new ThumbnailsCacheManager.ThumbnailGenerationTask(holder.image); if (thumbnail == null) { if (BitmapUtils.isVideo(file)) { thumbnail = ThumbnailsCacheManager.mDefaultVideo; @@ -129,28 +151,24 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos thumbnail, task ); - fileIcon.setImageDrawable(asyncDrawable); + holder.image.setImageDrawable(asyncDrawable); task.execute(file); Log_OC.v(TAG, "Executing task to generate a new thumbnail"); } // else, already being generated, don't restart it } } else { - fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); + holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); } - */ - holder.image.setImageResource(R.drawable.file_image); - /** - if (res == 0) { - holder.image.setBackgroundColor(Color.parseColor("#40000000")); + if(mMediaFolders.get(section).numberOfFiles > 8 && relativePosition >= 8-1) { + holder.counterValue.setText(Long.toString(mMediaFolders.get(section).numberOfFiles-8)); + holder.counterBar.setVisibility(View.VISIBLE); + holder.thumbnailDarkener.setVisibility(View.VISIBLE); } else { - Glide.with(c) - .fromResource() - .load(res) - .into(holder.image); + holder.counterBar.setVisibility(View.GONE); + holder.thumbnailDarkener.setVisibility(View.GONE); } - */ //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); holder.itemView.setOnClickListener(this); @@ -169,6 +187,13 @@ public interface ClickListener { } static class MainViewHolder extends RecyclerView.ViewHolder { + final ImageView image; + final TextView title; + final ImageButton menuButton; + final ImageButton syncStatusButton; + final LinearLayout counterBar; + final TextView counterValue; + final ImageView thumbnailDarkener; public MainViewHolder(View itemView) { super(itemView); @@ -176,11 +201,9 @@ public MainViewHolder(View itemView) { title = (TextView) itemView.findViewById(R.id.title); menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); + counterValue = (TextView) itemView.findViewById(R.id.counter); + thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); } - - final ImageView image; - final TextView title; - final ImageButton menuButton; - final ImageButton syncStatusButton; } } From 6369364fa95698356b1e87ac2bc462b5d9432804 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 22 Sep 2016 19:11:41 +0200 Subject: [PATCH 28/99] initial link of menu buttons --- res/layout/folder_sync_item_header.xml | 27 ++++++++++--------- res/layout/grid_sync_item.xml | 16 +++++------ .../android/ui/adapter/FolderSyncAdapter.java | 25 ++++++++++++++--- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index 52216afc0016..49f7593bc9b3 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -21,7 +21,9 @@ + android:paddingLeft="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding" + android:paddingBottom="@dimen/standard_padding"> - + \ No newline at end of file diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml index 988bd1c03b9c..332a212d87bc 100644 --- a/res/layout/grid_sync_item.xml +++ b/res/layout/grid_sync_item.xml @@ -58,23 +58,21 @@ android:orientation="horizontal"> diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index b8fd81845a15..99fe8b40a6e0 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,6 +32,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; @@ -95,14 +96,30 @@ public int getItemCount(int section) { @Override public void onBindHeaderViewHolder(MainViewHolder holder, int section) { + final int sectionId = section; holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnTouchListener(this); + holder.syncStatusButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + Toast.makeText(mContext, "Sync Status Clicked for " + mMediaFolders.get(sectionId).folder, Toast + .LENGTH_SHORT) + .show(); + return true; + } + }); holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); - holder.menuButton.setOnTouchListener(this); + holder.menuButton.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + Toast.makeText(mContext, "Menu Clicked for " + mMediaFolders.get(sectionId).folder, Toast.LENGTH_SHORT) + .show(); + return true; + } + }); } @Override @@ -199,8 +216,8 @@ public MainViewHolder(View itemView) { super(itemView); image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); - menuButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); - syncStatusButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); counterValue = (TextView) itemView.findViewById(R.id.counter); thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); From 34cae927fac9ad9e1fe7d1fe03164c43e04cceeb Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 20 Sep 2016 01:04:48 +0200 Subject: [PATCH 29/99] initial navigation setup for menu and activity --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index fe26e36d1718..d914563f1101 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -30,8 +30,6 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -49,8 +47,6 @@ public class FolderSyncActivity extends DrawerActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; - private ProgressBar mProgress; - private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { From db872635cee7ba6f239751f8ea3a631b58bfaefb Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 21 Sep 2016 15:43:41 +0200 Subject: [PATCH 30/99] initial add for recycler view implementation with headers (WIP) --- .../owncloud/android/ui/activity/FolderSyncActivity.java | 3 +++ src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index d914563f1101..5641ee1433c4 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -28,6 +28,8 @@ import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; @@ -39,6 +41,7 @@ import java.util.List; import java.util.TimerTask; +import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 99fe8b40a6e0..d96e87da0093 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,17 +32,10 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; -import com.owncloud.android.datamodel.MediaFolder; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.utils.BitmapUtils; -import com.owncloud.android.utils.MimetypeIconUtil; -import java.io.File; import java.util.ArrayList; import java.util.List; From 99a8278de896a31dc83e455fc1cdb2cc6b81d1d1 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 22 Sep 2016 20:14:25 +0200 Subject: [PATCH 31/99] wip --- AndroidManifest.xml | 1 + src/com/owncloud/android/MainApp.java | 6 ++ .../providers/FileContentProvider.java | 12 ++++ .../observer/SyncedFolderObserver.java | 49 +++++++++++++++ .../observer/SyncedFolderObserverService.java | 59 +++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 src/com/owncloud/android/services/observer/SyncedFolderObserver.java create mode 100644 src/com/owncloud/android/services/observer/SyncedFolderObserverService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 68e07d655f9f..058270823dca 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -119,6 +119,7 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> + diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index c9e2342cd076..64e59541bb97 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; @@ -33,6 +34,7 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.services.observer.SyncedFolderObserverService; /** @@ -85,6 +87,10 @@ public void onCreate(){ Log_OC.d("Debug", "start logging"); } + Log_OC.d("SyncedFolderObserverService", "Start service SyncedFolderObserverService"); + Intent i = new Intent(this, SyncedFolderObserverService.class); + startService(i); + // register global protection with pass code registerActivityLifecycleCallbacks( new ActivityLifecycleCallbacks() { diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 06ccb0e53f8d..d4d89920ec93 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -919,6 +919,18 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action ); + + // TODO Tobi remove after testing + db.execSQL("INSERT INTO " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + "(" + + ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + ", " // local path + + ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + ", " // remote path + + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + ", " // wifi_only + + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + ", " // charging only + + ProviderTableMeta.SYNCED_FOLDER_ENABLED + ", " // enabled + + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action + + "VALUES ('/sdcard/DCIM/', 'syncTest', 0, 0, 1, 1, 'tobi', 1)"); } /** diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java new file mode 100644 index 000000000000..fc44335797f6 --- /dev/null +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -0,0 +1,49 @@ +package com.owncloud.android.services.observer; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.os.FileObserver; +import android.util.Log; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.RecursiveFileObserver; + +import java.io.File; + +public class SyncedFolderObserver extends RecursiveFileObserver { + + File dirToWatch; + String remoteFolder; + Context context; + + public static final int MY_BACKGROUND_JOB = 0; + + + public SyncedFolderObserver(String path, String remoteFolder) { + super(path, FileObserver.CREATE + FileObserver.MOVED_TO); + + context = MainApp.getAppContext(); + dirToWatch = new File(path); + this.remoteFolder = remoteFolder; + Log_OC.d("SyncedFolderObserver", "Started watching: "+ path); + } + + + + @Override + public void onEvent(int event, String path) { + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); +// JobInfo job = new JobInfo.Builder( +// MY_BACKGROUND_JOB, +// new ComponentName(context, MyJobService.class)) +// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) +// .setRequiresCharging(true) +// .build(); +// js.schedule(job); + + Log.d("SyncedFolder", "Event: " + event + " Path: " + path); + } +} diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java new file mode 100644 index 000000000000..7f172dc35efb --- /dev/null +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -0,0 +1,59 @@ +package com.owncloud.android.services.observer; + +import java.io.File; +import java.util.HashSet; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.IBinder; +import android.widget.Toast; + +public class SyncedFolderObserverService extends Service { + private SyncedFolderObserver fileOb; + private static final String TAG = "InstantUploadFolderObserverService"; + + @Override + public void onCreate() { + File sdcard = new File("/mnt/sdcard/DCIM/"); + Log_OC.d("SyncedFolderObserverService", "watching file: " + sdcard.getAbsolutePath()); + fileOb = new SyncedFolderObserver(sdcard.getAbsolutePath(), "WhatsApp"); + fileOb.startWatching(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // TODO Auto-generated method stub + Log_OC.d("SyncedFolderObserverService", "start"); + return Service.START_NOT_STICKY; + //return super.onStartCommand(intent, flags, startId); + + } + + @Override + public void onStart(Intent intent, int startid) { + Log_OC.d("SyncedFolderObserverService", "start"); + fileOb.startWatching(); + /*for (int i = 0; i < fileOb_list.size(); ++i) { + fileOb_list.get(i).startWatching(); + }*/ + Toast.makeText(this.getApplicationContext(), "start monitoring file modification", Toast.LENGTH_SHORT).show(); + } + @Override + public void onDestroy() { + fileOb.stopWatching(); + /*for (int i = 0; i < fileOb_list.size(); ++i) { + fileOb_list.get(i).stopWatching(); + }*/ + Toast.makeText(this.getApplicationContext(), "stop monitoring file modification", Toast.LENGTH_SHORT).show(); + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } +} From 7fef98ce4733e9ba0cf4229aff471defa2cef98c Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 22 Sep 2016 20:43:20 +0200 Subject: [PATCH 32/99] wip --- src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index d96e87da0093..99fe8b40a6e0 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -32,10 +32,17 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.MimetypeIconUtil; +import java.io.File; import java.util.ArrayList; import java.util.List; From 77353f698db804f30b476a6ad81991d980b4ac74 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Sun, 25 Sep 2016 21:51:13 +0200 Subject: [PATCH 33/99] wip --- AndroidManifest.xml | 4 + .../android/datamodel/SyncedFolder.java | 83 +++++++++++++++ .../android/db/PreferenceManager.java | 2 +- src/com/owncloud/android/db/ProviderMeta.java | 2 + .../files/InstantUploadBroadcastReceiver.java | 8 +- .../providers/FileContentProvider.java | 16 ++- .../services/SyncedFolderJobService.java | 97 +++++++++++++++++ .../observer/SyncedFolderObserver.java | 48 ++++++--- .../observer/SyncedFolderObserverService.java | 100 ++++++++++++------ .../ui/activity/FolderSyncActivity.java | 2 +- .../android/utils/FileStorageUtils.java | 12 +-- 11 files changed, 312 insertions(+), 62 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/SyncedFolder.java create mode 100644 src/com/owncloud/android/services/SyncedFolderJobService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 058270823dca..ab0c601feb09 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -131,6 +131,10 @@ android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter_files" /> + . + */ + +package com.owncloud.android.datamodel; + +public class SyncedFolder { + private long id; + private String localPath; + private String remotePath; + private Boolean wifiOnly; + private Boolean chargingOnly; + private Boolean subfolderByDate; + private String account; + private Integer uploadAction; + private boolean enabled; + + public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { + this.id = id; + this.localPath = localPath; + this.remotePath = remotePath; + this.wifiOnly = wifiOnly; + this.chargingOnly = chargingOnly; + this.subfolderByDate = subfolderByDate; + this.account = account; + this.uploadAction = uploadAction; + this.enabled = enabled; + } + + public long getId() { + return id; + } + + public String getLocalPath() { + return localPath; + } + + public String getRemotePath() { + return remotePath; + } + + public Boolean getWifiOnly() { + return wifiOnly; + } + + public Boolean getChargingOnly() { + return chargingOnly; + } + + public Boolean getSubfolderByDate() { + return subfolderByDate; + } + + public String getAccount() { + return account; + } + + public Integer getUploadAction() { + return uploadAction; + } + + public boolean isEnabled() { + return enabled; + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/db/PreferenceManager.java b/src/com/owncloud/android/db/PreferenceManager.java index fd0ce8a9828f..4c2de4ebcb69 100644 --- a/src/com/owncloud/android/db/PreferenceManager.java +++ b/src/com/owncloud/android/db/PreferenceManager.java @@ -174,7 +174,7 @@ private static void saveIntPreference(Context context, String key, int value) { appPreferences.apply(); } - private static SharedPreferences getDefaultSharedPreferences(Context context) { + public static SharedPreferences getDefaultSharedPreferences(Context context) { return android.preference.PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); } } diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 10fb9a55f5df..0a33735dfd25 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -56,6 +56,8 @@ static public class ProviderTableMeta implements BaseColumns { + MainApp.getAuthority() + "/capabilities"); public static final Uri CONTENT_URI_UPLOADS = Uri.parse("content://" + MainApp.getAuthority() + "/uploads"); + public static final Uri CONTENT_URI_SYNCED_FOLDERS = Uri.parse("content://" + + MainApp.getAuthority() + "/synced_folders"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index c1bf93f3be64..cb0d13c22023 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -32,6 +32,7 @@ import android.provider.MediaStore.Video; import android.support.v4.content.ContextCompat; +import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.db.PreferenceManager; import com.owncloud.android.files.services.FileUploader; @@ -131,12 +132,17 @@ private void handleNewPictureAction(Context context, Intent intent) { new FileUploader.UploadRequester(); int behaviour = getUploadBehaviour(context); + Boolean subfolderByDate = com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context); + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); + String uploadPathdef = context.getString(R.string.instant_upload_path); + String uploadPath = pref.getString("instant_upload_path", uploadPathdef); + FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); requester.uploadNewFile( context, account, file_path, - FileStorageUtils.getInstantUploadFilePath(context, file_name, date_taken), + FileStorageUtils.getInstantUploadFilePath(uploadPath, file_name, date_taken, subfolderByDate), behaviour, mime_type, true, // create parent folder if not existent diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index d4d89920ec93..93905d4d2866 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -69,6 +69,7 @@ public class FileContentProvider extends ContentProvider { private static final int SHARES = 4; private static final int CAPABILITIES = 5; private static final int UPLOADS = 6; + private static final int SYNCED_FOLDERS = 7; private static final String TAG = FileContentProvider.class.getSimpleName(); @@ -335,6 +336,7 @@ public boolean onCreate() { mUriMatcher.addURI(authority, "capabilities/#", CAPABILITIES); mUriMatcher.addURI(authority, "uploads/", UPLOADS); mUriMatcher.addURI(authority, "uploads/#", UPLOADS); + mUriMatcher.addURI(authority, "synced_folders", SYNCED_FOLDERS); return true; } @@ -409,6 +411,13 @@ private Cursor query( + uri.getPathSegments().get(1)); } break; + case SYNCED_FOLDERS: + sqlQuery.setTables(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME); + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; default: throw new IllegalArgumentException("Unknown uri id: " + uri); } @@ -425,6 +434,9 @@ private Cursor query( case UPLOADS: order = ProviderTableMeta.UPLOADS_DEFAULT_SORT_ORDER; break; + case SYNCED_FOLDERS: + order = ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH; + break; default: // Files order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER; break; @@ -916,7 +928,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + " INTEGER, " // charging only + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date - + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " INTEGER, " // account + + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action ); @@ -930,7 +942,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action - + "VALUES ('/sdcard/DCIM/', 'syncTest', 0, 0, 1, 1, 'tobi', 1)"); + + "VALUES ('/sdcard/DCIM/', '/syncTest', 0, 0, 1, 1, 'tobi', 1)"); } /** diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java new file mode 100644 index 000000000000..f571cc566a61 --- /dev/null +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -0,0 +1,97 @@ +/** + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2016 Tobias Kaminsky + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.services; + +import android.accounts.Account; +import android.annotation.TargetApi; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Message; +import android.os.Messenger; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.util.Log; + +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.utils.FileStorageUtils; + +import java.io.File; +import java.util.Date; + +/** + * Created by tobi on 25.09.16. + */ + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class SyncedFolderJobService extends JobService { + private static final String TAG = "SyncedFolderJobService"; + private Context mContext; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // TODO Tobi why is this null? + mContext = MainApp.getAppContext(); + return START_NOT_STICKY; + } + + @Override + public boolean onStartJob(JobParameters params) { + Log_OC.d(TAG, "startJob: " + params.getJobId()); + + // TODO Tobi just for testing! + Context context = MainApp.getAppContext(); + Account account = AccountUtils.getCurrentOwnCloudAccount(context); + + PersistableBundle bundle = params.getExtras(); + String filePath = bundle.getString("filePath"); + String remoteFolder = bundle.getString("remoteFolder"); + Long dateTaken = bundle.getLong("dateTaken"); + Boolean subfolderByDate = bundle.getInt("subfolderByDate") == 1; + + File file = new File(filePath); + + FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); + requester.uploadNewFile( + context, + account, + filePath, + FileStorageUtils.getInstantUploadFilePath(remoteFolder, file.getName(), dateTaken, subfolderByDate), + FileUploader.LOCAL_BEHAVIOUR_FORGET, + "image/jpg", + true, // create parent folder if not existent + UploadFileOperation.CREATED_AS_INSTANT_PICTURE + ); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index fc44335797f6..cd7911a077ab 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -1,49 +1,63 @@ package com.owncloud.android.services.observer; +import android.annotation.TargetApi; import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; +import android.os.Build; import android.os.FileObserver; +import android.os.PersistableBundle; import android.util.Log; import com.owncloud.android.MainApp; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.services.SyncedFolderJobService; import com.owncloud.android.utils.RecursiveFileObserver; import java.io.File; +import java.util.Date; -public class SyncedFolderObserver extends RecursiveFileObserver { +class SyncedFolderObserver extends RecursiveFileObserver { - File dirToWatch; - String remoteFolder; - Context context; + private Context context; - public static final int MY_BACKGROUND_JOB = 0; + private static final int MY_BACKGROUND_JOB = 0; + public static final String TAG = "SyncedFolderObserver"; + private String remoteFolder; public SyncedFolderObserver(String path, String remoteFolder) { super(path, FileObserver.CREATE + FileObserver.MOVED_TO); context = MainApp.getAppContext(); - dirToWatch = new File(path); this.remoteFolder = remoteFolder; - Log_OC.d("SyncedFolderObserver", "Started watching: "+ path); + Log_OC.d("SyncedFolderObserver", "Started watching: " + path); } - + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onEvent(int event, String path) { + PersistableBundle bundle = new PersistableBundle(); + bundle.putString("filePath", path); + bundle.putString("remoteFolder", remoteFolder); + bundle.putLong("dateTaken", new Date().getTime()); + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); -// JobInfo job = new JobInfo.Builder( -// MY_BACKGROUND_JOB, -// new ComponentName(context, MyJobService.class)) -// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) -// .setRequiresCharging(true) -// .build(); -// js.schedule(job); - - Log.d("SyncedFolder", "Event: " + event + " Path: " + path); + JobInfo job = new JobInfo.Builder( + MY_BACKGROUND_JOB, + new ComponentName(context, SyncedFolderJobService.class)) + .setRequiresCharging(false) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setExtras(bundle) + .build(); + + Integer result = js.schedule(job); + if (result <= 0) { + Log_OC.d(TAG, "Job failed to start: " + result); + } + + Log.d(TAG, "Event: " + event + " Path: " + path); } } diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 7f172dc35efb..1001f1c31214 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -1,59 +1,95 @@ package com.owncloud.android.services.observer; -import java.io.File; -import java.util.HashSet; +import android.app.Service; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.os.IBinder; import com.owncloud.android.MainApp; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.IBinder; -import android.widget.Toast; +import java.util.ArrayList; public class SyncedFolderObserverService extends Service { - private SyncedFolderObserver fileOb; - private static final String TAG = "InstantUploadFolderObserverService"; + private static final String TAG = "SyncedFolderObserverService"; + private ContentResolver database; + private ArrayList syncedFolderObservers = new ArrayList<>(); @Override public void onCreate() { - File sdcard = new File("/mnt/sdcard/DCIM/"); - Log_OC.d("SyncedFolderObserverService", "watching file: " + sdcard.getAbsolutePath()); - fileOb = new SyncedFolderObserver(sdcard.getAbsolutePath(), "WhatsApp"); - fileOb.startWatching(); + database = MainApp.getAppContext().getContentResolver(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - // TODO Auto-generated method stub - Log_OC.d("SyncedFolderObserverService", "start"); - return Service.START_NOT_STICKY; - //return super.onStartCommand(intent, flags, startId); + for (SyncedFolder syncedFolder : getSyncedFolders()) { + SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder.getLocalPath(), + syncedFolder.getRemotePath()); - } + observer.startWatching(); + syncedFolderObservers.add(observer); + } - @Override - public void onStart(Intent intent, int startid) { - Log_OC.d("SyncedFolderObserverService", "start"); - fileOb.startWatching(); - /*for (int i = 0; i < fileOb_list.size(); ++i) { - fileOb_list.get(i).startWatching(); - }*/ - Toast.makeText(this.getApplicationContext(), "start monitoring file modification", Toast.LENGTH_SHORT).show(); + return Service.START_NOT_STICKY; } + @Override public void onDestroy() { - fileOb.stopWatching(); - /*for (int i = 0; i < fileOb_list.size(); ++i) { - fileOb_list.get(i).stopWatching(); - }*/ - Toast.makeText(this.getApplicationContext(), "stop monitoring file modification", Toast.LENGTH_SHORT).show(); + for (SyncedFolderObserver observer : syncedFolderObservers) { + observer.stopWatching(); + syncedFolderObservers.remove(observer); + } } @Override public IBinder onBind(Intent arg0) { return null; } + + private SyncedFolder[] getSyncedFolders() { + Cursor c = database.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + "1=1", + null, + null + ); + SyncedFolder[] list = new SyncedFolder[c.getCount()]; + if (c.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[c.getPosition()] = syncedFolder; + } + } while (c.moveToNext()); + + } + c.close(); + + return list; + } + + private SyncedFolder createSyncedFolderFromCursor(Cursor c) { + SyncedFolder syncedFolder = null; + if (c != null) { + long id = c.getLong(c.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); + String localPath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); + String remotePath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); + Boolean wifiOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; + Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; + Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; + String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); + Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION)); + Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; + + syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, + accountName, uploadAction, enabled); + } + return syncedFolder; + } } diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 5641ee1433c4..f2ba8198c9cb 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -46,7 +46,7 @@ /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends DrawerActivity { +public class FolderSyncActivity extends FileActivity { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index cc4aaa877b2a..0e227df7a17b 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -151,21 +151,17 @@ private static String getSubpathFromDate(long date) { /** * Returns the InstantUploadFilePath on the owncloud instance * - * @param context * @param fileName * @param dateTaken: Time in milliseconds since 1970 when the picture was taken. * @return */ - public static String getInstantUploadFilePath(Context context, String fileName, long dateTaken) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - String uploadPathdef = context.getString(R.string.instant_upload_path); - String uploadPath = pref.getString("instant_upload_path", uploadPathdef); + public static String getInstantUploadFilePath(String remotePath, String fileName, long dateTaken, + Boolean subfolderByDate) { String subPath = ""; - if (com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context)) { + if (subfolderByDate) { subPath = getSubpathFromDate(dateTaken); } - return uploadPath + OCFile.PATH_SEPARATOR + subPath - + (fileName == null ? "" : fileName); + return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName); } /** From 6774a002b44232a8c2700ae21696346e71757ecd Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 26 Sep 2016 17:58:37 +0200 Subject: [PATCH 34/99] ignore gradle.properties for git --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5654a3c8dbec..0d18cdf6bb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ tests/proguard-project.txt .gradle .idea *.iml -build \ No newline at end of file +build +/gradle.properties From a1931ed9a5b3c6b49a7971fbd67d04e459c99805 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 26 Sep 2016 23:12:20 +0200 Subject: [PATCH 35/99] layout tweaking, better listener support, loading visualization --- res/layout/folder_sync_item_header.xml | 42 ++++----- res/layout/folder_sync_layout.xml | 54 ++++++++++-- res/values/strings.xml | 2 + .../android/datamodel/MediaFolder.java | 4 +- .../android/datamodel/MediaProvider.java | 9 +- .../ui/activity/FolderSyncActivity.java | 86 +++++++++---------- .../android/ui/adapter/FolderSyncAdapter.java | 43 +++------- 7 files changed, 133 insertions(+), 107 deletions(-) diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index 49f7593bc9b3..be6b16a5cf0e 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -21,9 +21,9 @@ + android:paddingTop="@dimen/standard_half_padding" + android:paddingBottom="@dimen/standard_half_padding" + android:paddingLeft="@dimen/standard_padding"> + android:textColor="?android:textColorPrimary" + android:textStyle="bold"/> - - + android:layout_alignParentRight="true"> + + diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml index da581448d028..dc124ff60277 100644 --- a/res/layout/folder_sync_layout.xml +++ b/res/layout/folder_sync_layout.xml @@ -34,13 +34,55 @@ - + android:layout_height="match_parent"> + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index f4d0c0c97a0e..6e27d690486c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -494,6 +494,8 @@ Contribute as a developer, see <a href="https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> for details Move to… Copy to… + Loading folders… + No results " + mediaFolder.folder); + Log.d(TAG, "Reading images for --> " + mediaFolder.absolutePath); while (cursorImages.moveToNext()) { mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); } diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index f2ba8198c9cb..080b57e83d92 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -1,37 +1,37 @@ /** - * Nextcloud Android client application + * Nextcloud Android client application * - * @author Andy Scherzinger - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or 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 . + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.activity; -import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -41,15 +41,16 @@ import java.util.List; import java.util.TimerTask; -import com.owncloud.android.ui.adapter.FolderSyncAdapter; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends FileActivity { +public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; + private LinearLayout mProgress; + private TextView mEmpty; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,16 +70,13 @@ protected void onCreate(Bundle savedInstanceState) { private void setupContent() { - // TODO setup/initialize UI mRecyclerView = (RecyclerView) findViewById(android.R.id.list); + mProgress = (LinearLayout) findViewById(android.R.id.progress); + mEmpty = (TextView) findViewById(android.R.id.empty); + final int gridWidth = 4; - mAdapter = new FolderSyncAdapter(this, gridWidth, new FolderSyncAdapter.ClickListener() { - @Override - public void onClick(View view, int section, int relative, int absolute) { - selectItem(FolderSyncActivity.this); - } - }, mRecyclerView); + mAdapter = new FolderSyncAdapter(this, gridWidth, this, mRecyclerView); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); mAdapter.setLayoutManager(lm); @@ -88,11 +86,6 @@ public void onClick(View view, int section, int relative, int absolute) { load(); } - public static void selectItem(final Activity context) { - // TODO implement selectItem() - return; - } - private void load() { if (mAdapter.getItemCount() > 0) return; setListShown(false); @@ -103,7 +96,7 @@ public void run() { final List mediaFolders = MediaProvider.getAllShownImagesPath(FolderSyncActivity.this); for (MediaFolder mediaFolder : mediaFolders) { - Log.d(TAG, mediaFolder.path); + Log.d(TAG, mediaFolder.absolutePath); } mHandler.post(new TimerTask() { @@ -119,16 +112,9 @@ public void run() { void setListShown(boolean shown) { if (mRecyclerView != null) { - mRecyclerView.setVisibility(shown ? - View.VISIBLE : View.GONE); - - // TODO show/hide loading visuals - /** - mProgress.setVisibility(shown ? - View.GONE : View.VISIBLE); - mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? - View.VISIBLE : View.GONE); - **/ + mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE); + mProgress.setVisibility(shown ? View.GONE : View.VISIBLE); + mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); } } @@ -164,4 +150,14 @@ public void showFiles(boolean onDeviceOnly) { fileDisplayActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(fileDisplayActivity); } + + @Override + public void onSyncStatusToggleClick(int section, MediaFolder mediaFolder) { + Toast.makeText(this,"Sync Status Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder) { + Toast.makeText(this,"Menu Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + } } diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 99fe8b40a6e0..f7a1ed523685 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -25,14 +25,12 @@ import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; @@ -49,8 +47,7 @@ /** * Adapter to display all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncAdapter extends SectionedRecyclerViewAdapter - implements View.OnClickListener, View.OnTouchListener { +public class FolderSyncAdapter extends SectionedRecyclerViewAdapter { private static final String TAG = FolderSyncAdapter.class.getSimpleName(); @@ -74,16 +71,6 @@ public void setMediaFolders(List mediaFolders) { notifyDataSetChanged(); } - @Override - public void onClick(View v) { - - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - return false; - } - @Override public int getSectionCount() { return mMediaFolders.size(); @@ -95,29 +82,22 @@ public int getItemCount(int section) { } @Override - public void onBindHeaderViewHolder(MainViewHolder holder, int section) { - final int sectionId = section; - holder.title.setText(mMediaFolders.get(section).folder.substring(mMediaFolders.get(section).folder - .lastIndexOf("/") + 1, mMediaFolders.get(section).folder.length())); + public void onBindHeaderViewHolder(MainViewHolder holder, final int section) { + holder.title.setText(mMediaFolders.get(section).folderName); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); - holder.syncStatusButton.setOnTouchListener(new View.OnTouchListener() { + holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - Toast.makeText(mContext, "Sync Status Clicked for " + mMediaFolders.get(sectionId).folder, Toast - .LENGTH_SHORT) - .show(); - return true; + public void onClick(View v) { + mListener.onSyncStatusToggleClick(section,mMediaFolders.get(section)); } }); holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); - holder.menuButton.setOnTouchListener(new View.OnTouchListener() { + holder.menuButton.setOnClickListener(new View.OnClickListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - Toast.makeText(mContext, "Menu Clicked for " + mMediaFolders.get(sectionId).folder, Toast.LENGTH_SHORT) - .show(); - return true; + public void onClick(View v) { + mListener.onSyncFolderSettingsClick(section,mMediaFolders.get(section)); } }); } @@ -188,7 +168,7 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos } //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); - holder.itemView.setOnClickListener(this); + //holder.itemView.setOnClickListener(this); } @Override @@ -200,7 +180,8 @@ public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } public interface ClickListener { - void onClick(View view, int section, int relative, int absolute); + void onSyncStatusToggleClick(int section, MediaFolder mediaFolder); + void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder); } static class MainViewHolder extends RecyclerView.ViewHolder { From 67d7ec9741523f0c15bb069bc99871124f9a2298 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 01:06:52 +0200 Subject: [PATCH 36/99] renamed upload action column, extended SyncFolder POJO, createdd a draft implementation of a SyncFolderProvider for DB operations --- .../android/datamodel/MediaFolder.java | 21 ++- .../android/datamodel/SyncedFolder.java | 4 + .../datamodel/SyncedFolderProvider.java | 168 ++++++++++++++++++ src/com/owncloud/android/db/ProviderMeta.java | 2 +- .../providers/FileContentProvider.java | 4 +- .../observer/SyncedFolderObserverService.java | 2 +- 6 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 src/com/owncloud/android/datamodel/SyncedFolderProvider.java diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index c23c2541a3d7..db7137629506 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -1,3 +1,23 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; import java.util.ArrayList; @@ -6,7 +26,6 @@ /** * Created by scherzia on 22.09.2016. */ - public class MediaFolder { public String folderName; public String absolutePath; diff --git a/src/com/owncloud/android/datamodel/SyncedFolder.java b/src/com/owncloud/android/datamodel/SyncedFolder.java index 6082b8eeac1b..a0e5d648ef4b 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolder.java +++ b/src/com/owncloud/android/datamodel/SyncedFolder.java @@ -80,4 +80,8 @@ public Integer getUploadAction() { public boolean isEnabled() { return enabled; } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } \ No newline at end of file diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java new file mode 100644 index 000000000000..99eb429d69b8 --- /dev/null +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -0,0 +1,168 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.support.annotation.NonNull; + +import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.lib.common.utils.Log_OC; + +/** + * Database provider for handling the persistence aspects of synced folders. + */ +public class SyncedFolderProvider { + static private final String TAG = SyncedFolderProvider.class.getSimpleName(); + + private ContentResolver mContentResolver; + + public SyncedFolderProvider(ContentResolver contentResolver) { + if (contentResolver == null) { + throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); + } + mContentResolver = contentResolver; + } + + /** + * Stores an media folder sync object in database. + * + * @param syncedFolder synced folder to store + * @return upload id, -1 if the insert process fails. + */ + public long storeFolderSync(SyncedFolder syncedFolder) { + Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); + + ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); + + Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); + + if (result != null) { + return Long.parseLong(result.getPathSegments().get(1)); + } else { + Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); + return -1; + } + } + + /** + * Update upload status of file uniquely referenced by id. + * + * @param id folder sync id. + * @param enabled new status. + * @return the number of rows updated. + */ + public int updateFolderSyncEnabled(long id, Boolean enabled) { + Log_OC.v(TAG, "Storing sync folder id" + id + " with enabled=" + enabled); + + int result = 0; + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(id)}, + null + ); + + if (cursor == null || cursor.getCount() != 1) { + Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in UploadDb. Expected 1. Failed to " + + "update upload db."); + } else { + while (cursor.moveToNext()) { + // read sync folder object and update + SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); + + syncedFolder.setEnabled(enabled); + + // update sync folder object in db + result = updateSyncFolder(syncedFolder); + } + } + cursor.close(); + return result; + } + + public SyncedFolder findByLocalPath(String localPath) { + Cursor cursor = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + "==" + localPath, + null, + null + ); + + if (cursor == null || cursor.getCount() != 1) { + Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + " available in sync folder db. " + + "Expected 1. Failed to update sync folder db."); + return null; + } else { + return createSyncedFolderFromCursor(cursor); + } + } + + private int updateSyncFolder(SyncedFolder syncedFolder) { + Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); + + ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); + + return mContentResolver.update( + ProviderMeta.ProviderTableMeta.CONTENT_URI_UPLOADS, + cv, + ProviderMeta.ProviderTableMeta._ID + "=?", + new String[]{String.valueOf(syncedFolder.getId())} + ); + } + + private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { + SyncedFolder syncedFolder = null; + if (cursor != null) { + long id = cursor.getLong(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); + String localPath = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); + String remotePath = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); + Boolean wifiOnly = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; + Boolean chargingOnly = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; + Boolean subfolderByDate = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; + String accountName = cursor.getString(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); + Integer uploadAction = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); + Boolean enabled = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; + + syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, + accountName, uploadAction, enabled); + } + return syncedFolder; + } + + @NonNull + private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) { + ContentValues cv = new ContentValues(); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH, syncedFolder.getLocalPath()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.getRemotePath()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); + return cv; + } +} diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index 0a33735dfd25..533de14c9df3 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -162,6 +162,6 @@ static public class ProviderTableMeta implements BaseColumns { public static final String SYNCED_FOLDER_ENABLED = "enabled"; public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date"; public static final String SYNCED_FOLDER_ACCOUNT = "account"; - public static final String SYNCED_FOLDER_UPLOAD_OPTION = "upload_option"; + public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option"; } } \ No newline at end of file diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 93905d4d2866..9ff8b898be2e 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -929,7 +929,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + " INTEGER );" // upload action + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER );" // upload action ); // TODO Tobi remove after testing @@ -941,7 +941,7 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ENABLED + ", " // enabled + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION + ") " // upload action + + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + ") " // upload action + "VALUES ('/sdcard/DCIM/', '/syncTest', 0, 0, 1, 1, 'tobi', 1)"); } diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 1001f1c31214..051bb74626c3 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -84,7 +84,7 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor c) { Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); - Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_OPTION)); + Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, From 78dfb7cedf995a766be739c839a4f12a8a8e4ccf Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 01:14:02 +0200 Subject: [PATCH 37/99] make SyncedFolderObserverService use SyncedFolderProvider (todo also move the Observable to the Provider implementation) --- .../datamodel/SyncedFolderProvider.java | 25 +++++++++ .../observer/SyncedFolderObserverService.java | 55 ++----------------- 2 files changed, 29 insertions(+), 51 deletions(-) diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 99eb429d69b8..5843ff181e7a 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -65,6 +65,31 @@ public long storeFolderSync(SyncedFolder syncedFolder) { } } + public SyncedFolder[] getSyncedFolders() { + Cursor c = mContentResolver.query( + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, + null, + "1=1", + null, + null + ); + SyncedFolder[] list = new SyncedFolder[c.getCount()]; + if (c.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[c.getPosition()] = syncedFolder; + } + } while (c.moveToNext()); + + } + c.close(); + + return list; + } + /** * Update upload status of file uniquely referenced by id. * diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 051bb74626c3..f97af750b2f9 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -1,31 +1,28 @@ package com.owncloud.android.services.observer; import android.app.Service; -import android.content.ContentResolver; import android.content.Intent; -import android.database.Cursor; import android.os.IBinder; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.db.ProviderMeta; -import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.datamodel.SyncedFolderProvider; import java.util.ArrayList; public class SyncedFolderObserverService extends Service { private static final String TAG = "SyncedFolderObserverService"; - private ContentResolver database; + private SyncedFolderProvider mProvider; private ArrayList syncedFolderObservers = new ArrayList<>(); @Override public void onCreate() { - database = MainApp.getAppContext().getContentResolver(); + mProvider = new SyncedFolderProvider(MainApp.getAppContext().getContentResolver()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - for (SyncedFolder syncedFolder : getSyncedFolders()) { + for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) { SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder.getLocalPath(), syncedFolder.getRemotePath()); @@ -48,48 +45,4 @@ public void onDestroy() { public IBinder onBind(Intent arg0) { return null; } - - private SyncedFolder[] getSyncedFolders() { - Cursor c = database.query( - ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, - null, - "1=1", - null, - null - ); - SyncedFolder[] list = new SyncedFolder[c.getCount()]; - if (c.moveToFirst()) { - do { - SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); - if (syncedFolder == null) { - Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); - } else { - list[c.getPosition()] = syncedFolder; - } - } while (c.moveToNext()); - - } - c.close(); - - return list; - } - - private SyncedFolder createSyncedFolderFromCursor(Cursor c) { - SyncedFolder syncedFolder = null; - if (c != null) { - long id = c.getLong(c.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); - String localPath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); - String remotePath = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); - Boolean wifiOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; - Boolean chargingOnly = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; - Boolean subfolderByDate = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; - String accountName = c.getString(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); - Integer uploadAction = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); - Boolean enabled = c.getInt(c.getColumnIndex(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; - - syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, - accountName, uploadAction, enabled); - } - return syncedFolder; - } } From a04682fe5d112fc5043d7090b51e74f49c12102d Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 29 Sep 2016 10:48:08 +0200 Subject: [PATCH 38/99] hardening provider implementation + javadoc --- .../datamodel/SyncedFolderProvider.java | 155 +++++++++++++----- 1 file changed, 113 insertions(+), 42 deletions(-) diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 5843ff181e7a..b10e82c7fe19 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -1,22 +1,21 @@ /** - * Nextcloud Android client application + * Nextcloud Android client application * - * @author Andy Scherzinger - * Copyright (C) 2016 Andy Scherzinger - * Copyright (C) 2016 Nextcloud - *

- * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or 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 . + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; @@ -29,14 +28,21 @@ import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; +import java.util.Observable; + /** * Database provider for handling the persistence aspects of synced folders. */ -public class SyncedFolderProvider { +public class SyncedFolderProvider extends Observable { static private final String TAG = SyncedFolderProvider.class.getSimpleName(); private ContentResolver mContentResolver; + /** + * constructor. + * + * @param contentResolver the ContentResolver to work with. + */ public SyncedFolderProvider(ContentResolver contentResolver) { if (contentResolver == null) { throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); @@ -58,6 +64,7 @@ public long storeFolderSync(SyncedFolder syncedFolder) { Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); if (result != null) { + notifyFolderSyncObservers(); return Long.parseLong(result.getPathSegments().get(1)); } else { Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); @@ -65,29 +72,40 @@ public long storeFolderSync(SyncedFolder syncedFolder) { } } + /** + * get all synced folder entries. + * + * @return all synced folder entries, empty if none have been found + */ public SyncedFolder[] getSyncedFolders() { - Cursor c = mContentResolver.query( + Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, "1=1", null, null ); - SyncedFolder[] list = new SyncedFolder[c.getCount()]; - if (c.moveToFirst()) { - do { - SyncedFolder syncedFolder = createSyncedFolderFromCursor(c); - if (syncedFolder == null) { - Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); - } else { - list[c.getPosition()] = syncedFolder; - } - } while (c.moveToNext()); + if (cursor != null) { + SyncedFolder[] list = new SyncedFolder[cursor.getCount()]; + if (cursor.moveToFirst()) { + do { + SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); + if (syncedFolder == null) { + Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); + } else { + list[cursor.getPosition()] = syncedFolder; + } + } while (cursor.moveToNext()); + + } + cursor.close(); + return list; + } else { + Log_OC.e(TAG, "DB error creating read all cursor for synced folders."); } - c.close(); - return list; + return new SyncedFolder[0]; } /** @@ -109,10 +127,7 @@ public int updateFolderSyncEnabled(long id, Boolean enabled) { null ); - if (cursor == null || cursor.getCount() != 1) { - Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in UploadDb. Expected 1. Failed to " + - "update upload db."); - } else { + if (cursor != null && cursor.getCount() == 1) { while (cursor.moveToNext()) { // read sync folder object and update SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); @@ -121,13 +136,29 @@ public int updateFolderSyncEnabled(long id, Boolean enabled) { // update sync folder object in db result = updateSyncFolder(syncedFolder); + + cursor.close(); + } + } else { + if (cursor == null) { + Log_OC.e(TAG, "Sync folder db cursor for ID=" + id + " in NULL."); + } else { + Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in sync folder database. " + + "Expected 1. Failed to update sync folder db."); } } - cursor.close(); + return result; } + /** + * find a synced folder by local path. + * + * @param localPath the local path of the local folder + * @return the synced folder if found, else null + */ public SyncedFolder findByLocalPath(String localPath) { + SyncedFolder result = null; Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, @@ -136,28 +167,53 @@ public SyncedFolder findByLocalPath(String localPath) { null ); - if (cursor == null || cursor.getCount() != 1) { - Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + " available in sync folder db. " + - "Expected 1. Failed to update sync folder db."); - return null; + if (cursor != null && cursor.getCount() == 1) { + result = createSyncedFolderFromCursor(cursor); + cursor.close(); } else { - return createSyncedFolderFromCursor(cursor); + if (cursor == null) { + Log_OC.e(TAG, "Sync folder db cursor for local path=" + localPath + " in NULL."); + } else { + Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + + " available in sync folder db. Expected 1. Failed to update sync folder db."); + } } + + return result; + } + /** + * update given synced folder. + * + * @param syncedFolder the synced folder to be updated. + * @return the number of rows updated. + */ private int updateSyncFolder(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); - return mContentResolver.update( + int result = mContentResolver.update( ProviderMeta.ProviderTableMeta.CONTENT_URI_UPLOADS, cv, ProviderMeta.ProviderTableMeta._ID + "=?", new String[]{String.valueOf(syncedFolder.getId())} ); + + if (result > 0) { + notifyFolderSyncObservers(); + } + + return result; } + /** + * maps a cursor into a SyncedFolder object. + * + * @param cursor the cursor + * @return the mapped SyncedFolder, null if cursor is null + */ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { SyncedFolder syncedFolder = null; if (cursor != null) { @@ -177,6 +233,12 @@ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { return syncedFolder; } + /** + * create ContentValues object based on given SyncedFolder. + * + * @param syncedFolder the synced folder + * @return the corresponding ContentValues object + */ @NonNull private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) { ContentValues cv = new ContentValues(); @@ -190,4 +252,13 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); return cv; } + + /** + * Inform all observers about data change. + */ + private void notifyFolderSyncObservers() { + Log_OC.d(TAG, "notifying folder sync data observers"); + setChanged(); + notifyObservers(); + } } From 17b9eaed96cf095ae9700d294d3e36444f1bff3f Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Fri, 30 Sep 2016 01:27:50 +0200 Subject: [PATCH 39/99] initial code to read (yet empty DB) and display settings screen (non-functional yet) --- res/drawable-hdpi/ic_cellphone.png | Bin 0 -> 412 bytes res/drawable-hdpi/ic_cloud_outline.png | Bin 0 -> 920 bytes res/drawable-mdpi/ic_cellphone.png | Bin 0 -> 309 bytes res/drawable-mdpi/ic_cloud_outline.png | Bin 0 -> 600 bytes res/drawable-xhdpi/ic_cellphone.png | Bin 0 -> 512 bytes res/drawable-xhdpi/ic_cloud_outline.png | Bin 0 -> 1227 bytes res/drawable-xxhdpi/ic_cellphone.png | Bin 0 -> 645 bytes res/drawable-xxhdpi/ic_cloud_outline.png | Bin 0 -> 1906 bytes res/drawable-xxxhdpi/ic_cellphone.png | Bin 0 -> 879 bytes res/drawable-xxxhdpi/ic_cloud_outline.png | Bin 0 -> 2514 bytes res/layout/folder_sync_settings_layout.xml | 371 ++++++++++++++++++ res/values/strings.xml | 3 + res/xml/folder_sync_preferences.xml | 92 +++++ .../android/datamodel/MediaProvider.java | 69 ++-- .../android/datamodel/SyncedFolderItem.java | 66 ++++ .../datamodel/SyncedFolderProvider.java | 10 +- .../observer/SyncedFolderObserverService.java | 3 +- .../ui/activity/FolderSyncActivity.java | 102 ++++- .../android/ui/adapter/FolderSyncAdapter.java | 32 +- ...SyncedFolderPreferencesDialogFragment.java | 138 +++++++ .../dialog/parcel/SyncedFolderParcelable.java | 172 ++++++++ 21 files changed, 1000 insertions(+), 58 deletions(-) create mode 100644 res/drawable-hdpi/ic_cellphone.png create mode 100644 res/drawable-hdpi/ic_cloud_outline.png create mode 100644 res/drawable-mdpi/ic_cellphone.png create mode 100644 res/drawable-mdpi/ic_cloud_outline.png create mode 100644 res/drawable-xhdpi/ic_cellphone.png create mode 100644 res/drawable-xhdpi/ic_cloud_outline.png create mode 100644 res/drawable-xxhdpi/ic_cellphone.png create mode 100644 res/drawable-xxhdpi/ic_cloud_outline.png create mode 100644 res/drawable-xxxhdpi/ic_cellphone.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_outline.png create mode 100644 res/layout/folder_sync_settings_layout.xml create mode 100644 res/xml/folder_sync_preferences.xml create mode 100644 src/com/owncloud/android/datamodel/SyncedFolderItem.java create mode 100644 src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java create mode 100644 src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java diff --git a/res/drawable-hdpi/ic_cellphone.png b/res/drawable-hdpi/ic_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..aab56fea7077927c163030aa38e34db5ea60f77a GIT binary patch literal 412 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3#+VMs-gY$B>A_Z)X_#F&hfF+Ah}66Ws z)ciy$`!`SAd(#uV)09;TdM>zzY|(snxkW@UyZX9dq{{zm@%8hLU#^%UHB;kiMCjF? zj%O|=Z-W^QFdkTzIqQ2jLu7V7!ybl+l)H0Qs4kjX{7(DHN z*Q6THoImJrW)Gv)=Bb9q%0zh6+OpiX%SXKq)BGy)S@qS`YWbKLm-LRA(_OASnlVPF?vfuDi%!24O2y*WEO`)gN$!Z56- zY5ELs47hHiT2=i+L|*bd@7XX6|LizW5CnTv^(|E$+~PxDP}N2p$KP(I*x5{xF=p6W zd%6lEB3}Vdi^$!c=Xt7Hx7H2;$BSI4yF}z;W6W?z9BMY3yRt0%64+gYeixBrtyb&R zjjn5qIjX8Jib%bP{ouOp@bvWbZ&&3|mSwXQm}P72f&YL3&?t&t6OjjjG3F7VoMWxELrIcc*px%#X`3BgtM(V7!b}UJfivW)A`wv%OE<28MsM9b2s=B;RYM<-6 zXMNwl9aj6rE5H*)@`Wghj&~ZSsz#&X4h#$o0FMGw6kCvpEcW;J-&X@h%VH6Eqnj`Q z^YimLumn681i^>a+7Ez!RlPS&(}u&}O%LF^ZUa>{Gc)tKss*-4-*%Z^qI zIVS+dm`{L*ig;HV<5~)WV1K#FiqMhj)^QHFu}E}u>!@nIJ_Iayo;T7yeoRbEJm5Ia zDd47VJF|v<0rp2xwA9}1lO#CM3w!G6|D`LbN;Cyr8y?vJ0000(}6TtKSPDj(q%x-0Z$jl5Rc<;r*7mu)@-h4Xh2HT8~Q} zI4H4s^CUuzFQE!|RPRDw8{P)aNT1Joa_iq)=IO?ridz*SqYP zR@6j^C4cZ!bzo9)VEV(Pz|W}^Wa@kPzmI|v^T|j1u7Ce~xLS6(@I*x^ha&!juV)Ml qgxL*_m8_bTc~pwECC(o+`}|Ymo{C%T00BO0TU%Dec-yPD&YQt=4T|-iyBk zjs`&xIOld6V~zpOfC_LoilTHwH?Y<=fR4B0jH*Yx;}zLw3&12n-)urBZp#a9ziOh}=q&R#%@m+$B>MBZ)Y0%9d;0K{of=SeMn@bC6m*_ zX5l5A;tk9$nL$f7h**_J6iqzD>*dTY{y<=%W2b=og2fJAN;kvSEL`Zpm?n0c$K$h> z_-^a}XHT5fTW}&N$Gj!+M%3Cc=HxS$4ayAFqQ+|&Z3HjQegFGjEzAF8Q*U}~3nLI}wZ$=4s4Tki{jZ~lYd)`;$I7|o Y*D6zgs)ipe0>%-8r>mdKI;Vst0Q&gPZvX%Q literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_cloud_outline.png b/res/drawable-xhdpi/ic_cloud_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..1da7705aef8ed2188b055ddbc5a799bbc534bf4b GIT binary patch literal 1227 zcmV;+1T_1JP)xEv8d?53QE1%B2sZ`47?(W_V^e*HDL@1i3na<^MVXYH&J?4|i42BJyNRz6|6mm5MjZp8jd_IhanT zI}?e74P=0hsQ{(aGs$G~Jg~hMKQ(P3%d)m8rS`|<--94{v`{D7B9qCiG7RG=ureZ#nx=V6E|(iy za02OcdU<<$`_m%w02BZ1%Ob-SrPPD2>mIAe{1iKO>{zR{{ua1y+LTh3>$-h)Cy-93 zmnRYlA4ma-YB3_R%lG})7Gi!1%d*~3O1&D9{wS47UH$$2m#;QgS6OUJB9ZtJ*gBhe z;DXkA*J79lFbpFP{27s!b#`{Ho6p=C2{?}PvQp|}pmVNCN~!&>>t0-}1oHWO2{;th zL}bH!=1y;+$y4eGmH2{;Ly00xGJhK>vj z3!CJtt(XWHOoC48!<&j*wrp*1h?B{zz2R0AU!u9yc!{ z8$Hi^yAkFA+S=Mit`c%<7=~XujV+j|16PHSt+&6b=`LwVLXa$+cM8k*YmtA zfDVG-F=mz&CkF=y_chu)faiH?>JgD8N~!ymQg1P_ooD3gI8G0MvTged;Nhte5!vqh z{+?zeFdMdQ-vbU3gW;;5t+p?^O znr%2rHk-X0cp)YqQ~-|StPqjYz|zQILyNh&nC&1dl}c-(<#dmT9N@|(ry=EZ9Lusy zt@T~NQzG&b&>kHZk{EXH*>$+~VYn%I8#c`aKKo9VUh$MkkfY~6=W#Ale95`T_ p=4ZKF?$QF+Y|)}cix&Se{syH=n7Zc2!DRpd002ovPDHLkV1nUeN_PMN literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_cellphone.png b/res/drawable-xxhdpi/ic_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..393e0e3fa5da3bcd508c7dd5b765b54f1f87dec6 GIT binary patch literal 645 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Y)RhkE=uJQvb0|S$Qr;B4q#jUq@4D*B?C60dFzWb#>&U1uRq4 z932B+T~K(+aDa79YGp^xK&s2)ZaEHaRCAUZ`)^YyzqPK zw6Z^M%Uj4i*Gtahr%010n+x|AZ?Pg}!z4~TMOyjFvc8>qv&#AYJJG;X$e8H4U zmG78p&%fAvgs+(WMYUW0g1vXr?|<*HK4)HE^S^bIPXDpTAD_#pwjF-BaOT;YJ+BMy zAN^O(<)R_5(4mD%km`(k^|$^XJo8MkMMph3ZSMM&{s?;>?(MEU+;%?ssKoWEM^-Hp zWQ_C9{O0`i%I2Tqj(Yt~*|TOnyLRRrgHDn7&!dOVoZIuej6wD?oPL8@gwVu2bqs=UYrW&tvp~s literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_cloud_outline.png b/res/drawable-xxhdpi/ic_cloud_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ad000c7d24fbc70dfa9a2245823aca50dd704c GIT binary patch literal 1906 zcmV-&2aWiNP)FteRpfz{YaL4``)~n_toyq?940>5)u*;5)u*;5)u*;QfVlNN)w4h9W%E8 zm`_CWnfYP>=j8vQ0KNcln20j2>+Tam^dCQdJl)gNb8N;6q?C#b4i5g1i0%Qf6u|5v zLkttqYXF`a7#P@*N~Ll$MIbT^<8}a#0GL;zm>&RmSW3A?DU~g2YMUteEeS?VZtK&4cb z<2YLY+)*xJykO=RYin!oR7#yFW!#bqq?D=|8X9_mnSV3^_oD#PMD)7ry8mS6|J2ph z9RZLX9v-eA85wC3LR`tri-_nJjE->Q@nPn-`Tk;t{3ot;NYEzZd(rD`3=*$d!O4|A4?t}spW-LeX#l!^=u z4c)=as{mX#fg;SzEw*j%DW^DN7>02TfPcVa;!8xEP19UiHi2|qzn+Mm0B}VSakpBQ zb!R2SIjNnSi0DBN(-0AzZ<^*8lZop)%*W&L*_x)A`AUoFtG*?nRheu4U-AT%uiQZoH1P2^}9>HKiQb4^g%0Eu8h{#*Y5_EdX!1uKP-NclR4b3uG9E1wi+8Ci;Y#ACgi& zQ|^VT$zmAB-vP{@fa|{iY?e~~uF!kiR|4s}ej^d>g8#_YOGI}~b~LCIx~^{_qPvSI zvY(mTY}bc*s{EUDuZa_^wZYUMb}- zi+uwRP@u`Y))0WThGA@u#bT#SHY53U z+RuT}PWoFuPOk=?NIB{1>N+|yGV)_)e$UHJM2T1|rU?KSdpYECx%V)`*|>4zX8<1X zF`w7m+*LDBqb)A_n5JdE$mr>KSOM-5ArO-6(kdJAOVCJ`dj5i0}@M%C)0Ps5%v0N!t zvYprIOfHve^f7%dn0Y5gr`M-*k>fb`2Hp5dAcXMTp=IWO3){8_nAvZ8W^G$r+gU*; zPytqWT_mDCBLC0hJ_YB@nlrDYC)@AwqBl!)$KxNza;ix)56mrkd@Hj8MdqwD%2X6^-WjgQMg zDdk=1bUJ&=8gzc!WH*2Xi7MmC%ME4)uj{6DbH zvaDZ^{qWqy$cyv}fNO(pZ6zV1=S|aGfs^Kio+DFdXXg(}8pX7+2iB`GtXj>6fRAV@SoVw|DKcg9BxbeSH2rGf?B&&PzO^ z;^N2kZptoUIdbr5)B6KLCuBus{XI@1Gq_MAAL}rewfc2IuJ9+0$n*BJzO?%6{ z`<1_*|J>71U3AX;xth5%pMsLo^{G>3)~s3;%Am_2t+~{cftSJiiKzwShWxZ@hB5}8 zUV*8hbYq~nrB zjEpy{$5|g#bqEbD5B9pxptD=g*YbGUH-%#rGkzb^dw*`qCFz*CN7);m{+zSv^giZW zZ_AWhJ_w8dzkM#!_w}xQ+xPC>JOA^do8=B13tLxj|G}`qV19I%-1Y^>S;d-Ff8W`} zZm{`x>a|5S{X0|nCj>=lZkBmkdFJfnxY}33`P*;n{;HV~5}!gagApqd@wtLeLizFkH~-(yWS%+S_L$^))&tKuguj0l zkFW1}zuPm%iox^84bJb+6w%_e{Ib z_<8a(Hr22t8yw#Se_>d@eqq`-sRJ8-PnuF}Ge5XYk1?sIJ#8Cj!n0jEsb|jQTz;$) zdf=nO^NSf@_TJ2qW2|U7lzu(kg!#g|vfc7~*e*b(nQs|yFdFn2 j%1wx$+|d!kyq{6FciogVW|sqj*@3~+)z4*}Q$iB}x2b&k literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_cloud_outline.png b/res/drawable-xxxhdpi/ic_cloud_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..c615a96fe071fdc42a120e89ddc2d77dd8685119 GIT binary patch literal 2514 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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 6e27d690486c..c21434729ddb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -105,6 +105,7 @@ Cancel sync Cancel Back + Save Save & exit Error Loading … @@ -496,6 +497,8 @@ Copy to… Loading folders… No results + Folder Sync Preferences + Settings + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 161b5d2ff43d..ca64d9b4ba43 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -21,7 +21,7 @@ package com.owncloud.android.datamodel; -import android.app.Activity; +import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; @@ -41,18 +41,18 @@ public class MediaProvider { * TODO rewrite * Getting All Images Path * - * @param activity + * @param contentResolver the content resolver * @return List with images folders */ - public static List getAllShownImagesPath(Activity activity) { + public static List getAllShownImagesPath(ContentResolver contentResolver) { Cursor cursor; + List mediaFolders = new ArrayList<>(); int column_index_data, column_index_folder_name, column_index_data_image; ArrayList listOfAllImages = new ArrayList<>(); - String absolutePathOfImage = null; - String folderName = null; + String absolutePathOfImage; + String folderName; String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; - //String[] projection = {MediaStore.Images.Media.BUCKET_DISPLAY_NAME,MediaStore.Images.Media.BUCKET_ID}; String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore .MediaColumns.DATA}; String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; @@ -63,41 +63,52 @@ public static List getAllShownImagesPath(Activity activity) { String folderSortOrder = "MAX(" + MediaStore.Images.Media.DISPLAY_NAME + ") DESC"; String fileSortOrder = MediaStore.MediaColumns.DATA + " DESC LIMIT 8"; // LIMIT 8 - cursor = activity.getContentResolver().query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); + cursor = contentResolver.query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); + + if (cursor == null) { + return mediaFolders; + } column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); column_index_folder_name = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME); + Cursor cursorImages; - List mediaFolders = new ArrayList<>(); while (cursor.moveToNext()) { - MediaFolder mediaFolder = new MediaFolder(); absolutePathOfImage = cursor.getString(column_index_data); - folderName = cursor.getString(column_index_folder_name); - mediaFolder.folderName = folderName; - mediaFolder.absolutePath = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + - folderName - .length()); - mediaFolder.filePaths = new ArrayList<>(); - - // TODO: This can be done with one query, no limit, but only adding the 8 to the list and still get the - // total count - Cursor cursorImages = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + "'" + + cursorImages = contentResolver.query(MEDIA_URI, fileProjection, fileSelection + "'" + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, fileSortOrder); - column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - Log.d(TAG, "Reading images for --> " + mediaFolder.absolutePath); - while (cursorImages.moveToNext()) { - mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); - } - mediaFolder.numberOfFiles = activity.getContentResolver().query(MEDIA_URI, fileProjection, fileSelection + - "'" + - absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, - null).getCount(); + if (cursorImages != null) { + + MediaFolder mediaFolder = new MediaFolder(); + folderName = cursor.getString(column_index_folder_name); + mediaFolder.folderName = folderName; + mediaFolder.absolutePath = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + + folderName + .length()); + mediaFolder.filePaths = new ArrayList<>(); + + column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + Log.d(TAG, "Reading images for --> " + mediaFolder.absolutePath); + while (cursorImages.moveToNext()) { + mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); + } + cursorImages.close(); - mediaFolders.add(mediaFolder); + mediaFolder.numberOfFiles = contentResolver.query( + MEDIA_URI, + fileProjection, + fileSelection + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", + null, + null).getCount(); + + mediaFolders.add(mediaFolder); + } } + cursor.close(); return mediaFolders; } diff --git a/src/com/owncloud/android/datamodel/SyncedFolderItem.java b/src/com/owncloud/android/datamodel/SyncedFolderItem.java new file mode 100644 index 000000000000..45896c30e202 --- /dev/null +++ b/src/com/owncloud/android/datamodel/SyncedFolderItem.java @@ -0,0 +1,66 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.datamodel; + +import java.util.List; + +/** + * TODO javadoc + */ +public class SyncedFolderItem extends SyncedFolder { + private List filePaths; + private String folderName; + private long numberOfFiles; + + public SyncedFolderItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, + List filePaths, String folderName, long numberOfFiles) { + super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled); + this.filePaths = filePaths; + this.folderName = folderName; + this.numberOfFiles = numberOfFiles; + } + + public List getFilePaths() { + return filePaths; + } + + public void setFilePaths(List filePaths) { + this.filePaths = filePaths; + } + + public String getFolderName() { + return folderName; + } + + public void setFolderName(String folderName) { + this.folderName = folderName; + } + + public long getNumberOfFiles() { + return numberOfFiles; + } + + public void setNumberOfFiles(long numberOfFiles) { + this.numberOfFiles = numberOfFiles; + } +} diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index b10e82c7fe19..dac2526f7d22 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -28,6 +28,8 @@ import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; +import java.util.ArrayList; +import java.util.List; import java.util.Observable; /** @@ -77,7 +79,7 @@ public long storeFolderSync(SyncedFolder syncedFolder) { * * @return all synced folder entries, empty if none have been found */ - public SyncedFolder[] getSyncedFolders() { + public List getSyncedFolders() { Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, @@ -87,14 +89,14 @@ public SyncedFolder[] getSyncedFolders() { ); if (cursor != null) { - SyncedFolder[] list = new SyncedFolder[cursor.getCount()]; + List list = new ArrayList<>(cursor.getCount()); if (cursor.moveToFirst()) { do { SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); if (syncedFolder == null) { Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); } else { - list[cursor.getPosition()] = syncedFolder; + list.add(cursor.getPosition(), syncedFolder); } } while (cursor.moveToNext()); @@ -105,7 +107,7 @@ public SyncedFolder[] getSyncedFolders() { Log_OC.e(TAG, "DB error creating read all cursor for synced folders."); } - return new SyncedFolder[0]; + return new ArrayList<>(0); } /** diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index f97af750b2f9..608325bd1678 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -9,11 +9,12 @@ import com.owncloud.android.datamodel.SyncedFolderProvider; import java.util.ArrayList; +import java.util.List; public class SyncedFolderObserverService extends Service { private static final String TAG = "SyncedFolderObserverService"; private SyncedFolderProvider mProvider; - private ArrayList syncedFolderObservers = new ArrayList<>(); + private List syncedFolderObservers = new ArrayList<>(); @Override public void onCreate() { diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 080b57e83d92..40b4d92cdf23 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -24,6 +24,8 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -35,22 +37,35 @@ import com.owncloud.android.MainApp; import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.MediaFolder; import com.owncloud.android.datamodel.MediaProvider; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.TimerTask; /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener { +public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener, SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { private static final String TAG = FolderSyncActivity.class.getSimpleName(); + + private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; + private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; private LinearLayout mProgress; private TextView mEmpty; + private SyncedFolderProvider mSyncedFolderProvider; + private List syncFolderItems; @Override protected void onCreate(Bundle savedInstanceState) { @@ -77,6 +92,7 @@ private void setupContent() { final int gridWidth = 4; mAdapter = new FolderSyncAdapter(this, gridWidth, this, mRecyclerView); + mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver()); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); mAdapter.setLayoutManager(lm); @@ -93,7 +109,9 @@ private void load() { new Thread(new Runnable() { @Override public void run() { - final List mediaFolders = MediaProvider.getAllShownImagesPath(FolderSyncActivity.this); + final List mediaFolders = MediaProvider.getAllShownImagesPath(getContentResolver()); + syncFolderItems = mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), + mediaFolders); for (MediaFolder mediaFolder : mediaFolders) { Log.d(TAG, mediaFolder.absolutePath); @@ -102,7 +120,7 @@ public void run() { mHandler.post(new TimerTask() { @Override public void run() { - mAdapter.setMediaFolders(mediaFolders); + mAdapter.setSyncFolderItems(syncFolderItems); setListShown(true); } }); @@ -110,7 +128,65 @@ public void run() { }).start(); } - void setListShown(boolean shown) { + private List mergeFolderData(List syncedFolders, List mediaFolders) { + Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); + List result = new ArrayList<>(); + + for (MediaFolder mediaFolder : mediaFolders) { + if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { + SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath); + result.add(createSyncedFolder(syncedFolder,mediaFolder)); + } else { + result.add(createSyncedFolderFromMediaFolder(mediaFolder)); + } + } + + return result; + } + + private SyncedFolderItem createSyncedFolder(SyncedFolder syncedFolder, MediaFolder mediaFolder) { + return new SyncedFolderItem( + syncedFolder.getId(), + syncedFolder.getLocalPath(), + syncedFolder.getRemotePath(), + syncedFolder.getWifiOnly(), + syncedFolder.getChargingOnly(), + syncedFolder.getSubfolderByDate(), + syncedFolder.getAccount(), + syncedFolder.getUploadAction(), + syncedFolder.isEnabled(), + mediaFolder.filePaths, + mediaFolder.folderName, + mediaFolder.numberOfFiles); + } + + private SyncedFolderItem createSyncedFolderFromMediaFolder(MediaFolder mediaFolder) { + return new SyncedFolderItem( + 0, + mediaFolder.absolutePath, + getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName, + true, + false, + false, + AccountUtils.getCurrentOwnCloudAccount(this).name, + 1, + false, + mediaFolder.filePaths, + mediaFolder.folderName, + mediaFolder.numberOfFiles); + } + + private Map createSyncedFoldersMap(List syncFolders) { + Map result = new HashMap<>(); + if (syncFolders != null) { + for (SyncedFolder syncFolder : syncFolders) { + result.put(syncFolder.getLocalPath(), syncFolder); + } + } + return result; + } + + private void setListShown(boolean shown) { if (mRecyclerView != null) { mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE); mProgress.setVisibility(shown ? View.GONE : View.VISIBLE); @@ -152,12 +228,22 @@ public void showFiles(boolean onDeviceOnly) { } @Override - public void onSyncStatusToggleClick(int section, MediaFolder mediaFolder) { - Toast.makeText(this,"Sync Status Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + public void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem) { + Toast.makeText(this, "Sync Status Clicked for " + syncedFolderItem.getLocalPath(), Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSyncFolderSettingsClick(int section, SyncedFolderItem syncedFolderItem) { + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + ft.addToBackStack(null); + + SyncedFolderPreferencesDialogFragment.newInstance(syncedFolderItem) + .show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG); } @Override - public void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder) { - Toast.makeText(this,"Menu Clicked for " + mediaFolder.absolutePath,Toast.LENGTH_SHORT).show(); + public void onSaveSyncedFolderPreference() { + Toast.makeText(this, "onSaveSyncedFolderPreference clicked", Toast.LENGTH_SHORT).show(); } } diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index f7a1ed523685..69a68c928b42 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -34,7 +34,7 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; -import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.SyncedFolderItem; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.BitmapUtils; @@ -54,42 +54,42 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mMediaFolders; + private final List mSyncFolderItems; private final RecyclerView mRecyclerView; public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { mContext = context; mGridWidth = gridWidth * 2; mListener = listener; - mMediaFolders = new ArrayList<>(); + mSyncFolderItems = new ArrayList<>(); mRecyclerView = recyclerView; } - public void setMediaFolders(List mediaFolders) { - mMediaFolders.clear(); - mMediaFolders.addAll(mediaFolders); + public void setSyncFolderItems(List syncFolderItems) { + mSyncFolderItems.clear(); + mSyncFolderItems.addAll(syncFolderItems); notifyDataSetChanged(); } @Override public int getSectionCount() { - return mMediaFolders.size(); + return mSyncFolderItems.size(); } @Override public int getItemCount(int section) { - return mMediaFolders.get(section).filePaths.size(); + return mSyncFolderItems.get(section).getFilePaths().size(); } @Override public void onBindHeaderViewHolder(MainViewHolder holder, final int section) { - holder.title.setText(mMediaFolders.get(section).folderName); + holder.title.setText(mSyncFolderItems.get(section).getFolderName()); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mListener.onSyncStatusToggleClick(section,mMediaFolders.get(section)); + mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); } }); holder.menuButton.setVisibility(View.VISIBLE); @@ -97,7 +97,7 @@ public void onClick(View v) { holder.menuButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mListener.onSyncFolderSettingsClick(section,mMediaFolders.get(section)); + mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section)); } }); } @@ -106,7 +106,7 @@ public void onClick(View v) { public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { final Context c = holder.itemView.getContext(); - File file = new File(mMediaFolders.get(section).filePaths.get(relativePosition)); + File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); /** Cancellation needs do be checked and done before changing the drawable in fileIcon, or * {@link ThumbnailsCacheManager#cancelPotentialThumbnailWork} will NEVER cancel any task. @@ -158,8 +158,8 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); } - if(mMediaFolders.get(section).numberOfFiles > 8 && relativePosition >= 8-1) { - holder.counterValue.setText(Long.toString(mMediaFolders.get(section).numberOfFiles-8)); + if (mSyncFolderItems.get(section).getNumberOfFiles() > 8 && relativePosition >= 8 - 1) { + holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - 8)); holder.counterBar.setVisibility(View.VISIBLE); holder.thumbnailDarkener.setVisibility(View.VISIBLE); } else { @@ -180,8 +180,8 @@ public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } public interface ClickListener { - void onSyncStatusToggleClick(int section, MediaFolder mediaFolder); - void onSyncFolderSettingsClick(int section, MediaFolder mediaFolder); + void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem); + void onSyncFolderSettingsClick(int section, SyncedFolderItem syncedFolderItem); } static class MainViewHolder extends RecyclerView.ViewHolder { diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java new file mode 100644 index 000000000000..d3c5388a555d --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -0,0 +1,138 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.dialog; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.owncloud.android.R; +import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; + +/** + * Dialog to show the preferences/configuration of a synced folder allowing the user + * to change the different parameters. + */ +public class SyncedFolderPreferencesDialogFragment extends DialogFragment { + + private final static String TAG = SyncedFolderPreferencesDialogFragment.class.getSimpleName(); + public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable"; + + protected View mView = null; + + private SyncedFolderParcelable mSyncedFolder; + + public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem syncedFolder) { + SyncedFolderPreferencesDialogFragment dialogFragment = new SyncedFolderPreferencesDialogFragment(); + + if (syncedFolder == null) { + throw new IllegalArgumentException("SyncedFolder is mandatory but NULL!"); + } + + Bundle args = new Bundle(); + args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder)); + dialogFragment.setArguments(args); + + return dialogFragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (!(activity instanceof OnSyncedFolderPreferenceListener)) { + throw new IllegalArgumentException("The host activity must implement " + OnSyncedFolderPreferenceListener.class.getCanonicalName()); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // keep the state of the fragment on configuration changes + setRetainInstance(true); + + // TODO check UX if it shouldn't be cancelable + //setCancelable(false); + mView = null; + + mSyncedFolder = getArguments().getParcelable(SYNCED_FOLDER_PARCELABLE); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateView, savedInstanceState is " + savedInstanceState); + + mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false); + + Button save = (Button) mView.findViewById(R.id.save); + save.setOnClickListener(new OnSyncedFolderSaveClickListener()); + + Button cancel = (Button) mView.findViewById(R.id.cancel); + cancel.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + + ((TextView) mView.findViewById(R.id.local_folder_summary)).setText(mSyncedFolder.getLocalPath()); + ((TextView) mView.findViewById(R.id.remote_folder_summary)).setText(mSyncedFolder.getRemotePath()); + + return mView; + } + + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setTitle(R.string.folder_sync_preferences); + return dialog; + } + + @Override + public void onDestroyView() { + Log_OC.d(TAG, "destroy SyncedFolderPreferencesDialogFragment view"); + if (getDialog() != null && getRetainInstance()) + getDialog().setDismissMessage(null); + super.onDestroyView(); + } + + private class OnSyncedFolderSaveClickListener implements OnClickListener { + @Override + public void onClick(View v) { + dismiss(); + ((OnSyncedFolderPreferenceListener) getActivity()).onSaveSyncedFolderPreference(); + } + } + + public interface OnSyncedFolderPreferenceListener { + public void onSaveSyncedFolderPreference(); + } +} diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java new file mode 100644 index 000000000000..89fe8a2f1a3e --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -0,0 +1,172 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.dialog.parcel; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.owncloud.android.datamodel.SyncedFolderItem; + +/** + * Parceble for SyncedFolder objects to transport them from/to dialog fragments. + */ +public class SyncedFolderParcelable implements Parcelable { + private String mLocalPath; + private String mRemotePath; + private Boolean mWifiOnly; + private Boolean mChargingOnly; + private boolean mEnabled; + private Boolean mSubfolderByDate; + private Integer mUploadAction; + private long mId; + private String mAccount; + + public SyncedFolderParcelable() { + } + + public SyncedFolderParcelable(SyncedFolderItem syncedFolderItem) { + mId = syncedFolderItem.getId(); + mLocalPath = syncedFolderItem.getLocalPath(); + mRemotePath = syncedFolderItem.getRemotePath(); + mWifiOnly = syncedFolderItem.getWifiOnly(); + mChargingOnly = syncedFolderItem.getChargingOnly(); + mEnabled = syncedFolderItem.isEnabled(); + mSubfolderByDate = syncedFolderItem.getSubfolderByDate(); + mAccount = syncedFolderItem.getAccount(); + mUploadAction = syncedFolderItem.getUploadAction(); + } + + public SyncedFolderParcelable(Parcel read) { + mId = read.readLong(); + mLocalPath = read.readString(); + mRemotePath = read.readString(); + mWifiOnly = read.readInt()!= 0; + mChargingOnly = read.readInt() != 0; + mEnabled = read.readInt() != 0; + mSubfolderByDate = read.readInt() != 0; + mAccount = read.readString(); + mUploadAction = read.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeString(mLocalPath); + dest.writeString(mRemotePath); + dest.writeInt(mWifiOnly ? 1 : 0); + dest.writeInt(mChargingOnly ? 1 : 0); + dest.writeInt(mEnabled ? 1 : 0); + dest.writeInt(mSubfolderByDate ? 1 : 0); + dest.writeString(mAccount); + dest.writeInt(mUploadAction); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public SyncedFolderParcelable createFromParcel(Parcel source) { + return new SyncedFolderParcelable(source); + } + + @Override + public SyncedFolderParcelable[] newArray(int size) { + return new SyncedFolderParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + public String getLocalPath() { + return mLocalPath; + } + + public void setLocalPath(String mLocalPath) { + this.mLocalPath = mLocalPath; + } + + public String getRemotePath() { + return mRemotePath; + } + + public void setRemotePath(String mRemotePath) { + this.mRemotePath = mRemotePath; + } + + public Boolean getWifiOnly() { + return mWifiOnly; + } + + public void setWifiOnly(Boolean mWifiOnly) { + this.mWifiOnly = mWifiOnly; + } + + public Boolean getChargingOnly() { + return mChargingOnly; + } + + public void setChargingOnly(Boolean mChargingOnly) { + this.mChargingOnly = mChargingOnly; + } + + public boolean isEnabled() { + return mEnabled; + } + + public void setEnabled(boolean mEnabled) { + this.mEnabled = mEnabled; + } + + public Boolean getSubfolderByDate() { + return mSubfolderByDate; + } + + public void setSubfolderByDate(Boolean mSubfolderByDate) { + this.mSubfolderByDate = mSubfolderByDate; + } + + public Integer getUploadAction() { + return mUploadAction; + } + + public void setUploadAction(Integer mUploadAction) { + this.mUploadAction = mUploadAction; + } + + public long getId() { + return mId; + } + + public void setId(long mId) { + this.mId = mId; + } + + public String getAccount() { + return mAccount; + } + + public void setAccount(String mAccount) { + this.mAccount = mAccount; + } +} From b9c15c127ca9b5544a8e54bbd9045276a5b4738a Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Fri, 30 Sep 2016 22:02:13 +0200 Subject: [PATCH 40/99] toggle folder's sync on/off - wip since state persistence is not working reliably yet --- .../android/datamodel/SyncedFolderItem.java | 1 + .../datamodel/SyncedFolderProvider.java | 2 +- .../providers/FileContentProvider.java | 18 +++++++++++++ .../ui/activity/FolderSyncActivity.java | 26 ++++++++++++++----- .../android/ui/adapter/FolderSyncAdapter.java | 14 +++++++++- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/com/owncloud/android/datamodel/SyncedFolderItem.java b/src/com/owncloud/android/datamodel/SyncedFolderItem.java index 45896c30e202..f2a8d183f9f8 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderItem.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderItem.java @@ -27,6 +27,7 @@ * TODO javadoc */ public class SyncedFolderItem extends SyncedFolder { + public static final long UNPERSISTED_ID = Long.MIN_VALUE; private List filePaths; private String folderName; private long numberOfFiles; diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index dac2526f7d22..3c6c80354647 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -197,7 +197,7 @@ private int updateSyncFolder(SyncedFolder syncedFolder) { ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); int result = mContentResolver.update( - ProviderMeta.ProviderTableMeta.CONTENT_URI_UPLOADS, + ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv, ProviderMeta.ProviderTableMeta._ID + "=?", new String[]{String.valueOf(syncedFolder.getId())} diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 9ff8b898be2e..ff42399ec3b0 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -180,6 +180,9 @@ private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) case UPLOADS: count = db.delete(ProviderTableMeta.UPLOADS_TABLE_NAME, where, whereArgs); break; + case SYNCED_FOLDERS: + count = db.delete(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, where, whereArgs); + break; default: //Log_OC.e(TAG, "Unknown uri " + uri); throw new IllegalArgumentException("Unknown uri: " + uri.toString()); @@ -289,6 +292,19 @@ private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) { } return insertedUploadUri; + + case SYNCED_FOLDERS: + Uri insertedSyncedFolderUri = null; + long syncedFolderId = db.insert(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, null, values); + if (syncedFolderId > 0) { + insertedSyncedFolderUri = + ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, syncedFolderId); + } else { + throw new SQLException("ERROR " + uri); + + } + return insertedSyncedFolderUri; + default: throw new IllegalArgumentException("Unknown uri id: " + uri); } @@ -494,6 +510,8 @@ private int update( ); trimSuccessfulUploads(db); return ret; + case SYNCED_FOLDERS: + return db.update(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, values, selection, selectionArgs); default: return db.update( ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 40b4d92cdf23..e0b5aeba8df3 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -24,11 +24,11 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; @@ -83,13 +83,13 @@ protected void onCreate(Bundle savedInstanceState) { setupContent(); } - private void setupContent() { mRecyclerView = (RecyclerView) findViewById(android.R.id.list); mProgress = (LinearLayout) findViewById(android.R.id.progress); mEmpty = (TextView) findViewById(android.R.id.empty); + // TODO implement "dynamic" grid count via xml-value for tablet vs. phone final int gridWidth = 4; mAdapter = new FolderSyncAdapter(this, gridWidth, this, mRecyclerView); mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver()); @@ -113,9 +113,12 @@ public void run() { syncFolderItems = mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), mediaFolders); + // TODO remove before mergeing to master, keeping it for debugging atm + /** for (MediaFolder mediaFolder : mediaFolders) { Log.d(TAG, mediaFolder.absolutePath); } + */ mHandler.post(new TimerTask() { @Override @@ -128,7 +131,9 @@ public void run() { }).start(); } - private List mergeFolderData(List syncedFolders, List mediaFolders) { + @NonNull + private List mergeFolderData(List syncedFolders, + @NonNull List mediaFolders) { Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); List result = new ArrayList<>(); @@ -144,7 +149,8 @@ private List mergeFolderData(List syncedFolders, return result; } - private SyncedFolderItem createSyncedFolder(SyncedFolder syncedFolder, MediaFolder mediaFolder) { + @NonNull + private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) { return new SyncedFolderItem( syncedFolder.getId(), syncedFolder.getLocalPath(), @@ -160,9 +166,10 @@ private SyncedFolderItem createSyncedFolder(SyncedFolder syncedFolder, MediaFold mediaFolder.numberOfFiles); } - private SyncedFolderItem createSyncedFolderFromMediaFolder(MediaFolder mediaFolder) { + @NonNull + private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { return new SyncedFolderItem( - 0, + SyncedFolderItem.UNPERSISTED_ID, mediaFolder.absolutePath, getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName, true, @@ -176,6 +183,7 @@ private SyncedFolderItem createSyncedFolderFromMediaFolder(MediaFolder mediaFold mediaFolder.numberOfFiles); } + @NonNull private Map createSyncedFoldersMap(List syncFolders) { Map result = new HashMap<>(); if (syncFolders != null) { @@ -229,7 +237,11 @@ public void showFiles(boolean onDeviceOnly) { @Override public void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem) { - Toast.makeText(this, "Sync Status Clicked for " + syncedFolderItem.getLocalPath(), Toast.LENGTH_SHORT).show(); + if(syncedFolderItem.getId() > SyncedFolderItem.UNPERSISTED_ID) { + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(),!syncedFolderItem.isEnabled()); + } else { + mSyncedFolderProvider.storeFolderSync(syncedFolderItem); + } } @Override diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 69a68c928b42..7108db217b4c 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -82,7 +82,7 @@ public int getItemCount(int section) { } @Override - public void onBindHeaderViewHolder(MainViewHolder holder, final int section) { + public void onBindHeaderViewHolder(final MainViewHolder holder, final int section) { holder.title.setText(mSyncFolderItems.get(section).getFolderName()); holder.syncStatusButton.setVisibility(View.VISIBLE); holder.syncStatusButton.setTag(section); @@ -90,8 +90,12 @@ public void onBindHeaderViewHolder(MainViewHolder holder, final int section) { @Override public void onClick(View v) { mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); + mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); } }); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + holder.menuButton.setVisibility(View.VISIBLE); holder.menuButton.setTag(section); holder.menuButton.setOnClickListener(new View.OnClickListener() { @@ -204,4 +208,12 @@ public MainViewHolder(View itemView) { thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); } } + + private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) { + if(enabled) { + syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_on); + } else { + syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off); + } + } } From b607a770f2d4770b348b18a107d69a3b1090e9bf Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Thu, 6 Oct 2016 18:25:23 +0200 Subject: [PATCH 41/99] minor layout optimization --- res/layout/folder_sync_settings_layout.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index b49c0271e369..29d4404ade3d 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -31,7 +31,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/folder_sync_preferences" - android:textAppearance="@style/TextAppearance.AppCompat.Title"/> + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:visibility="gone"/> + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:baselineAligned="false"> Date: Thu, 6 Oct 2016 23:22:56 +0200 Subject: [PATCH 42/99] initial wip implementation of config element click listeners --- res/layout/folder_sync_settings_layout.xml | 13 ++++--- ...SyncedFolderPreferencesDialogFragment.java | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index 29d4404ade3d..c7592317d7e0 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -161,6 +161,7 @@ android:textColor="@color/color_accent"/> Date: Sun, 9 Oct 2016 23:27:30 +0200 Subject: [PATCH 43/99] remote folder can now be changed, all config changes for a synced folder are now saved --- res/layout/folder_sync_settings_layout.xml | 31 ++-- res/values/strings.xml | 3 + .../android/datamodel/SyncedFolder.java | 47 ++++- .../android/datamodel/SyncedFolderItem.java | 1 - .../datamodel/SyncedFolderProvider.java | 4 +- .../ui/activity/FolderSyncActivity.java | 88 +++++++-- ...SyncedFolderPreferencesDialogFragment.java | 170 +++++++++++++----- .../dialog/parcel/SyncedFolderParcelable.java | 24 ++- 8 files changed, 291 insertions(+), 77 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index c7592317d7e0..912d179fb541 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -31,8 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/folder_sync_preferences" - android:textAppearance="@style/TextAppearance.AppCompat.Title" - android:visibility="gone"/> + android:textAppearance="@style/TextAppearance.AppCompat.Title"/> @@ -97,7 +96,18 @@ + + @@ -150,16 +160,6 @@ - - %1$s could not be copied to %2$s local folder Instant upload folder + Local folder + Remote folder Use subfolders Store in subfolders based on year and month @@ -495,6 +497,7 @@ Contribute as a developer, see <a href="https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> for details Move to… Copy to… + Choose folder… Loading folders… No results Folder Sync Preferences diff --git a/src/com/owncloud/android/datamodel/SyncedFolder.java b/src/com/owncloud/android/datamodel/SyncedFolder.java index a0e5d648ef4b..3553a5ee0282 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolder.java +++ b/src/com/owncloud/android/datamodel/SyncedFolder.java @@ -22,7 +22,8 @@ package com.owncloud.android.datamodel; public class SyncedFolder { - private long id; + public static final long UNPERSISTED_ID = Long.MIN_VALUE; + private long id = UNPERSISTED_ID; private String localPath; private String remotePath; private Boolean wifiOnly; @@ -45,38 +46,82 @@ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOn this.enabled = enabled; } + public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { + this.localPath = localPath; + this.remotePath = remotePath; + this.wifiOnly = wifiOnly; + this.chargingOnly = chargingOnly; + this.subfolderByDate = subfolderByDate; + this.account = account; + this.uploadAction = uploadAction; + this.enabled = enabled; + } + public long getId() { return id; } + public void setId(long id) { + this.id = id; + } + public String getLocalPath() { return localPath; } + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + public String getRemotePath() { return remotePath; } + public void setRemotePath(String remotePath) { + this.remotePath = remotePath; + } + public Boolean getWifiOnly() { return wifiOnly; } + public void setWifiOnly(Boolean wifiOnly) { + this.wifiOnly = wifiOnly; + } + public Boolean getChargingOnly() { return chargingOnly; } + public void setChargingOnly(Boolean chargingOnly) { + this.chargingOnly = chargingOnly; + } + public Boolean getSubfolderByDate() { return subfolderByDate; } + public void setSubfolderByDate(Boolean subfolderByDate) { + this.subfolderByDate = subfolderByDate; + } + public String getAccount() { return account; } + public void setAccount(String account) { + this.account = account; + } + public Integer getUploadAction() { return uploadAction; } + public void setUploadAction(Integer uploadAction) { + this.uploadAction = uploadAction; + } + public boolean isEnabled() { return enabled; } diff --git a/src/com/owncloud/android/datamodel/SyncedFolderItem.java b/src/com/owncloud/android/datamodel/SyncedFolderItem.java index f2a8d183f9f8..45896c30e202 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderItem.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderItem.java @@ -27,7 +27,6 @@ * TODO javadoc */ public class SyncedFolderItem extends SyncedFolder { - public static final long UNPERSISTED_ID = Long.MIN_VALUE; private List filePaths; private String folderName; private long numberOfFiles; diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 3c6c80354647..9540c5b330b3 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -56,7 +56,7 @@ public SyncedFolderProvider(ContentResolver contentResolver) { * Stores an media folder sync object in database. * * @param syncedFolder synced folder to store - * @return upload id, -1 if the insert process fails. + * @return synced folder id, -1 if the insert process fails. */ public long storeFolderSync(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); @@ -191,7 +191,7 @@ public SyncedFolder findByLocalPath(String localPath) { * @param syncedFolder the synced folder to be updated. * @return the number of rows updated. */ - private int updateSyncFolder(SyncedFolder syncedFolder) { + public int updateSyncFolder(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index e0b5aeba8df3..95fe762fc4a2 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -40,11 +40,13 @@ import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.MediaFolder; import com.owncloud.android.datamodel.MediaProvider; +import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.datamodel.SyncedFolderItem; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; +import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; import java.util.ArrayList; import java.util.HashMap; @@ -52,6 +54,8 @@ import java.util.Map; import java.util.TimerTask; +import static com.owncloud.android.datamodel.SyncedFolderItem.UNPERSISTED_ID; + /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ @@ -66,6 +70,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte private TextView mEmpty; private SyncedFolderProvider mSyncedFolderProvider; private List syncFolderItems; + private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment; @Override protected void onCreate(Bundle savedInstanceState) { @@ -113,11 +118,11 @@ public void run() { syncFolderItems = mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), mediaFolders); - // TODO remove before mergeing to master, keeping it for debugging atm + // TODO remove before merging to master, keeping it for debugging atm /** - for (MediaFolder mediaFolder : mediaFolders) { - Log.d(TAG, mediaFolder.absolutePath); - } + for (MediaFolder mediaFolder : mediaFolders) { + Log.d(TAG, mediaFolder.absolutePath); + } */ mHandler.post(new TimerTask() { @@ -140,7 +145,7 @@ private List mergeFolderData(List syncedFolders, for (MediaFolder mediaFolder : mediaFolders) { if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath); - result.add(createSyncedFolder(syncedFolder,mediaFolder)); + result.add(createSyncedFolder(syncedFolder, mediaFolder)); } else { result.add(createSyncedFolderFromMediaFolder(mediaFolder)); } @@ -169,7 +174,7 @@ private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { return new SyncedFolderItem( - SyncedFolderItem.UNPERSISTED_ID, + UNPERSISTED_ID, mediaFolder.absolutePath, getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName, true, @@ -184,7 +189,7 @@ private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder } @NonNull - private Map createSyncedFoldersMap(List syncFolders) { + private Map createSyncedFoldersMap(List syncFolders) { Map result = new HashMap<>(); if (syncFolders != null) { for (SyncedFolder syncFolder : syncFolders) { @@ -237,8 +242,8 @@ public void showFiles(boolean onDeviceOnly) { @Override public void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem) { - if(syncedFolderItem.getId() > SyncedFolderItem.UNPERSISTED_ID) { - mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(),!syncedFolderItem.isEnabled()); + if (syncedFolderItem.getId() > UNPERSISTED_ID) { + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(), !syncedFolderItem.isEnabled()); } else { mSyncedFolderProvider.storeFolderSync(syncedFolderItem); } @@ -250,12 +255,71 @@ public void onSyncFolderSettingsClick(int section, SyncedFolderItem syncedFolder FragmentTransaction ft = fm.beginTransaction(); ft.addToBackStack(null); - SyncedFolderPreferencesDialogFragment.newInstance(syncedFolderItem) - .show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG); + mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(syncedFolderItem, + section); + mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER + && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) { + OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); + mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath()); + + } else { + super.onActivityResult(requestCode, resultCode, data); + } } @Override - public void onSaveSyncedFolderPreference() { + public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { Toast.makeText(this, "onSaveSyncedFolderPreference clicked", Toast.LENGTH_SHORT).show(); + SyncedFolderItem item = syncFolderItems.get(syncedFolder.getSection()); + item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder + .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder + .getUploadAction()); + + if (syncedFolder.getId() == UNPERSISTED_ID) { + // newly set up folder sync config + mSyncedFolderProvider.storeFolderSync(item); + } else { + // existing synced folder setup to be updated + mSyncedFolderProvider.updateSyncFolder(item); + } + mSyncedFolderPreferencesDialogFragment = null; + } + + @Override + public void onCancelSyncedFolderPreference() { + mSyncedFolderPreferencesDialogFragment = null; + } + + /** + * update given synced folder with the given values. + * + * @param item the synced folder to be updated + * @param localPath the local path + * @param remotePath the remote path + * @param wifiOnly upload on wifi only + * @param chargingOnly upload on charging only + * @param subfolderByDate created sub folders + * @param uploadAction upload action + * @return the updated item + */ + private SyncedFolderItem updateSyncedFolderItem(SyncedFolderItem item, + String localPath, + String remotePath, + Boolean wifiOnly, + Boolean chargingOnly, + Boolean subfolderByDate, + Integer uploadAction) { + item.setLocalPath(localPath); + item.setRemotePath(remotePath); + item.setWifiOnly(wifiOnly); + item.setChargingOnly(chargingOnly); + item.setSubfolderByDate(subfolderByDate); + item.setUploadAction(uploadAction); + return item; } } diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index 7b98de6c4966..cb7e07ad572e 100644 --- a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -22,20 +22,23 @@ import android.app.Activity; import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; import com.owncloud.android.R; import com.owncloud.android.datamodel.SyncedFolderItem; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.FolderPickerActivity; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; /** @@ -46,15 +49,23 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private final static String TAG = SyncedFolderPreferencesDialogFragment.class.getSimpleName(); public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable"; + public static final int REQUEST_CODE__SELECT_REMOTE_FOLDER = 0; + + private CharSequence[] mUploadBehaviorItemStrings; + private CharSequence[] mUploadBehaviorItemValues; protected View mView = null; private CheckBox mUploadOnWifiCheckbox; private CheckBox mUploadOnChargingCheckbox; private CheckBox mUploadUseSubfoldersCheckbox; + private TextView mUploadBehaviorSummary; + private TextView mLocalFolderSummary; + private TextView mRemoteFolderSummary; private SyncedFolderParcelable mSyncedFolder; + private int mSection; - public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem syncedFolder) { + public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem syncedFolder, int section) { SyncedFolderPreferencesDialogFragment dialogFragment = new SyncedFolderPreferencesDialogFragment(); if (syncedFolder == null) { @@ -62,7 +73,7 @@ public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem } Bundle args = new Bundle(); - args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder)); + args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder, section)); dialogFragment.setArguments(args); return dialogFragment; @@ -72,7 +83,8 @@ public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof OnSyncedFolderPreferenceListener)) { - throw new IllegalArgumentException("The host activity must implement " + OnSyncedFolderPreferenceListener.class.getCanonicalName()); + throw new IllegalArgumentException("The host activity must implement " + + OnSyncedFolderPreferenceListener.class.getCanonicalName()); } } @@ -82,11 +94,12 @@ public void onCreate(Bundle savedInstanceState) { // keep the state of the fragment on configuration changes setRetainInstance(true); - // TODO check UX if it shouldn't be cancelable - //setCancelable(false); + setCancelable(false); mView = null; mSyncedFolder = getArguments().getParcelable(SYNCED_FOLDER_PARCELABLE); + mUploadBehaviorItemStrings = getResources().getTextArray(R.array.pref_behaviour_entries); + mUploadBehaviorItemValues = getResources().getTextArray(R.array.pref_behaviour_entryValues); } @Override @@ -95,61 +108,128 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false); - Button save = (Button) mView.findViewById(R.id.save); - save.setOnClickListener(new OnSyncedFolderSaveClickListener()); - - Button cancel = (Button) mView.findViewById(R.id.cancel); - cancel.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - ((TextView) mView.findViewById(R.id.local_folder_summary)).setText(mSyncedFolder.getLocalPath()); ((TextView) mView.findViewById(R.id.remote_folder_summary)).setText(mSyncedFolder.getRemotePath()); - // TODO add all necessary listeners and fields + setupDialogElements(mView); + setupListeners(mView); + + return mView; + } + + /** + * find all relevant UI elements and set their values. + * + * @param view the parent view + */ + private void setupDialogElements(View view) { + // find/saves UI elements + mLocalFolderSummary = (TextView) mView.findViewById(R.id.local_folder_summary); + mRemoteFolderSummary = (TextView) mView.findViewById(R.id.remote_folder_summary); mUploadOnWifiCheckbox = (CheckBox) mView.findViewById(R.id.setting_instant_upload_on_wifi_checkbox); mUploadOnChargingCheckbox = (CheckBox) mView.findViewById(R.id.setting_instant_upload_on_charging_checkbox); mUploadUseSubfoldersCheckbox = (CheckBox) mView.findViewById(R.id .setting_instant_upload_path_use_subfolders_checkbox); - // TODO create separate setup methods to keep code easy to read + mUploadBehaviorSummary = (TextView) mView.findViewById(R.id.setting_instant_behaviour_summary); - mView.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // TODO save checkbox state to boolean - mUploadOnWifiCheckbox.toggle(); - } - }); + // Set values + mLocalFolderSummary.setText(mSyncedFolder.getLocalPath()); + mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); - mView.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // TODO save checkbox state to boolean - mUploadOnChargingCheckbox.toggle(); - } - }); + mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly()); + mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly()); + mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate()); + + mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadAction()]); + } + + /** + * set (new) remote path on activity result of the folder picker activity. The result gets originally propagated + * to the underlying activity since the picker is an activity and the result can't get passed to the dialog + * fragment directly. + * + * @param path the remote path to be set + */ + public void setRemoteFolderSummary(String path) { + mSyncedFolder.setRemotePath(path); + mRemoteFolderSummary.setText(path); + } + + /** + * setup all listeners. + * + * @param view the parent view + */ + private void setupListeners(View view) { + view.findViewById(R.id.save).setOnClickListener(new OnSyncedFolderSaveClickListener()); + view.findViewById(R.id.cancel).setOnClickListener(new OnSyncedFolderCancelClickListener()); + + view.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setWifiOnly(!mSyncedFolder.getWifiOnly()); + mUploadOnWifiCheckbox.toggle(); + } + }); + + view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly()); + mUploadOnChargingCheckbox.toggle(); + } + }); + + view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setSubfolderByDate(!mSyncedFolder.getSubfolderByDate()); + mUploadUseSubfoldersCheckbox.toggle(); + } + }); - mView.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener(new OnClickListener() { + view.findViewById(R.id.remote_folder_container).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - // TODO save checkbox state to boolean - mUploadUseSubfoldersCheckbox.toggle(); + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + action.putExtra( + FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.choose_remote_folder)); + getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER); } }); - return mView; + view.findViewById(R.id.setting_instant_behaviour_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.prefs_instant_behaviour_dialogTitle) + .setSingleChoiceItems(getResources().getTextArray(R.array.pref_behaviour_entries), + mSyncedFolder.getUploadAction(), + new + DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSyncedFolder.setUploadAction(which); + mUploadBehaviorSummary.setText(SyncedFolderPreferencesDialogFragment + .this.mUploadBehaviorItemStrings[which]); + dialog.dismiss(); + } + }); + builder.create().show(); + } + }); } @Override @NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { final Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.setTitle(R.string.folder_sync_preferences); + dialog.setTitle(null); return dialog; } @@ -165,11 +245,21 @@ private class OnSyncedFolderSaveClickListener implements OnClickListener { @Override public void onClick(View v) { dismiss(); - ((OnSyncedFolderPreferenceListener) getActivity()).onSaveSyncedFolderPreference(); + ((OnSyncedFolderPreferenceListener) getActivity()).onSaveSyncedFolderPreference(mSyncedFolder); + } + } + + private class OnSyncedFolderCancelClickListener implements OnClickListener { + @Override + public void onClick(View v) { + dismiss(); + ((OnSyncedFolderPreferenceListener) getActivity()).onCancelSyncedFolderPreference(); } } public interface OnSyncedFolderPreferenceListener { - public void onSaveSyncedFolderPreference(); + public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder); + + public void onCancelSyncedFolderPreference(); } } diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index 89fe8a2f1a3e..116940a8dce9 100644 --- a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -31,18 +31,19 @@ public class SyncedFolderParcelable implements Parcelable { private String mLocalPath; private String mRemotePath; - private Boolean mWifiOnly; - private Boolean mChargingOnly; - private boolean mEnabled; - private Boolean mSubfolderByDate; + private Boolean mWifiOnly = false; + private Boolean mChargingOnly = false; + private Boolean mEnabled = false; + private Boolean mSubfolderByDate = false; private Integer mUploadAction; private long mId; private String mAccount; + private int mSection; public SyncedFolderParcelable() { } - public SyncedFolderParcelable(SyncedFolderItem syncedFolderItem) { + public SyncedFolderParcelable(SyncedFolderItem syncedFolderItem, int section) { mId = syncedFolderItem.getId(); mLocalPath = syncedFolderItem.getLocalPath(); mRemotePath = syncedFolderItem.getRemotePath(); @@ -52,6 +53,7 @@ public SyncedFolderParcelable(SyncedFolderItem syncedFolderItem) { mSubfolderByDate = syncedFolderItem.getSubfolderByDate(); mAccount = syncedFolderItem.getAccount(); mUploadAction = syncedFolderItem.getUploadAction(); + mSection = section; } public SyncedFolderParcelable(Parcel read) { @@ -64,6 +66,7 @@ public SyncedFolderParcelable(Parcel read) { mSubfolderByDate = read.readInt() != 0; mAccount = read.readString(); mUploadAction = read.readInt(); + mSection = read.readInt(); } @Override @@ -77,6 +80,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mSubfolderByDate ? 1 : 0); dest.writeString(mAccount); dest.writeInt(mUploadAction); + dest.writeInt(mSection); } public static final Creator CREATOR = @@ -130,7 +134,7 @@ public void setChargingOnly(Boolean mChargingOnly) { this.mChargingOnly = mChargingOnly; } - public boolean isEnabled() { + public boolean getEnabled() { return mEnabled; } @@ -169,4 +173,12 @@ public String getAccount() { public void setAccount(String mAccount) { this.mAccount = mAccount; } + + public int getSection() { + return mSection; + } + + public void setSection(int mSection) { + this.mSection = mSection; + } } From e1273ee86d8ff79cc848e7fc14ea87e911809e49 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Sun, 9 Oct 2016 23:30:23 +0200 Subject: [PATCH 44/99] ellipsizing --- res/layout/folder_sync_settings_layout.xml | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index 912d179fb541..f0f07c98e26c 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -77,10 +77,10 @@ android:id="@+id/local_folder_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/prefs_folder_sync_local_path_title" android:maxLines="1" - android:textColor="?android:attr/textColorSecondary" - android:textAppearance="?android:attr/textAppearanceListItem"/> + android:text="@string/prefs_folder_sync_local_path_title" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textColor="?android:attr/textColorSecondary"/> @@ -110,9 +111,9 @@ android:id="@+id/remote_folder_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:baselineAligned="false" android:gravity="center_vertical" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:baselineAligned="false"> + android:minHeight="?android:attr/listPreferredItemHeightSmall"> + android:text="@string/prefs_folder_sync_remote_path_title" + android:textAppearance="?attr/textAppearanceListItem"/> @@ -356,21 +357,21 @@ + android:gravity="right"> + android:text="@string/common_cancel"/> + android:text="@string/common_save"/> From 48d3ef6a246ac841f2191692393f217858700605 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Sun, 9 Oct 2016 23:43:46 +0200 Subject: [PATCH 45/99] removed toast --- .../owncloud/android/ui/activity/FolderSyncActivity.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 95fe762fc4a2..75323af731ae 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -33,7 +33,6 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -268,13 +267,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath()); } else { - super.onActivityResult(requestCode, resultCode, data); - } + super.onActivityResult(requestCode, resultCode, data); + } } @Override public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { - Toast.makeText(this, "onSaveSyncedFolderPreference clicked", Toast.LENGTH_SHORT).show(); SyncedFolderItem item = syncFolderItems.get(syncedFolder.getSection()); item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder From 0454c2498b4a37cce3135a6a11ddf1b577490510 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 10 Oct 2016 21:44:32 +0200 Subject: [PATCH 46/99] bump gradle plugin to 2.2.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1e88c91662bb..88fc591b7460 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.1' } } From 52fdc316d6dc88f3d3604b191e848b62c6a36796 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 10 Oct 2016 22:02:54 +0200 Subject: [PATCH 47/99] better empty message --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c58ad0cd733b..b89d90bb56cd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -499,7 +499,7 @@ Copy to… Choose folder… Loading folders… - No results + No media folders found. Folder Sync Preferences Settings From db96fe03992d6068e6d96e40e4831b1af60eb94a Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 10 Oct 2016 22:58:25 +0200 Subject: [PATCH 48/99] media folder sorting (enabled, alphanumerical) + javadoc --- .../android/datamodel/MediaProvider.java | 4 +- .../ui/activity/FolderSyncActivity.java | 78 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index ca64d9b4ba43..82fa2ee582de 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -38,8 +38,8 @@ public class MediaProvider { private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; /** - * TODO rewrite - * Getting All Images Path + * TODO rewrite/beautify + * Getting All Images Paths. * * @param contentResolver the content resolver * @return List with images folders diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 75323af731ae..8fdb6d9ead17 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -48,6 +48,8 @@ import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -87,6 +89,9 @@ protected void onCreate(Bundle savedInstanceState) { setupContent(); } + /** + * sets up the UI elements and loads all media/synced folders. + */ private void setupContent() { mRecyclerView = (RecyclerView) findViewById(android.R.id.list); @@ -106,6 +111,9 @@ private void setupContent() { load(); } + /** + * loads all media/synced folders, adds them to the recycler view adapter and shows the list. + */ private void load() { if (mAdapter.getItemCount() > 0) return; setListShown(false); @@ -114,8 +122,8 @@ private void load() { @Override public void run() { final List mediaFolders = MediaProvider.getAllShownImagesPath(getContentResolver()); - syncFolderItems = mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), - mediaFolders); + syncFolderItems = sortSyncedFolderItem(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), + mediaFolders)); // TODO remove before merging to master, keeping it for debugging atm /** @@ -135,6 +143,13 @@ public void run() { }).start(); } + /** + * merges two lists of SyncedFolder and MediaFolder items into one of SyncedFolderItems. + * + * @param syncedFolders the synced folders + * @param mediaFolders the media folders + * @return the merged list of SyncedFolderItems + */ @NonNull private List mergeFolderData(List syncedFolders, @NonNull List mediaFolders) { @@ -153,6 +168,48 @@ private List mergeFolderData(List syncedFolders, return result; } + /** + * Sorts list by SyncedFolderItems. + * + * @param syncFolderItemList SyncedFolderItems to sort + */ + public static List sortSyncedFolderItem(List syncFolderItemList) { + Collections.sort(syncFolderItemList, new Comparator() { + public int compare(SyncedFolderItem f1, SyncedFolderItem f2) { + if (f1 == null && f2 == null) { + return 0; + } else if (f1 == null) { + return -1; + } else if (f2 == null) { + return 1; + } else if (f1.isEnabled() && f2.isEnabled()) { + return f1.getFolderName().compareTo(f2.getFolderName()); + } else if (f1.isEnabled()) { + return -1; + } else if (f2.isEnabled()) { + return 1; + } else if (f1.getFolderName() == null && f2.getFolderName() == null) { + return 0; + } else if (f1.getFolderName() == null) { + return -1; + } else if (f2.getFolderName() == null) { + return 1; + } else { + return f1.getFolderName().compareTo(f2.getFolderName()); + } + } + }); + + return syncFolderItemList; + } + + /** + * creates a SyncedFolderItem merging a SyncedFolder and MediaFolder object instance. + * + * @param syncedFolder the synced folder object + * @param mediaFolder the media folder object + * @return the created SyncedFolderItem + */ @NonNull private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) { return new SyncedFolderItem( @@ -170,6 +227,12 @@ private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, mediaFolder.numberOfFiles); } + /** + * creates a SyncedFolderItem based on a MediaFolder object instance. + * + * @param mediaFolder the media folder object + * @return the created SyncedFolderItem + */ @NonNull private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { return new SyncedFolderItem( @@ -187,6 +250,12 @@ private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder.numberOfFiles); } + /** + * creates a lookup map for a list of given synced folders with their local path as the key. + * + * @param syncFolders list of synced folders + * @return the lookup map for synced folders + */ @NonNull private Map createSyncedFoldersMap(List syncFolders) { Map result = new HashMap<>(); @@ -198,6 +267,11 @@ private Map createSyncedFoldersMap(List sync return result; } + /** + * show/hide recycler view list or the empty message / progress info. + * + * @param shown flag if list should be shown + */ private void setListShown(boolean shown) { if (mRecyclerView != null) { mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE); From 179c69512820a8c3fe0e50d7677365e6ea35f62f Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 12 Oct 2016 20:44:54 +0200 Subject: [PATCH 49/99] removed dummy synced folder entry --- .../android/providers/FileContentProvider.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index 8d5b16f73533..45f3a8616924 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -956,18 +956,6 @@ private void createSyncedFoldersTable(SQLiteDatabase db){ + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " TEXT, " // account + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER );" // upload action ); - - // TODO Tobi remove after testing - db.execSQL("INSERT INTO " + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME + "(" - + ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + ", " // local path - + ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + ", " // remote path - + ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY + ", " // wifi_only - + ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY + ", " // charging only - + ProviderTableMeta.SYNCED_FOLDER_ENABLED + ", " // enabled - + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + ", " // subfolder by date - + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + ", " // account - + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + ") " // upload action - + "VALUES ('/sdcard/DCIM/', '/syncTest', 0, 0, 1, 1, 'tobi', 1)"); } /** From c3a8b0c4506d3356573b251d7d8ead347d12a827 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 12 Oct 2016 21:41:44 +0200 Subject: [PATCH 50/99] update job to obey syncedfolder settings --- .../services/SyncedFolderJobService.java | 16 +++++++-------- .../observer/SyncedFolderObserver.java | 20 ++++++++++++------- .../observer/SyncedFolderObserverService.java | 3 +-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java index f571cc566a61..10d7f046e66b 100644 --- a/src/com/owncloud/android/services/SyncedFolderJobService.java +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -40,6 +40,7 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.MimetypeIconUtil; import java.io.File; import java.util.Date; @@ -51,12 +52,9 @@ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class SyncedFolderJobService extends JobService { private static final String TAG = "SyncedFolderJobService"; - private Context mContext; @Override public int onStartCommand(Intent intent, int flags, int startId) { - // TODO Tobi why is this null? - mContext = MainApp.getAppContext(); return START_NOT_STICKY; } @@ -64,17 +62,17 @@ public int onStartCommand(Intent intent, int flags, int startId) { public boolean onStartJob(JobParameters params) { Log_OC.d(TAG, "startJob: " + params.getJobId()); - // TODO Tobi just for testing! Context context = MainApp.getAppContext(); - Account account = AccountUtils.getCurrentOwnCloudAccount(context); - PersistableBundle bundle = params.getExtras(); String filePath = bundle.getString("filePath"); - String remoteFolder = bundle.getString("remoteFolder"); + String remoteFolder = bundle.getString("remotePath"); Long dateTaken = bundle.getLong("dateTaken"); Boolean subfolderByDate = bundle.getInt("subfolderByDate") == 1; + Account account = AccountUtils.getOwnCloudAccountByName(context, bundle.getString("account")); + Integer uploadBehaviour = bundle.getInt("uploadBehaviour"); File file = new File(filePath); + String mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(file.getAbsolutePath()); FileUploader.UploadRequester requester = new FileUploader.UploadRequester(); requester.uploadNewFile( @@ -82,8 +80,8 @@ public boolean onStartJob(JobParameters params) { account, filePath, FileStorageUtils.getInstantUploadFilePath(remoteFolder, file.getName(), dateTaken, subfolderByDate), - FileUploader.LOCAL_BEHAVIOUR_FORGET, - "image/jpg", + uploadBehaviour, + mimeType, true, // create parent folder if not existent UploadFileOperation.CREATED_AS_INSTANT_PICTURE ); diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index cd7911a077ab..0bd5912ed167 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -11,6 +11,7 @@ import android.util.Log; import com.owncloud.android.MainApp; +import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.services.SyncedFolderJobService; import com.owncloud.android.utils.RecursiveFileObserver; @@ -24,15 +25,15 @@ class SyncedFolderObserver extends RecursiveFileObserver { private static final int MY_BACKGROUND_JOB = 0; public static final String TAG = "SyncedFolderObserver"; - private String remoteFolder; + private SyncedFolder syncedFolder; - public SyncedFolderObserver(String path, String remoteFolder) { - super(path, FileObserver.CREATE + FileObserver.MOVED_TO); + public SyncedFolderObserver(SyncedFolder syncedFolder) { + super(syncedFolder.getLocalPath(), FileObserver.CREATE + FileObserver.MOVED_TO); context = MainApp.getAppContext(); - this.remoteFolder = remoteFolder; - Log_OC.d("SyncedFolderObserver", "Started watching: " + path); + this.syncedFolder = syncedFolder; + Log_OC.d("SyncedFolderObserver", "Started watching: " + syncedFolder.getLocalPath()); } @@ -40,15 +41,20 @@ public SyncedFolderObserver(String path, String remoteFolder) { @Override public void onEvent(int event, String path) { PersistableBundle bundle = new PersistableBundle(); + // TODO extract bundle.putString("filePath", path); - bundle.putString("remoteFolder", remoteFolder); + bundle.putString("remotePath", syncedFolder.getRemotePath()); bundle.putLong("dateTaken", new Date().getTime()); + bundle.putString("account", syncedFolder.getAccount()); + bundle.putInt("uploadBehaviour", syncedFolder.getUploadAction()); + bundle.putInt("subfolderByDate", syncedFolder.getSubfolderByDate() ? 1 : 0); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo job = new JobInfo.Builder( MY_BACKGROUND_JOB, new ComponentName(context, SyncedFolderJobService.class)) - .setRequiresCharging(false) + .setRequiresCharging(syncedFolder.getChargingOnly()) + // TODO change to UNMETERED after developing .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setExtras(bundle) .build(); diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 608325bd1678..617748fe3815 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -24,8 +24,7 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) { - SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder.getLocalPath(), - syncedFolder.getRemotePath()); + SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder); observer.startWatching(); syncedFolderObservers.add(observer); From 0889f8a69ea90f47a3cb696f17d5e1a81bcd8858 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Thu, 13 Oct 2016 02:00:49 +0200 Subject: [PATCH 51/99] grid spacing added + negative/shortened margins for containers for nice image grids --- res/layout/folder_sync_item_header.xml | 4 +- res/layout/folder_sync_layout.xml | 5 ++- res/layout/grid_sync_item.xml | 8 +--- res/values/dims.xml | 1 + .../ui/activity/FolderSyncActivity.java | 3 ++ .../android/ui/adapter/FolderSyncAdapter.java | 2 + .../decoration/MediaGridItemDecoration.java | 44 +++++++++++++++++++ 7 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index be6b16a5cf0e..2c6e3d867e18 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -21,8 +21,8 @@ + android:visibility="visible" + android:layout_marginRight="-3dp" + android:layout_marginLeft="-3dp" + android:layout_marginBottom="-3dp"/> . --> - + android:layout_height="match_parent"> @@ -44,8 +42,6 @@ android:id="@+id/thumbnailDarkener" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingLeft="8dp" - android:paddingRight="8dp" android:scaleType="centerCrop" android:background="#99000000"/> diff --git a/res/values/dims.xml b/res/values/dims.xml index e2f7687a8e7b..b853494b6ff9 100644 --- a/res/values/dims.xml +++ b/res/values/dims.xml @@ -99,4 +99,5 @@ 12dip 35dp 12dp + 2dp diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 8fdb6d9ead17..961e943ddc2b 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -44,6 +44,7 @@ import com.owncloud.android.datamodel.SyncedFolderItem; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import com.owncloud.android.ui.decoration.MediaGridItemDecoration; import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; @@ -105,6 +106,8 @@ private void setupContent() { final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); mAdapter.setLayoutManager(lm); + int spacing = getResources().getDimensionPixelSize(R.dimen.mediaGridSpacing); + mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing)); mRecyclerView.setLayoutManager(lm); mRecyclerView.setAdapter(mAdapter); diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 7108db217b4c..475037cc5313 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -162,6 +162,8 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); } + holder.itemView.setTag(relativePosition % (mGridWidth/2)); + if (mSyncFolderItems.get(section).getNumberOfFiles() > 8 && relativePosition >= 8 - 1) { holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - 8)); holder.counterBar.setVisibility(View.VISIBLE); diff --git a/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java b/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java new file mode 100644 index 000000000000..de8bca3a1c19 --- /dev/null +++ b/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java @@ -0,0 +1,44 @@ +/** + * Nextcloud Android client application + * + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.owncloud.android.ui.decoration; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ItemDecoration; +import android.view.View; + +/** + * Decoration for media grid items. + */ +public class MediaGridItemDecoration extends ItemDecoration { + private int space; + + public MediaGridItemDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.right = space; + outRect.bottom = space; + outRect.left = space; + outRect.top = space; + } +} \ No newline at end of file From 6783a1c62828d888fe11506238815edc364df954 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Thu, 13 Oct 2016 02:07:32 +0200 Subject: [PATCH 52/99] minor persistence bugfix --- src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 475037cc5313..2b68582e862f 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -89,9 +89,9 @@ public void onBindHeaderViewHolder(final MainViewHolder holder, final int sectio holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); } }); setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); From bf39292df644978cae4c29b65f0d3161e4054c82 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 17 Oct 2016 12:13:37 +0200 Subject: [PATCH 53/99] persistence fix --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 961e943ddc2b..8ae2e56abc40 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -319,7 +319,7 @@ public void showFiles(boolean onDeviceOnly) { @Override public void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem) { if (syncedFolderItem.getId() > UNPERSISTED_ID) { - mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(), !syncedFolderItem.isEnabled()); + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(), syncedFolderItem.isEnabled()); } else { mSyncedFolderProvider.storeFolderSync(syncedFolderItem); } From c7147795c7045ade65e0153c6b66fd37eecdb2b9 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 21 Oct 2016 21:52:00 +0200 Subject: [PATCH 54/99] fix wlan upload --- src/com/owncloud/android/services/SyncedFolderJobService.java | 2 +- .../android/services/observer/SyncedFolderObserver.java | 3 +-- .../android/services/observer/SyncedFolderObserverService.java | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java index 10d7f046e66b..afbb7bda89f6 100644 --- a/src/com/owncloud/android/services/SyncedFolderJobService.java +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -55,7 +55,7 @@ public class SyncedFolderJobService extends JobService { @Override public int onStartCommand(Intent intent, int flags, int startId) { - return START_NOT_STICKY; + return START_REDELIVER_INTENT; } @Override diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index 0bd5912ed167..f756a26e75f9 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -54,8 +54,7 @@ public void onEvent(int event, String path) { MY_BACKGROUND_JOB, new ComponentName(context, SyncedFolderJobService.class)) .setRequiresCharging(syncedFolder.getChargingOnly()) - // TODO change to UNMETERED after developing - .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setRequiredNetworkType(syncedFolder.getWifiOnly() ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY) .setExtras(bundle) .build(); diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 617748fe3815..6b44e95a65e8 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -7,6 +7,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.SyncedFolder; import com.owncloud.android.datamodel.SyncedFolderProvider; +import com.owncloud.android.lib.common.utils.Log_OC; import java.util.ArrayList; import java.util.List; @@ -23,6 +24,7 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { + Log_OC.d(TAG, "start"); for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) { SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder); From a4a41a67051c3809d3f0040a810001cca32c339a Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 10:58:23 +0200 Subject: [PATCH 55/99] enforce camera folder top position --- .../owncloud/android/ui/activity/FolderSyncActivity.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 8ae2e56abc40..1f2a297ecd3b 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -61,10 +61,12 @@ /** * Activity displaying all auto-synced folders and/or instant upload media folders. */ -public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener, SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { +public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener, + SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { private static final String TAG = FolderSyncActivity.class.getSimpleName(); private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; + public static final String PRIORITIZED_FOLDER = "Camera"; private RecyclerView mRecyclerView; private FolderSyncAdapter mAdapter; @@ -197,6 +199,10 @@ public int compare(SyncedFolderItem f1, SyncedFolderItem f2) { return -1; } else if (f2.getFolderName() == null) { return 1; + } else if (PRIORITIZED_FOLDER.equals(f1.getFolderName())) { + return -1; + } else if (PRIORITIZED_FOLDER.equals(f2.getFolderName())) { + return 1; } else { return f1.getFolderName().compareTo(f2.getFolderName()); } From 6afeee75351a97662bd11ffd3db15ac7229e93bc Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 11:56:43 +0200 Subject: [PATCH 56/99] hide instant upload icon on Android pre-lollipop --- src/com/owncloud/android/ui/activity/DrawerActivity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/owncloud/android/ui/activity/DrawerActivity.java b/src/com/owncloud/android/ui/activity/DrawerActivity.java index 2f1b1da95118..3a325457c9f4 100644 --- a/src/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/com/owncloud/android/ui/activity/DrawerActivity.java @@ -166,6 +166,11 @@ protected void setupDrawer() { setupDrawerMenu(mNavigationView); setupQuotaElement(); + + // show folder sync menu item only for Android 5+ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + mNavigationView.getMenu().removeItem(R.id.nav_folder_sync); + } } setupDrawerToggle(); From eff5d9e70ac33a49750a94db2df7acdc71c5c852 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 11:57:11 +0200 Subject: [PATCH 57/99] Order images by date (to always show the latest pictures taken) --- src/com/owncloud/android/datamodel/MediaProvider.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 82fa2ee582de..2df2c71d0a73 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -52,16 +52,16 @@ public static List getAllShownImagesPath(ContentResolver contentRes String absolutePathOfImage; String folderName; - String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN}; String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore - .MediaColumns.DATA}; + .MediaColumns.DATA, MediaStore.Images.Media.DATE_TAKEN}; String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; String folderSelection = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " IS NOT NULL) GROUP BY (" + MediaStore .Images.Media .BUCKET_DISPLAY_NAME; String fileSelection = MediaStore.MediaColumns.DATA + " LIKE "; - String folderSortOrder = "MAX(" + MediaStore.Images.Media.DISPLAY_NAME + ") DESC"; - String fileSortOrder = MediaStore.MediaColumns.DATA + " DESC LIMIT 8"; // LIMIT 8 + String folderSortOrder = "MAX(" + MediaStore.MediaColumns.DATA + ") DESC"; + String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT 8"; // LIMIT 8 cursor = contentResolver.query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); From e68c54798f7887250a3c44ca6c52273064dbc63f Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 12:12:45 +0200 Subject: [PATCH 58/99] fix default upload behavior for synced folder --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 1f2a297ecd3b..088ec776181d 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -252,7 +252,7 @@ private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder false, false, AccountUtils.getCurrentOwnCloudAccount(this).name, - 1, + 0, false, mediaFolder.filePaths, mediaFolder.folderName, From 223e29d5d4f5784a1640b67847266dfd8b97c9c8 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 13:21:09 +0200 Subject: [PATCH 59/99] hide legacy instant upload preferences on Android 5+ --- .../android/ui/activity/Preferences.java | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index 32ad63a818b5..aa2610f4b817 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; @@ -327,10 +328,14 @@ public boolean onPreferenceClick(Preference preference) { } } - mPrefInstantUploadPath = findPreference("instant_upload_path"); - if (mPrefInstantUploadPath != null){ + mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); - mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Instant upload via preferences on pre Android Lollipop + mPrefInstantUploadPath = findPreference("instant_upload_path"); + if (mPrefInstantUploadPath != null) { + + mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { if (!mUploadPath.endsWith(OCFile.PATH_SEPARATOR)) { @@ -342,34 +347,31 @@ public boolean onPreferenceClick(Preference preference) { return true; } }); - } - - mPrefInstantUploadCategory = - (PreferenceCategory) findPreference("instant_uploading_category"); - - mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders"); - mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi"); - mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging"); - mPrefInstantUpload = findPreference("instant_uploading"); - - toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked()); - - mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantPictureOptions((Boolean) newValue); - toggleInstantUploadBehaviour( - ((CheckBoxPreference)mPrefInstantVideoUpload).isChecked(), - (Boolean) newValue); - return true; } - }); - - mPrefInstantVideoUploadPath = findPreference("instant_video_upload_path"); - if (mPrefInstantVideoUploadPath != null){ - mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { + mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders"); + mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi"); + mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging"); + mPrefInstantUpload = findPreference("instant_uploading"); + + toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked()); + + mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + toggleInstantPictureOptions((Boolean) newValue); + toggleInstantUploadBehaviour( + ((CheckBoxPreference) mPrefInstantVideoUpload).isChecked(), + (Boolean) newValue); + return true; + } + }); + + mPrefInstantVideoUploadPath = findPreference("instant_video_upload_path"); + if (mPrefInstantVideoUploadPath != null) { + + mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { if (!mUploadVideoPath.endsWith(OCFile.PATH_SEPARATOR)) { @@ -382,30 +384,37 @@ public boolean onPreferenceClick(Preference preference) { return true; } }); - } + } - mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders"); - mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi"); - mPrefInstantVideoUpload = findPreference("instant_video_uploading"); - mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging"); - toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked()); - - mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders"); + mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi"); + mPrefInstantVideoUpload = findPreference("instant_video_uploading"); + mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging"); + toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked()); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantVideoOptions((Boolean) newValue); - toggleInstantUploadBehaviour( - (Boolean) newValue, - ((CheckBoxPreference) mPrefInstantUpload).isChecked()); - return true; - } - }); + mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour"); - toggleInstantUploadBehaviour( - ((CheckBoxPreference)mPrefInstantVideoUpload).isChecked(), - ((CheckBoxPreference)mPrefInstantUpload).isChecked()); + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + toggleInstantVideoOptions((Boolean) newValue); + toggleInstantUploadBehaviour( + (Boolean) newValue, + ((CheckBoxPreference) mPrefInstantUpload).isChecked()); + return true; + } + }); + + mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour"); + toggleInstantUploadBehaviour( + ((CheckBoxPreference) mPrefInstantVideoUpload).isChecked(), + ((CheckBoxPreference) mPrefInstantUpload).isChecked()); + + loadInstantUploadPath(); + loadInstantUploadVideoPath(); + } else { + // Instant upload is handled via synced folders on Android Lollipop and up + getPreferenceScreen().removePreference(mPrefInstantUploadCategory); + } /* About App */ pAboutApp = (Preference) findPreference("about_app"); @@ -414,9 +423,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { getString(R.string.app_name))); pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion)); } - - loadInstantUploadPath(); - loadInstantUploadVideoPath(); } private void launchDavDroidLogin() From c30dcd4a6d6ce001760cd2912e313bc662410fe6 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 14:45:09 +0200 Subject: [PATCH 60/99] minor renamings & javadoc --- .../android/datamodel/MediaProvider.java | 8 +- ...Item.java => SyncedFolderDisplayItem.java} | 27 +++++- .../ui/activity/FolderSyncActivity.java | 93 +++++++++---------- .../android/ui/adapter/FolderSyncAdapter.java | 10 +- ...SyncedFolderPreferencesDialogFragment.java | 4 +- .../dialog/parcel/SyncedFolderParcelable.java | 26 +++--- 6 files changed, 89 insertions(+), 79 deletions(-) rename src/com/owncloud/android/datamodel/{SyncedFolderItem.java => SyncedFolderDisplayItem.java} (55%) diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 2df2c71d0a73..7b9191c85db2 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -31,7 +31,7 @@ import java.util.List; /** - * media queries to gain access to media lists for the device. + * Media queries to gain access to media lists for the device. */ public class MediaProvider { private static final String TAG = MediaProvider.class.getSimpleName(); @@ -44,15 +44,13 @@ public class MediaProvider { * @param contentResolver the content resolver * @return List with images folders */ - public static List getAllShownImagesPath(ContentResolver contentResolver) { + public static List getMediaFolders(ContentResolver contentResolver) { Cursor cursor; List mediaFolders = new ArrayList<>(); int column_index_data, column_index_folder_name, column_index_data_image; - ArrayList listOfAllImages = new ArrayList<>(); String absolutePathOfImage; String folderName; - String[] projectionTest = {MediaStore.MediaColumns.DATA, MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN}; String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore .MediaColumns.DATA, MediaStore.Images.Media.DATE_TAKEN}; String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; @@ -91,7 +89,7 @@ public static List getAllShownImagesPath(ContentResolver contentRes mediaFolder.filePaths = new ArrayList<>(); column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - Log.d(TAG, "Reading images for --> " + mediaFolder.absolutePath); + Log.d(TAG, "Reading images for " + mediaFolder.absolutePath); while (cursorImages.moveToNext()) { mediaFolder.filePaths.add(cursorImages.getString(column_index_data_image)); } diff --git a/src/com/owncloud/android/datamodel/SyncedFolderItem.java b/src/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java similarity index 55% rename from src/com/owncloud/android/datamodel/SyncedFolderItem.java rename to src/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java index 45896c30e202..35aca82a8c97 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderItem.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java @@ -24,16 +24,33 @@ import java.util.List; /** - * TODO javadoc + * Display item specialization for synced folder objects to be displayed in a list/grid view adding further + * information to be displayed in the UI but not part of the persisted underlying {@link SyncedFolder} object. */ -public class SyncedFolderItem extends SyncedFolder { +public class SyncedFolderDisplayItem extends SyncedFolder { private List filePaths; private String folderName; private long numberOfFiles; - public SyncedFolderItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, - Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, - List filePaths, String folderName, long numberOfFiles) { + /** + * constructor for the display item specialization for a synced folder object. + * + * @param id id + * @param localPath local path + * @param remotePath remote path + * @param wifiOnly upload on wifi only flag + * @param chargingOnly upload on charging only + * @param subfolderByDate create sub-folders by date (month) + * @param account the account owning the synced folder + * @param uploadAction the action to be done after the upload + * @param enabled flag if synced folder config is active + * @param filePaths the UI info for the file path + * @param folderName the UI info for the folder's name + * @param numberOfFiles the UI info for number of files within the folder + */ + public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, + Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled, + List filePaths, String folderName, long numberOfFiles) { super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled); this.filePaths = filePaths; this.folderName = folderName; diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 088ec776181d..c830823db523 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -41,7 +41,7 @@ import com.owncloud.android.datamodel.MediaProvider; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.ui.adapter.FolderSyncAdapter; import com.owncloud.android.ui.decoration.MediaGridItemDecoration; @@ -56,7 +56,7 @@ import java.util.Map; import java.util.TimerTask; -import static com.owncloud.android.datamodel.SyncedFolderItem.UNPERSISTED_ID; +import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID; /** * Activity displaying all auto-synced folders and/or instant upload media folders. @@ -73,7 +73,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte private LinearLayout mProgress; private TextView mEmpty; private SyncedFolderProvider mSyncedFolderProvider; - private List syncFolderItems; + private List syncFolderItems; private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment; @Override @@ -126,17 +126,10 @@ private void load() { new Thread(new Runnable() { @Override public void run() { - final List mediaFolders = MediaProvider.getAllShownImagesPath(getContentResolver()); - syncFolderItems = sortSyncedFolderItem(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), + final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver()); + syncFolderItems = sortSyncedFolderItems(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), mediaFolders)); - // TODO remove before merging to master, keeping it for debugging atm - /** - for (MediaFolder mediaFolder : mediaFolders) { - Log.d(TAG, mediaFolder.absolutePath); - } - */ - mHandler.post(new TimerTask() { @Override public void run() { @@ -149,17 +142,17 @@ public void run() { } /** - * merges two lists of SyncedFolder and MediaFolder items into one of SyncedFolderItems. + * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems. * * @param syncedFolders the synced folders * @param mediaFolders the media folders * @return the merged list of SyncedFolderItems */ @NonNull - private List mergeFolderData(List syncedFolders, - @NonNull List mediaFolders) { + private List mergeFolderData(List syncedFolders, + @NonNull List mediaFolders) { Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (MediaFolder mediaFolder : mediaFolders) { if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { @@ -174,13 +167,15 @@ private List mergeFolderData(List syncedFolders, } /** - * Sorts list by SyncedFolderItems. + * Sorts list of {@link SyncedFolderDisplayItem}s. * - * @param syncFolderItemList SyncedFolderItems to sort + * @param syncFolderItemList list of items to be sorted + * @return sorted list of items */ - public static List sortSyncedFolderItem(List syncFolderItemList) { - Collections.sort(syncFolderItemList, new Comparator() { - public int compare(SyncedFolderItem f1, SyncedFolderItem f2) { + public static List sortSyncedFolderItems(List + syncFolderItemList) { + Collections.sort(syncFolderItemList, new Comparator() { + public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { if (f1 == null && f2 == null) { return 0; } else if (f1 == null) { @@ -213,15 +208,15 @@ public int compare(SyncedFolderItem f1, SyncedFolderItem f2) { } /** - * creates a SyncedFolderItem merging a SyncedFolder and MediaFolder object instance. + * creates a SyncedFolderDisplayItem merging a {@link SyncedFolder} and a {@link MediaFolder} object instance. * * @param syncedFolder the synced folder object * @param mediaFolder the media folder object - * @return the created SyncedFolderItem + * @return the created SyncedFolderDisplayItem */ @NonNull - private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) { - return new SyncedFolderItem( + private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) { + return new SyncedFolderDisplayItem( syncedFolder.getId(), syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), @@ -237,14 +232,14 @@ private SyncedFolderItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, } /** - * creates a SyncedFolderItem based on a MediaFolder object instance. + * creates a {@link SyncedFolderDisplayItem} based on a {@link MediaFolder} object instance. * * @param mediaFolder the media folder object - * @return the created SyncedFolderItem + * @return the created SyncedFolderDisplayItem */ @NonNull - private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { - return new SyncedFolderItem( + private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { + return new SyncedFolderDisplayItem( UNPERSISTED_ID, mediaFolder.absolutePath, getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName, @@ -260,10 +255,10 @@ private SyncedFolderItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder } /** - * creates a lookup map for a list of given synced folders with their local path as the key. + * creates a lookup map for a list of given {@link SyncedFolder}s with their local path as the key. * - * @param syncFolders list of synced folders - * @return the lookup map for synced folders + * @param syncFolders list of {@link SyncedFolder}s + * @return the lookup map for {@link SyncedFolder}s */ @NonNull private Map createSyncedFoldersMap(List syncFolders) { @@ -291,7 +286,7 @@ private void setListShown(boolean shown) { @Override public boolean onOptionsItemSelected(MenuItem item) { - boolean retval; + boolean result; switch (item.getItemId()) { case android.R.id.home: { if (isDrawerOpen()) { @@ -302,9 +297,9 @@ public boolean onOptionsItemSelected(MenuItem item) { } default: - retval = super.onOptionsItemSelected(item); + result = super.onOptionsItemSelected(item); } - return retval; + return result; } @Override @@ -323,21 +318,21 @@ public void showFiles(boolean onDeviceOnly) { } @Override - public void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem) { - if (syncedFolderItem.getId() > UNPERSISTED_ID) { - mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderItem.getId(), syncedFolderItem.isEnabled()); + public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { + if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) { + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(), syncedFolderDisplayItem.isEnabled()); } else { - mSyncedFolderProvider.storeFolderSync(syncedFolderItem); + mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem); } } @Override - public void onSyncFolderSettingsClick(int section, SyncedFolderItem syncedFolderItem) { + public void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.addToBackStack(null); - mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(syncedFolderItem, + mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance(syncedFolderDisplayItem, section); mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG); } @@ -356,7 +351,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { - SyncedFolderItem item = syncFolderItems.get(syncedFolder.getSection()); + SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection()); item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder .getUploadAction()); @@ -388,13 +383,13 @@ public void onCancelSyncedFolderPreference() { * @param uploadAction upload action * @return the updated item */ - private SyncedFolderItem updateSyncedFolderItem(SyncedFolderItem item, - String localPath, - String remotePath, - Boolean wifiOnly, - Boolean chargingOnly, - Boolean subfolderByDate, - Integer uploadAction) { + private SyncedFolderDisplayItem updateSyncedFolderItem(SyncedFolderDisplayItem item, + String localPath, + String remotePath, + Boolean wifiOnly, + Boolean chargingOnly, + Boolean subfolderByDate, + Integer uploadAction) { item.setLocalPath(localPath); item.setRemotePath(remotePath); item.setWifiOnly(wifiOnly); diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 2b68582e862f..196ec13d7aa4 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -34,7 +34,7 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; import com.owncloud.android.R; -import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.BitmapUtils; @@ -54,7 +54,7 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mSyncFolderItems; + private final List mSyncFolderItems; private final RecyclerView mRecyclerView; public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { @@ -65,7 +65,7 @@ public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, mRecyclerView = recyclerView; } - public void setSyncFolderItems(List syncFolderItems) { + public void setSyncFolderItems(List syncFolderItems) { mSyncFolderItems.clear(); mSyncFolderItems.addAll(syncFolderItems); notifyDataSetChanged(); @@ -186,8 +186,8 @@ public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } public interface ClickListener { - void onSyncStatusToggleClick(int section, SyncedFolderItem syncedFolderItem); - void onSyncFolderSettingsClick(int section, SyncedFolderItem syncedFolderItem); + void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); + void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); } static class MainViewHolder extends RecyclerView.ViewHolder { diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index cb7e07ad572e..0bbbbff83587 100644 --- a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -36,7 +36,7 @@ import android.widget.TextView; import com.owncloud.android.R; -import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FolderPickerActivity; import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; @@ -65,7 +65,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private SyncedFolderParcelable mSyncedFolder; private int mSection; - public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderItem syncedFolder, int section) { + public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderDisplayItem syncedFolder, int section) { SyncedFolderPreferencesDialogFragment dialogFragment = new SyncedFolderPreferencesDialogFragment(); if (syncedFolder == null) { diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index 116940a8dce9..bb99dab460c5 100644 --- a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -23,10 +23,10 @@ import android.os.Parcel; import android.os.Parcelable; -import com.owncloud.android.datamodel.SyncedFolderItem; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; /** - * Parceble for SyncedFolder objects to transport them from/to dialog fragments. + * Parcelable for {@link SyncedFolderDisplayItem} objects to transport them from/to dialog fragments. */ public class SyncedFolderParcelable implements Parcelable { private String mLocalPath; @@ -43,20 +43,20 @@ public class SyncedFolderParcelable implements Parcelable { public SyncedFolderParcelable() { } - public SyncedFolderParcelable(SyncedFolderItem syncedFolderItem, int section) { - mId = syncedFolderItem.getId(); - mLocalPath = syncedFolderItem.getLocalPath(); - mRemotePath = syncedFolderItem.getRemotePath(); - mWifiOnly = syncedFolderItem.getWifiOnly(); - mChargingOnly = syncedFolderItem.getChargingOnly(); - mEnabled = syncedFolderItem.isEnabled(); - mSubfolderByDate = syncedFolderItem.getSubfolderByDate(); - mAccount = syncedFolderItem.getAccount(); - mUploadAction = syncedFolderItem.getUploadAction(); + public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, int section) { + mId = syncedFolderDisplayItem.getId(); + mLocalPath = syncedFolderDisplayItem.getLocalPath(); + mRemotePath = syncedFolderDisplayItem.getRemotePath(); + mWifiOnly = syncedFolderDisplayItem.getWifiOnly(); + mChargingOnly = syncedFolderDisplayItem.getChargingOnly(); + mEnabled = syncedFolderDisplayItem.isEnabled(); + mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate(); + mAccount = syncedFolderDisplayItem.getAccount(); + mUploadAction = syncedFolderDisplayItem.getUploadAction(); mSection = section; } - public SyncedFolderParcelable(Parcel read) { + private SyncedFolderParcelable(Parcel read) { mId = read.readLong(); mLocalPath = read.readString(); mRemotePath = read.readString(); From faea2f01fb2fecdc4fcb51c82390f766e213252d Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 15:21:00 +0200 Subject: [PATCH 61/99] dynamic media grid width sizing and revampedd instant upload icon --- res/drawable-hdpi/ic_cloud_upload.png | Bin 0 -> 772 bytes res/drawable-mdpi/ic_cloud_upload.png | Bin 0 -> 537 bytes res/drawable-xhdpi/ic_cloud_upload.png | Bin 0 -> 944 bytes res/drawable-xxhdpi/ic_cloud_upload.png | Bin 0 -> 1402 bytes res/drawable-xxxhdpi/ic_cloud_upload.png | Bin 0 -> 1865 bytes res/menu/drawer_menu.xml | 2 +- res/values-sw360dp/dims.xml | 1 + res/values-sw600dp/dims.xml | 26 ++++++++++++++++++ res/values/dims.xml | 3 +- res/values/strings.xml | 2 +- .../android/datamodel/MediaProvider.java | 10 ++++--- .../ui/activity/FolderSyncActivity.java | 16 ++++++----- .../android/ui/adapter/FolderSyncAdapter.java | 17 ++++++------ 13 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 res/drawable-hdpi/ic_cloud_upload.png create mode 100644 res/drawable-mdpi/ic_cloud_upload.png create mode 100644 res/drawable-xhdpi/ic_cloud_upload.png create mode 100644 res/drawable-xxhdpi/ic_cloud_upload.png create mode 100644 res/drawable-xxxhdpi/ic_cloud_upload.png create mode 100644 res/values-sw600dp/dims.xml diff --git a/res/drawable-hdpi/ic_cloud_upload.png b/res/drawable-hdpi/ic_cloud_upload.png new file mode 100644 index 0000000000000000000000000000000000000000..e76fb0ac819bb9aa337a2330f591a63f86a13f52 GIT binary patch literal 772 zcmV+f1N;1mP)lW`4 z3Jy*>+oGV3qGAzCk+ve}(7~bzA}T6^b-KIfP$QusRQ&Ur4H&w`W-T3P2>LuU;u%lY6s{Rm> zYq3~t(skXREf>mUGKW?5wyO3vSoE^04i^fAS1l8=ZMzS61avhhQ{po)Sf&&B~PHIF4JRQ*Tvu(HL{n^SnEN zmTp)8iA3U_h}6BmRFeUio12?Wr_%x)s}RyDA_swyR4O%;OeP=B&(D8{wFP`aL>{zC zrom2?a(;-SC{-vF-WU-X*$f$|Z%qNH>Kmh^>HF~zcNP+Yo86qHEuIp~6y|7K2|D0c@8uIbmQ`8&)0000K{;t;Bp7*U=~3g1s!w~OOQY=oA~SC3%Nst zfVjDJw=SiF)!}|bP;hY*I=XZ-4hF7|gUyhTH*14SKhwQ@@B8I>jypK!e|L?e zo{BMU&s>3Xt_p0;(!xHfdLxAJW_bNXe~{<-Lbuz!CnC3i^P|i~Wc|SG(TKfgz~*qP zGJtx$zMN9}AR>2w}jsg26U}KH}pQKi+mCNPwM-f^5cfUbY z^;)UjZvRRtT?M`zrMZYa4k5hF06Lw{k7~90y4UONiO3SL#P}a{5&Pua3GS b#~hB|iV%~0x$;7+00000NkvXXu0mjf<5%|# literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_cloud_upload.png b/res/drawable-xhdpi/ic_cloud_upload.png new file mode 100644 index 0000000000000000000000000000000000000000..acae48ada0b90158a36e0945791dc5a5b907497c GIT binary patch literal 944 zcmV;h15f;kP)YIpAJ?7RrP7O9QviRW^; zI#2>~NxD%)&iKCX0a$Cjm?3^|x7+zI%O2%HC2 z*4Nk1t*oqc_YF|1)$(B&o(FClr+k7C{rqB#DK9N8o$J470KzaleOU64Xifke55w>) zYwcP3M#)62R?DmEJ0#eZlgRgIwOZfK&CLn$NX)7a7mLMPR##WQ0WjTe_X!Tz4@NED zBjJ+(s9I|m0E~z{mWKS0Aw*S|JZHpn8F{*koZHplY4e({!7D*A2P1EgmKLh?sTP6vr`n_3RUcLmp zo3>0sJ`n(G?To5k0FI_@6oX&e?e={J!1w*%MC561q9s*TtIcNf%0J(QTCLVk#bU7y z%rnAorel&1>h*d9t~r4QLGY%CJPG`nw&WoBjg5_0d%p+0)Sm^OBjHJP4B{7!F*V=! zKkC0eaHv>o?**FP)g3Q79V4_LkNn>cJ8addQO5 z-6SBssx&yeF<^vB1%;lpf7Bi-1q*EtJ3&;$+QydRCe~A{2esXeHoN=tkZeiQjm_@9 z*_p)mfj!L5o8SAr53|30GxG+}Xfzs)Mx)VaG#X94AwN2e$KxxQ*#gi`MD5JH4!~VA z9|OQO09T19Q!EzG3L)Id$;s0rBO`w;SwT|D(Dm!rpCO`G06Y(1d4(bVA)=E2J{lh% zKX&-=;lff8M6ps70Ic;Yc_La3;MGVZGL=rJ)1LI9I-rztSvH$J2wkH zpEWf#y(p#pTV-6Of~1s!P?@0h>({>npwkyExAJs0n|)VRFeOFWw(T86q%0`~v|E;S!FAp9wFauW z2KDsxv>1kQ5x_luTbaY}%=};~mAYC>keYvDhGFbO@Zis8y&~FSZ#pNLK;aaL%CdTCo}IU6ber$O8z!E zj^juGh{xlqB!vL<0_d(fuRArcZF>_D9RRS=3o&!$l!(XUQjw$_5v_I{=UO!f&-*VH zjYgM8B9UPt+7Dn=J>>vEI-Ncpi9`s%lQni>F!MRrb$_hp;Ic(Jkw`QfhH-+3_CVoW zX`ymTIF2Jrb*aXFwpV-XZG#^e7+A&3rF} z&TJIaq@Mts18}ZbEFK>k8v3qcL9tjY3BV4T9EFl*a~{BlrfKe%Qr?)a(aj=l+xFI( zg6!E%v|8iGosB%1#1OO|E=q1at*md1A zWd+%`y^V-^gD%HINd(|2%d*yn!{IN-#>Tj`L(B}If3e6Zp=t^+huy8Mt*_7iqM@&^ zuZ@|Li%Cui56&b2=&&s78`pI&0e~nLi=N!%@>)&_56&b4a3C6uE(ZWH^MrFz$|>Q2 zq;Nw+!)w6wolFoRqS4aT{QG_<6GVg%UkCu0n3y;O;DX-?1&k}1OlI`v z_0Y^M*RTA}DnKNX$)t00CY?&9P69aKcUA%6SJO1#1Liyz%jfew0KWD+JD*_y(b8JS zTLcXc4^M_dp=|)FUJbDX7)&OUM@t)J@5%J{_urk*=Z^z;%15p9@xm1zY4 zWowf?;0Ay{0Q?LfErd9h$z(?B^~R7!qtR$I8jVJy(P%2;UrD~uS3S}o)&Kwi07*qo IM6N<$f<>}(=l}o! literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_cloud_upload.png b/res/drawable-xxxhdpi/ic_cloud_upload.png new file mode 100644 index 0000000000000000000000000000000000000000..5177446b38f4dffac6c9279b090a90eeef17affe GIT binary patch literal 1865 zcmZvdcU03^7RO%z1Bn8m7hxb#R1iia4$?#ueo~CE8WPJuXhTscDugC2GzlFCFbN|t zLm9*Zh!7AIgJ+RJg4qDlAqZ=LWdehMblo5G-=4GYocliSym#(D_xru?reAPzkVk8y z0RZw&ju089*KWum;eCo7X#oT2Lvny%!fV5>H30_dmgChhn2vvkeOI9E?q$R+LI6y! zzawgNcm+=FcY+9%s1eBnw;)xjX6HB2TnH*SKREB&k)r-66v0!!Z0OPf8%nvo0rEn> ztY-$Qz<9X$>f?)VSswn#peL-Ms9a(eG;Pqvo-AsX*SLB7?v;a^y)tV3xYmmL)|uts zjJbu}(FpN_HzlIc_a(8S$giUK=QHNgn){H+?Ovw;gXSUZIO4oROw3kN>RyeSJo1Di zbQ005%u@q55dG3eq%8JKs^S%ugaUx%M8C#NG3HyU=xg3aVm~QzH6@F4(roz z^K0?3=r+aQT#8F*QkZ$CGHKxw{yi%U5!ClF0qvowrRB7;y1G-|TTMpjbXIryKVLlh za!^npaIm*un4h2Dax1j+LpfHGpZPpnM=3Qv{A@{T%Y<-GX*60V)xz~>2E|A%pS&Pl zM^1eW*6rnWyR5L#4 zG`7y1)xF_uU5)OGGUJS$fdoGB*%AJ4oO^nD9FRz4yj(Zo+8a8Z?oK|U8ZT34VXT+2 zuEGoVb{-(M?omUhhPba!N%L^vmA95+hPVn;WSwg zG#WuUn8+DiZ!1;k6=yjRmZD;vNF)*qGi3rIrLW1gPOE4gwFSOr96E`#7tw6uI~Xo4 zExovBgcZ8qD#x|B$KM3xs$8V~p<8S-iJHniOE6F^6V84rE-rq($FJ@Q8kW5~vtz0X ze1|LhS3p|+@xpb+&*w1wZPKB9OI3FJoc6)|u5cnVng0@3;ZoQ^pJLsj4I1uPrXldP zioFvBYF<|d)Nyxo+ZP4znV}uWltK-RuLlRO_RZ0;azEu?g00{NRtW8PgcLqyTN!)l zO;V6~t5x0C;zb`b&Px{4mLX2>59-h(X(sR?dKO0(q2njsx##%r%!P%8^5^wGED_=I ze4k>$6ej;;M2@(voFPVu@XNoX5P3`t@`_F z#6O=@S1;*ycDW43kL{dN&#b(ql%be%+Dcm`K^MRH$EVOV*XsU;7}1RVZei0gQ{x*p z?@N!O1NZ>*lQ^xYs7Sqm%Z)E_soIFmeosAfLR-SF#gFM5^oy-Uie1r*Ud^2YvEMv`d`iGFaVQnulHef{<29ts-qBqdF}oC zU!`l1ntsiGvBf|**w9yJ-fC<(2u zcfkXd`S|f;!GEs{3yV{4hn1t;Zm&l zFVA_cU;~no%-J8?`3Z9h6-n1IL}n@7;bMC~3M99bKlDv0_{><)H)6X=@od4n-x+sr z>!`FXep8tIb-~f7fPetd3U~KZD+4H0ssxeOr|VovfUY9|+`T!yzYuI07 zWXQI3jU7Fb4Bh2Ljo#L=40$@v4bJ4y#_v%<0CU!8Fg)$8$y<3v#WiJNQ%m_{;9>IW zb%?vv=GN9S>;(jP=P^i5+*V45N{r&xMWHJ-wALJq@PKAqS25O$9X&rxP-HljASXqy0Ax2fN*e1F@ZmPpD@}KU5k^u3R_g5A1 zyTgr*jf!8^)@BaJAD7&(SlrT+YHrWU@$cz|ALeyBE#umbb=!vZ4GgG1t*y-^8I}%2 zXYEK$$uNRTW+a?P!Ysv8!`yM8-_HNV+lBHV7th~D;}?fsgw3SSX$v;{hrdWhHO`g` zo%!3QjQuk{^EhuNN2ElwIi1f+k;RP+U&hp8%Rca5?klflzg8_*E0f%-%Ckro7EWV< w19LXe@Kak$ON(93wBvZY*8iCy?1b&T=ZBLMhX?kB!-Ebuoppg~h+awm1&0z}+W-In literal 0 HcmV?d00001 diff --git a/res/menu/drawer_menu.xml b/res/menu/drawer_menu.xml index 0493f7680161..8e6dc6c91774 100644 --- a/res/menu/drawer_menu.xml +++ b/res/menu/drawer_menu.xml @@ -40,7 +40,7 @@ diff --git a/res/values-sw360dp/dims.xml b/res/values-sw360dp/dims.xml index 8335e86359f0..ee25dc73f712 100644 --- a/res/values-sw360dp/dims.xml +++ b/res/values-sw360dp/dims.xml @@ -22,4 +22,5 @@ 64dp 32dp + 4 diff --git a/res/values-sw600dp/dims.xml b/res/values-sw600dp/dims.xml new file mode 100644 index 000000000000..553677fad810 --- /dev/null +++ b/res/values-sw600dp/dims.xml @@ -0,0 +1,26 @@ + + + + + 164dp + 64dp + + 32dp + 6 + diff --git a/res/values/dims.xml b/res/values/dims.xml index b853494b6ff9..6121f60c3343 100644 --- a/res/values/dims.xml +++ b/res/values/dims.xml @@ -99,5 +99,6 @@ 12dip 35dp 12dp - 2dp + 2dp + 4 diff --git a/res/values/strings.xml b/res/values/strings.xml index 3f9ed67f2f8b..5cabfec9a8a3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -479,7 +479,7 @@ Search This is a Nextcloud feature, please update. Learn more - Folder Sync + Instant Upload Participate Help us testing Found a bug? Something is odd? diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 7b9191c85db2..f6d0caf7b3c0 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -38,19 +38,21 @@ public class MediaProvider { private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; /** - * TODO rewrite/beautify * Getting All Images Paths. * * @param contentResolver the content resolver - * @return List with images folders + * @param itemLimit the number of media items (usually images) to be returned per media folder. + * @return list with media folders */ - public static List getMediaFolders(ContentResolver contentResolver) { + public static List getMediaFolders(ContentResolver contentResolver, int itemLimit) { Cursor cursor; List mediaFolders = new ArrayList<>(); int column_index_data, column_index_folder_name, column_index_data_image; String absolutePathOfImage; String folderName; + // TODO rewrite/beautify + String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore .MediaColumns.DATA, MediaStore.Images.Media.DATE_TAKEN}; String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; @@ -59,7 +61,7 @@ public static List getMediaFolders(ContentResolver contentResolver) .BUCKET_DISPLAY_NAME; String fileSelection = MediaStore.MediaColumns.DATA + " LIKE "; String folderSortOrder = "MAX(" + MediaStore.MediaColumns.DATA + ") DESC"; - String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT 8"; // LIMIT 8 + String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT "+ itemLimit; cursor = contentResolver.query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index c830823db523..172d09d8c543 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -101,32 +101,34 @@ private void setupContent() { mProgress = (LinearLayout) findViewById(android.R.id.progress); mEmpty = (TextView) findViewById(android.R.id.empty); - // TODO implement "dynamic" grid count via xml-value for tablet vs. phone - final int gridWidth = 4; - mAdapter = new FolderSyncAdapter(this, gridWidth, this, mRecyclerView); + final int gridWidth = getResources().getInteger(R.integer.media_grid_width); + mAdapter = new FolderSyncAdapter(this, gridWidth, this); mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver()); final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); mAdapter.setLayoutManager(lm); - int spacing = getResources().getDimensionPixelSize(R.dimen.mediaGridSpacing); + int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing); mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing)); mRecyclerView.setLayoutManager(lm); mRecyclerView.setAdapter(mAdapter); - load(); + load(gridWidth * 2); } /** * loads all media/synced folders, adds them to the recycler view adapter and shows the list. + * + * @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder */ - private void load() { + private void load(final int perFolderMediaItemLimit) { if (mAdapter.getItemCount() > 0) return; setListShown(false); final Handler mHandler = new Handler(); new Thread(new Runnable() { @Override public void run() { - final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver()); + final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver(), + perFolderMediaItemLimit); syncFolderItems = sortSyncedFolderItems(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), mediaFolders)); diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 196ec13d7aa4..c71f91f4cb7c 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -53,16 +53,16 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter mSyncFolderItems; - private final RecyclerView mRecyclerView; - public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, RecyclerView recyclerView) { + public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener) { mContext = context; - mGridWidth = gridWidth * 2; + mGridWidth = gridWidth; + mGridTotal = gridWidth * 2; mListener = listener; mSyncFolderItems = new ArrayList<>(); - mRecyclerView = recyclerView; } public void setSyncFolderItems(List syncFolderItems) { @@ -108,7 +108,6 @@ public void onClick(View v) { @Override public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { - final Context c = holder.itemView.getContext(); File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); @@ -162,10 +161,10 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); } - holder.itemView.setTag(relativePosition % (mGridWidth/2)); + holder.itemView.setTag(relativePosition % mGridWidth); - if (mSyncFolderItems.get(section).getNumberOfFiles() > 8 && relativePosition >= 8 - 1) { - holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - 8)); + if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { + holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - mGridTotal)); holder.counterBar.setVisibility(View.VISIBLE); holder.thumbnailDarkener.setVisibility(View.VISIBLE); } else { @@ -199,7 +198,7 @@ static class MainViewHolder extends RecyclerView.ViewHolder { final TextView counterValue; final ImageView thumbnailDarkener; - public MainViewHolder(View itemView) { + private MainViewHolder(View itemView) { super(itemView); image = (ImageView) itemView.findViewById(R.id.thumbnail); title = (TextView) itemView.findViewById(R.id.title); From 85b2223d09f0c2d17b913b0011f958e857dcb8da Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 15:24:00 +0200 Subject: [PATCH 62/99] fix copyright note --- res/values-sw600dp/dims.xml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/res/values-sw600dp/dims.xml b/res/values-sw600dp/dims.xml index 553677fad810..f6623a83d941 100644 --- a/res/values-sw600dp/dims.xml +++ b/res/values-sw600dp/dims.xml @@ -1,8 +1,8 @@ - - 164dp - 64dp - - 32dp 6 From 9efa7de760741aed8befe69d1f96886ef026317d Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 15:52:16 +0200 Subject: [PATCH 63/99] fix synced folder settings dialog theming + code cleanup --- res/layout/folder_sync_settings_layout.xml | 6 ++--- .../ui/activity/FolderSyncActivity.java | 4 ++-- ...SyncedFolderPreferencesDialogFragment.java | 23 ++++++++----------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index f0f07c98e26c..ce8dfe57540e 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -195,7 +195,7 @@ android:orientation="vertical" android:paddingLeft="@dimen/standard_padding"> - - - Date: Mon, 24 Oct 2016 16:43:10 +0200 Subject: [PATCH 64/99] initially show instant upload feature revamp message if instant upload has been used/active with the legacy preferences --- res/values/strings.xml | 1 + .../android/db/PreferenceManager.java | 3 +- .../ui/activity/FileDisplayActivity.java | 38 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 5cabfec9a8a3..84f054bb5929 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -503,6 +503,7 @@ No media folders found. Folder Sync Preferences Settings + Instant upload has been revamped completely. Please see the main menu and re-configure your instant upload. Sorry for the inconvenience.\n\nEnjoy the new and extended instant upload capabilities! disable + show info + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (PreferenceManager.instantPictureUploadEnabled(this) || PreferenceManager.instantPictureUploadEnabled + (this)) { + PreferenceManager.saveBooleanPreference(this, "instant_uploading", false); + PreferenceManager.saveBooleanPreference(this, "instant_video_uploading", false); + + // show info pop-up + new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.drawer_folder_sync) + .setMessage(R.string.folder_sync_new_info) + .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // show instant upload + Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); + dialog.dismiss(); + startActivity(folderSyncIntent); + } + }) + .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setIcon(R.drawable.ic_cloud_upload) + .show(); + } + } } @Override From cf28cdbbdaae3dd541293bdbf4ea417378a8880b Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 24 Oct 2016 18:28:31 +0200 Subject: [PATCH 65/99] better legacy shared prefereenced cleanup --- .../ui/activity/FileDisplayActivity.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index f90998478764..ed66c22d285a 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.SyncRequest; import android.content.pm.PackageManager; import android.content.res.Resources.NotFoundException; @@ -250,8 +251,21 @@ private void upgradeNotificationForInstantUpload() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (PreferenceManager.instantPictureUploadEnabled(this) || PreferenceManager.instantPictureUploadEnabled (this)) { - PreferenceManager.saveBooleanPreference(this, "instant_uploading", false); - PreferenceManager.saveBooleanPreference(this, "instant_video_uploading", false); + + // remove legacy shared preferences + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.remove("instant_uploading") + .remove("instant_video_uploading") + .remove("instant_upload_path") + .remove("instant_upload_path_use_subfolders") + .remove("instant_upload_on_wifi") + .remove("instant_upload_on_charging") + .remove("instant_video_upload_path") + .remove("instant_video_upload_path_use_subfolders") + .remove("instant_video_upload_on_wifi") + .remove("instant_video_uploading") + .remove("instant_video_upload_on_charging") + .remove("prefs_instant_behaviour").apply(); // show info pop-up new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) From d2d8352bacbbcc0b5f8e6279cfb5df030dc8a782 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Tue, 25 Oct 2016 18:02:12 +0200 Subject: [PATCH 66/99] recyclerview/media-folder optimized thumbnail cache/async task implementation --- .../datamodel/ThumbnailsCacheManager.java | 175 ++++++++++++++---- .../android/ui/adapter/FolderSyncAdapter.java | 59 ++---- 2 files changed, 153 insertions(+), 81 deletions(-) diff --git a/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 9ff4a3f0c52c..b042843835ca 100644 --- a/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -21,15 +21,6 @@ package com.owncloud.android.datamodel; -import java.io.File; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.net.FileNameMap; -import java.net.URLConnection; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; - import android.accounts.Account; import android.content.res.Resources; import android.graphics.Bitmap; @@ -39,7 +30,6 @@ import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.media.Image; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.AsyncTask; @@ -56,8 +46,9 @@ import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.ui.adapter.DiskLruImageCache; import com.owncloud.android.utils.BitmapUtils; -import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.MimetypeIconUtil; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; @@ -65,7 +56,6 @@ import java.io.File; import java.io.InputStream; import java.lang.ref.WeakReference; -import com.owncloud.android.utils.FileStorageUtils; /** * Manager for concurrent access to thumbnails cache. @@ -131,7 +121,37 @@ protected Void doInBackground(File... params) { return null; } } - + + /** + * Converts size of file icon from dp to pixel + * @return int + */ + private static int getThumbnailDimension(){ + // Converts dp to pixel + Resources r = MainApp.getAppContext().getResources(); + return Math.round(r.getDimension(R.dimen.file_icon_size_grid)); + } + + /** + * Add thumbnail to cache + * @param imageKey: thumb key + * @param bitmap: image for extracting thumbnail + * @param path: image path + * @param px: thumbnail dp + * @return Bitmap + */ + private static Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){ + + Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Rotate image, obeying exif tag + thumbnail = BitmapUtils.rotateImage(thumbnail,path); + + // Add thumbnail to cache + addBitmapToCache(imageKey, thumbnail); + + return thumbnail; + } public static void addBitmapToCache(String key, Bitmap bitmap) { synchronized (mThumbnailsDiskCacheLock) { @@ -141,7 +161,6 @@ public static void addBitmapToCache(String key, Bitmap bitmap) { } } - public static Bitmap getBitmapFromDiskCache(String key) { synchronized (mThumbnailsDiskCacheLock) { // Wait while disk cache is started from background thread @@ -256,27 +275,6 @@ protected void onPostExecute(Bitmap bitmap){ } } - /** - * Add thumbnail to cache - * @param imageKey: thumb key - * @param bitmap: image for extracting thumbnail - * @param path: image path - * @param px: thumbnail dp - * @return Bitmap - */ - private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){ - - Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px); - - // Rotate image, obeying exif tag - thumbnail = BitmapUtils.rotateImage(thumbnail,path); - - // Add thumbnail to cache - addBitmapToCache(imageKey, thumbnail); - - return thumbnail; - } - /** * Converts size of file icon from dp to pixel * @return int @@ -397,6 +395,97 @@ private Bitmap doFileInBackground() { } + public static class MediaThumbnailGenerationTask extends AsyncTask { + private final WeakReference mImageViewReference; + private File mFile; + private String mImageKey = null; + + public MediaThumbnailGenerationTask(ImageView imageView) { + // Use a WeakReference to ensure the ImageView can be garbage collected + mImageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Object... params) { + Bitmap thumbnail = null; + + try { + if (params[0] instanceof File) { + mFile = (File) params[0]; + if (params.length == 2) { + mImageKey = (String) params[1]; + } + + if (BitmapUtils.isImage(mFile)) { + thumbnail = doFileInBackground(mFile); + } + } + } catch (Throwable t) { + // the app should never break due to a problem with thumbnails + Log_OC.e(TAG, "Generation of thumbnail for " + mFile.getAbsolutePath() + " failed", t); + if (t instanceof OutOfMemoryError) { + System.gc(); + } + } + + return thumbnail; + } + + protected void onPostExecute(Bitmap bitmap) { + String tagId = ""; + final ImageView imageView = mImageViewReference.get(); + if (imageView != null) { + if (mFile != null) { + tagId = String.valueOf(mFile.hashCode()); + } + + if (bitmap != null) { + if (tagId.equals(String.valueOf(imageView.getTag()))) { + imageView.setImageBitmap(bitmap); + } + } else { + if (mFile != null) { + if (mFile.isDirectory()) { + imageView.setImageResource(R.drawable.ic_menu_archive); + } else { + if (BitmapUtils.isVideo(mFile)) { + imageView.setImageBitmap(ThumbnailsCacheManager.mDefaultVideo); + } else { + imageView.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, mFile.getName())); + } + } + } + } + } + } + + private Bitmap doFileInBackground(File file) { + final String imageKey; + + if (mImageKey != null) { + imageKey = mImageKey; + } else { + imageKey = String.valueOf(file.hashCode()); + } + + // Check disk cache in background thread + Bitmap thumbnail = getBitmapFromDiskCache(imageKey); + + // Not found in disk cache + if (thumbnail == null) { + + int px = getThumbnailDimension(); + + Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), px, px); + + if (bitmap != null) { + thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px); + } + } + return thumbnail; + } + } + public static class AvatarGenerationTask extends AsyncTask { private final WeakReference mAvatarGenerationListener; private final Object mCallContext; @@ -692,6 +781,22 @@ public ThumbnailGenerationTask getBitmapWorkerTask() { } } + public static class AsyncMediaThumbnailDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncMediaThumbnailDrawable( + Resources res, Bitmap bitmap, MediaThumbnailGenerationTask bitmapWorkerTask + ) { + + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public MediaThumbnailGenerationTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + public static class AsyncAvatarDrawable extends BitmapDrawable { private final WeakReference avatarWorkerTaskReference; diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index c71f91f4cb7c..3f08192663e5 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -22,7 +22,6 @@ package com.owncloud.android.ui.adapter; import android.content.Context; -import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; @@ -36,9 +35,6 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.utils.BitmapUtils; -import com.owncloud.android.utils.MimetypeIconUtil; import java.io.File; import java.util.ArrayList; @@ -116,51 +112,22 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos **/ boolean allowedToCreateNewThumbnail = (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, holder.image)); - if (!file.isDirectory()) { - holder.image.setImageResource(R.drawable.file); - } else { - holder.image.setImageResource(R.drawable.ic_menu_archive); - } + final ThumbnailsCacheManager.MediaThumbnailGenerationTask task = + new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image); + + final ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( + mContext.getResources(), + ThumbnailsCacheManager.mDefaultImg, + task + ); + holder.image.setImageDrawable(asyncDrawable); + + task.execute(file); + // set proper tag holder.image.setTag(file.hashCode()); - // get Thumbnail if file is image - if (BitmapUtils.isImage(file)) { - // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - String.valueOf(file.hashCode()) - ); - if (thumbnail != null) { - holder.image.setImageBitmap(thumbnail); - } else { - - // generate new Thumbnail - if (allowedToCreateNewThumbnail) { - final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(holder.image); - if (thumbnail == null) { - if (BitmapUtils.isVideo(file)) { - thumbnail = ThumbnailsCacheManager.mDefaultVideo; - } else { - thumbnail = ThumbnailsCacheManager.mDefaultImg; - } - } - final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncThumbnailDrawable( - mContext.getResources(), - thumbnail, - task - ); - holder.image.setImageDrawable(asyncDrawable); - task.execute(file); - Log_OC.v(TAG, "Executing task to generate a new thumbnail"); - - } // else, already being generated, don't restart it - } - } else { - holder.image.setImageResource(MimetypeIconUtil.getFileTypeIconId(null, file.getName())); - } - holder.itemView.setTag(relativePosition % mGridWidth); if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { From cc45b0f1e9865c8ae6eb6aae9adcabfff608ab25 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 12:14:56 +0200 Subject: [PATCH 67/99] added possibility to enable/disable sync folder in the settings screen --- res/layout/folder_sync_settings_layout.xml | 60 ++++++------------- .../ui/activity/FolderSyncActivity.java | 12 +++- .../android/ui/adapter/FolderSyncAdapter.java | 5 ++ ...SyncedFolderPreferencesDialogFragment.java | 29 ++++++++- 4 files changed, 59 insertions(+), 47 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index ce8dfe57540e..26bdfde049e5 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -45,27 +45,13 @@ android:orientation="vertical"> - - - - - - + - + + + + + - - - - - syncFolderItems) { notifyDataSetChanged(); } + public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) { + mSyncFolderItems.set(location, syncFolderItem); + notifyDataSetChanged(); + } + @Override public int getSectionCount() { return mSyncFolderItems.size(); diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index 06d9ad231412..1a4c6f8fb9b2 100644 --- a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -33,6 +33,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; +import android.widget.ImageView; import android.widget.TextView; import com.owncloud.android.R; @@ -53,6 +54,8 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private CharSequence[] mUploadBehaviorItemStrings; protected View mView = null; + private boolean mEnabled; + private ImageView mEnabledIcon; private CheckBox mUploadOnWifiCheckbox; private CheckBox mUploadOnChargingCheckbox; private CheckBox mUploadUseSubfoldersCheckbox; @@ -105,9 +108,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false); - ((TextView) mView.findViewById(R.id.local_folder_summary)).setText(mSyncedFolder.getLocalPath()); - ((TextView) mView.findViewById(R.id.remote_folder_summary)).setText(mSyncedFolder.getRemotePath()); - setupDialogElements(mView); setupListeners(mView); @@ -121,6 +121,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa */ private void setupDialogElements(View view) { // find/saves UI elements + mEnabledIcon = (ImageView) view.findViewById(R.id.local_folder_status_icon); mLocalFolderSummary = (TextView) view.findViewById(R.id.local_folder_summary); mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary); @@ -132,6 +133,7 @@ private void setupDialogElements(View view) { mUploadBehaviorSummary = (TextView) view.findViewById(R.id.setting_instant_behaviour_summary); // Set values + setEnabled(mSyncedFolder.getEnabled()); mLocalFolderSummary.setText(mSyncedFolder.getLocalPath()); mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); @@ -142,6 +144,20 @@ private void setupDialogElements(View view) { mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadAction()]); } + /** + * set correct icon/flag. + * + * @param enabled if enabled or disabled + */ + private void setEnabled(boolean enabled) { + mSyncedFolder.setEnabled(enabled); + if(enabled) { + mEnabledIcon.setImageResource(R.drawable.ic_cloud_sync_on); + } else { + mEnabledIcon.setImageResource(R.drawable.ic_cloud_sync_off); + } + } + /** * set (new) remote path on activity result of the folder picker activity. The result gets originally propagated * to the underlying activity since the picker is an activity and the result can't get passed to the dialog @@ -200,6 +216,13 @@ public void onClick(View v) { } }); + view.findViewById(R.id.local_folder_container).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setEnabled(!mSyncedFolder.getEnabled()); + } + }); + view.findViewById(R.id.setting_instant_behaviour_container).setOnClickListener( new OnClickListener() { @Override From e879517481227b77f44b558a182536e27da4a68f Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 13:36:25 +0200 Subject: [PATCH 68/99] removed final declaration --- .../owncloud/android/ui/adapter/FolderSyncAdapter.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index 36347c33f288..a9c84f1ac1e4 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -112,15 +112,10 @@ public void onBindViewHolder(MainViewHolder holder, int section, int relativePos File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); - /** Cancellation needs do be checked and done before changing the drawable in fileIcon, or - * {@link ThumbnailsCacheManager#cancelPotentialThumbnailWork} will NEVER cancel any task. - **/ - boolean allowedToCreateNewThumbnail = (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, holder.image)); - - final ThumbnailsCacheManager.MediaThumbnailGenerationTask task = + ThumbnailsCacheManager.MediaThumbnailGenerationTask task = new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image); - final ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = + ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( mContext.getResources(), ThumbnailsCacheManager.mDefaultImg, From 29d5e1e8cb612d28d708c51d64a77d92e9775b83 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 14:02:40 +0200 Subject: [PATCH 69/99] tweaked layout for preferences --- res/layout/folder_sync_settings_layout.xml | 105 +++++++++--------- res/values/strings.xml | 2 +- ...SyncedFolderPreferencesDialogFragment.java | 4 +- .../dialog/parcel/SyncedFolderParcelable.java | 12 ++ 4 files changed, 66 insertions(+), 57 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index 26bdfde049e5..33f6b232fd18 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -33,6 +33,13 @@ android:text="@string/folder_sync_preferences" android:textAppearance="@style/TextAppearance.AppCompat.Title"/> + + + android:paddingBottom="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:orientation="vertical" + android:paddingBottom="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> - + android:maxLines="1" + android:text="@string/prefs_folder_sync_remote_path_title" + android:textAppearance="?attr/textAppearanceListItem"/> - - - - - + @@ -141,6 +140,7 @@ android:id="@+id/setting_instant_upload_on_wifi_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:baselineAligned="false" android:clipToPadding="false" android:gravity="center_vertical" android:minHeight="?attr/listPreferredItemHeightSmall"> @@ -187,6 +187,7 @@ android:id="@+id/setting_instant_upload_on_charging_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:baselineAligned="false" android:clipToPadding="false" android:gravity="center_vertical" android:minHeight="?attr/listPreferredItemHeightSmall"> @@ -233,6 +234,7 @@ android:id="@+id/setting_instant_upload_path_use_subfolders_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:baselineAligned="false" android:clipToPadding="false" android:gravity="center_vertical" android:minHeight="?attr/listPreferredItemHeightSmall"> @@ -291,38 +293,31 @@ android:id="@+id/setting_instant_behaviour_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:baselineAligned="false" android:clipToPadding="false" android:gravity="center_vertical" - android:minHeight="?attr/listPreferredItemHeightSmall"> + android:minHeight="?attr/listPreferredItemHeightSmall" + android:orientation="vertical" + android:paddingBottom="@dimen/standard_padding" + android:paddingTop="@dimen/standard_padding"> - - - - - + android:ellipsize="marquee" + android:maxLines="1" + android:text="@string/prefs_instant_behaviour_title" + android:textAppearance="?attr/textAppearanceListItem"/> - + diff --git a/res/values/strings.xml b/res/values/strings.xml index 84f054bb5929..aa48ffdca480 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -501,7 +501,7 @@ Choose folder… Loading folders… No media folders found. - Folder Sync Preferences + Instant Upload Preferences Settings Instant upload has been revamped completely. Please see the main menu and re-configure your instant upload. Sorry for the inconvenience.\n\nEnjoy the new and extended instant upload capabilities! diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index 1a4c6f8fb9b2..8dbbfe85b348 100644 --- a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -54,12 +54,12 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment { private CharSequence[] mUploadBehaviorItemStrings; protected View mView = null; - private boolean mEnabled; private ImageView mEnabledIcon; private CheckBox mUploadOnWifiCheckbox; private CheckBox mUploadOnChargingCheckbox; private CheckBox mUploadUseSubfoldersCheckbox; private TextView mUploadBehaviorSummary; + private TextView mLocalFolderName; private TextView mLocalFolderSummary; private TextView mRemoteFolderSummary; @@ -121,6 +121,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa */ private void setupDialogElements(View view) { // find/saves UI elements + mLocalFolderName = (TextView )view.findViewById(R.id.folder_sync_settings_subtitle); mEnabledIcon = (ImageView) view.findViewById(R.id.local_folder_status_icon); mLocalFolderSummary = (TextView) view.findViewById(R.id.local_folder_summary); mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary); @@ -134,6 +135,7 @@ private void setupDialogElements(View view) { // Set values setEnabled(mSyncedFolder.getEnabled()); + mLocalFolderName.setText(mSyncedFolder.getFolderName()); mLocalFolderSummary.setText(mSyncedFolder.getLocalPath()); mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index bb99dab460c5..c70ae04aa6b1 100644 --- a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -29,6 +29,7 @@ * Parcelable for {@link SyncedFolderDisplayItem} objects to transport them from/to dialog fragments. */ public class SyncedFolderParcelable implements Parcelable { + private String mFolderName; private String mLocalPath; private String mRemotePath; private Boolean mWifiOnly = false; @@ -45,6 +46,7 @@ public SyncedFolderParcelable() { public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, int section) { mId = syncedFolderDisplayItem.getId(); + mFolderName = syncedFolderDisplayItem.getFolderName(); mLocalPath = syncedFolderDisplayItem.getLocalPath(); mRemotePath = syncedFolderDisplayItem.getRemotePath(); mWifiOnly = syncedFolderDisplayItem.getWifiOnly(); @@ -58,6 +60,7 @@ public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, i private SyncedFolderParcelable(Parcel read) { mId = read.readLong(); + mFolderName = read.readString(); mLocalPath = read.readString(); mRemotePath = read.readString(); mWifiOnly = read.readInt()!= 0; @@ -72,6 +75,7 @@ private SyncedFolderParcelable(Parcel read) { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mId); + dest.writeString(mFolderName); dest.writeString(mLocalPath); dest.writeString(mRemotePath); dest.writeInt(mWifiOnly ? 1 : 0); @@ -102,6 +106,14 @@ public int describeContents() { return 0; } + public String getFolderName() { + return mFolderName; + } + + public void setFolderName(String mFolderName) { + this.mFolderName = mFolderName; + } + public String getLocalPath() { return mLocalPath; } From 235b01b4bf0a3a80728f712cd7bcdd8b215edfe2 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 18:08:25 +0200 Subject: [PATCH 70/99] fixed review comments --- res/layout/folder_sync_item_header.xml | 4 +- res/values-sw600dp/dims.xml | 16 ++-- res/xml/folder_sync_preferences.xml | 92 ------------------- .../android/datamodel/MediaFolder.java | 6 +- 4 files changed, 14 insertions(+), 104 deletions(-) delete mode 100644 res/xml/folder_sync_preferences.xml diff --git a/res/layout/folder_sync_item_header.xml b/res/layout/folder_sync_item_header.xml index 2c6e3d867e18..d73aa80d248d 100644 --- a/res/layout/folder_sync_item_header.xml +++ b/res/layout/folder_sync_item_header.xml @@ -12,11 +12,11 @@ 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 + 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 . + License along with this program. If not, see . --> . + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see . --> 6 diff --git a/res/xml/folder_sync_preferences.xml b/res/xml/folder_sync_preferences.xml deleted file mode 100644 index 511f617fd7f4..000000000000 --- a/res/xml/folder_sync_preferences.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index db7137629506..a87bd810197f 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -12,11 +12,11 @@ *

* 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 + * 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 . + * License along with this program. If not, see . */ package com.owncloud.android.datamodel; @@ -24,7 +24,7 @@ import java.util.List; /** - * Created by scherzia on 22.09.2016. + * Business object representing a media folder with all information that are gathered via media queries. */ public class MediaFolder { public String folderName; From 3c62f4128d811bd283d0f287647ebe741bc92405 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 26 Oct 2016 20:32:31 +0200 Subject: [PATCH 71/99] observers are restarted after changing a setting --- src/com/owncloud/android/MainApp.java | 28 ++++++++++ .../datamodel/SyncedFolderProvider.java | 12 ++--- .../services/SyncedFolderJobService.java | 9 ---- .../observer/SyncedFolderObserverService.java | 53 +++++++++++++++---- 4 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index 64e59541bb97..53548ab0c9ad 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -22,12 +22,15 @@ import android.app.Activity; import android.app.Application; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Environment; +import android.os.IBinder; import com.owncloud.android.authentication.PassCodeManager; import com.owncloud.android.datamodel.ThumbnailsCacheManager; @@ -58,6 +61,9 @@ public class MainApp extends Application { private static boolean mOnlyOnDevice = false; + private static SyncedFolderObserverService mObserverService; + private boolean mBound; + public void onCreate(){ super.onCreate(); @@ -90,6 +96,7 @@ public void onCreate(){ Log_OC.d("SyncedFolderObserverService", "Start service SyncedFolderObserverService"); Intent i = new Intent(this, SyncedFolderObserverService.class); startService(i); + bindService(i, syncedFolderObserverServiceConnection, Context.BIND_AUTO_CREATE); // register global protection with pass code registerActivityLifecycleCallbacks( new ActivityLifecycleCallbacks() { @@ -189,6 +196,10 @@ public static boolean getOnlyOnDevice(){ return mOnlyOnDevice; } + public static SyncedFolderObserverService getSyncedFolderObserverService() { + return mObserverService; + } + // user agent public static String getUserAgent() { String appString = getAppContext().getResources().getString(R.string.user_agent); @@ -210,4 +221,21 @@ public static String getUserAgent() { return userAgent; } + + /** Defines callbacks for service binding, passed to bindService() */ + private ServiceConnection syncedFolderObserverServiceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + SyncedFolderObserverService.SyncedFolderObserverBinder binder = (SyncedFolderObserverService.SyncedFolderObserverBinder) service; + mObserverService = binder.getService(); + mBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mBound = false; + } + }; + } diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index 9540c5b330b3..e74dd057d173 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -25,6 +25,7 @@ import android.net.Uri; import android.support.annotation.NonNull; +import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; @@ -66,7 +67,7 @@ public long storeFolderSync(SyncedFolder syncedFolder) { Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); if (result != null) { - notifyFolderSyncObservers(); + notifyFolderSyncObservers(syncedFolder); return Long.parseLong(result.getPathSegments().get(1)); } else { Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); @@ -204,7 +205,7 @@ public int updateSyncFolder(SyncedFolder syncedFolder) { ); if (result > 0) { - notifyFolderSyncObservers(); + notifyFolderSyncObservers(syncedFolder); } return result; @@ -258,9 +259,8 @@ private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFol /** * Inform all observers about data change. */ - private void notifyFolderSyncObservers() { - Log_OC.d(TAG, "notifying folder sync data observers"); - setChanged(); - notifyObservers(); + private void notifyFolderSyncObservers(SyncedFolder syncedFolder) { + MainApp.getSyncedFolderObserverService().restartObserver(syncedFolder); + Log_OC.d(TAG, "notifying folder sync data observers for changed/added: " + syncedFolder.getLocalPath()); } } diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java index afbb7bda89f6..0d3fce7f45fb 100644 --- a/src/com/owncloud/android/services/SyncedFolderJobService.java +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -28,11 +28,7 @@ import android.content.Context; import android.content.Intent; import android.os.Build; -import android.os.Message; -import android.os.Messenger; import android.os.PersistableBundle; -import android.os.RemoteException; -import android.util.Log; import com.owncloud.android.MainApp; import com.owncloud.android.authentication.AccountUtils; @@ -43,11 +39,6 @@ import com.owncloud.android.utils.MimetypeIconUtil; import java.io.File; -import java.util.Date; - -/** - * Created by tobi on 25.09.16. - */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class SyncedFolderJobService extends JobService { diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java index 6b44e95a65e8..1eee13d8d3e1 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserverService.java @@ -2,6 +2,7 @@ import android.app.Service; import android.content.Intent; +import android.os.Binder; import android.os.IBinder; import com.owncloud.android.MainApp; @@ -9,13 +10,13 @@ import com.owncloud.android.datamodel.SyncedFolderProvider; import com.owncloud.android.lib.common.utils.Log_OC; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; public class SyncedFolderObserverService extends Service { private static final String TAG = "SyncedFolderObserverService"; private SyncedFolderProvider mProvider; - private List syncedFolderObservers = new ArrayList<>(); + private HashMap syncedFolderMap = new HashMap<>(); + private final IBinder mBinder = new SyncedFolderObserverBinder(); @Override public void onCreate() { @@ -26,10 +27,11 @@ public void onCreate() { public int onStartCommand(Intent intent, int flags, int startId) { Log_OC.d(TAG, "start"); for (SyncedFolder syncedFolder : mProvider.getSyncedFolders()) { - SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder); - - observer.startWatching(); - syncedFolderObservers.add(observer); + if (syncedFolder.isEnabled()) { + SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder); + observer.startWatching(); + syncedFolderMap.put(syncedFolder.getLocalPath(), observer); + } } return Service.START_NOT_STICKY; @@ -37,14 +39,45 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public void onDestroy() { - for (SyncedFolderObserver observer : syncedFolderObservers) { + for (SyncedFolderObserver observer : syncedFolderMap.values()) { observer.stopWatching(); - syncedFolderObservers.remove(observer); + syncedFolderMap.remove(observer); + } + } + + /** + * Restart oberver if it is enabled + * If syncedFolder exists already, use it, otherwise create new observer + * @param syncedFolder + */ + public void restartObserver(SyncedFolder syncedFolder){ + if (syncedFolderMap.containsKey(syncedFolder.getLocalPath())) { + Log_OC.d(TAG, "stop observer: " + syncedFolder.getLocalPath()); + syncedFolderMap.get(syncedFolder.getLocalPath()).stopWatching(); + syncedFolderMap.remove(syncedFolder.getLocalPath()); + } + + if (syncedFolder.isEnabled()) { + Log_OC.d(TAG, "start observer: " + syncedFolder.getLocalPath()); + if (syncedFolderMap.containsKey(syncedFolder.getLocalPath())) { + syncedFolderMap.get(syncedFolder.getLocalPath()).startWatching(); + } else { + SyncedFolderObserver observer = new SyncedFolderObserver(syncedFolder); + observer.startWatching(); + syncedFolderMap.put(syncedFolder.getLocalPath(), observer); + } } } @Override public IBinder onBind(Intent arg0) { - return null; + return mBinder; } + + public class SyncedFolderObserverBinder extends Binder { + public SyncedFolderObserverService getService() { + return SyncedFolderObserverService.this; + } + } + } From 10cc73cf68cd16e236487fa1b645ed856a148035 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 21:41:10 +0200 Subject: [PATCH 72/99] header --- src/com/owncloud/android/ui/activity/Preferences.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index aa2610f4b817..dbda26d7e236 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -5,6 +5,7 @@ * @author David A. Velasco * Copyright (C) 2011 Bartek Przybylski * Copyright (C) 2016 ownCloud Inc. + * Copyright (C) 2016 Nextcloud * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, From 624e222ad57acd534afd97ab0c26f1a0ecc6cc1f Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 23:24:40 +0200 Subject: [PATCH 73/99] beatufied code --- .../android/datamodel/MediaProvider.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index f6d0caf7b3c0..1eed291c46d6 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -35,7 +35,22 @@ */ public class MediaProvider { private static final String TAG = MediaProvider.class.getSimpleName(); + + // fixed query parameters private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + private static final String[] FOLDER_PROJECTION = new String[] { + "Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.DATA, MediaStore.Images.Media.DATE_TAKEN + }; + private static final String[] FILE_PROJECTION = new String[] { + MediaStore.MediaColumns.DATA + }; + private static final String FOLDER_SELECTION = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + + " IS NOT NULL) GROUP BY (" + MediaStore.Images.Media.BUCKET_DISPLAY_NAME; + private static final String FILE_SELECTION = MediaStore.MediaColumns.DATA + " LIKE "; + private static final String FOLDER_SORT_ORDER = "MAX(" + MediaStore.MediaColumns.DATA + ") DESC"; + private static final String SEPARATOR = "/"; + private static final String WILDCARD = "%"; /** * Getting All Images Paths. @@ -51,19 +66,9 @@ public static List getMediaFolders(ContentResolver contentResolver, String absolutePathOfImage; String folderName; - // TODO rewrite/beautify - - String[] folderProjection = new String[]{"Distinct " + MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore - .MediaColumns.DATA, MediaStore.Images.Media.DATE_TAKEN}; - String[] fileProjection = new String[]{MediaStore.MediaColumns.DATA}; - String folderSelection = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " IS NOT NULL) GROUP BY (" + MediaStore - .Images.Media - .BUCKET_DISPLAY_NAME; - String fileSelection = MediaStore.MediaColumns.DATA + " LIKE "; - String folderSortOrder = "MAX(" + MediaStore.MediaColumns.DATA + ") DESC"; - String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT "+ itemLimit; + String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit; - cursor = contentResolver.query(MEDIA_URI, folderProjection, folderSelection, null, folderSortOrder); + cursor = contentResolver.query(MEDIA_URI, FOLDER_PROJECTION, FOLDER_SELECTION, null, FOLDER_SORT_ORDER); if (cursor == null) { return mediaFolders; @@ -76,8 +81,9 @@ public static List getMediaFolders(ContentResolver contentResolver, while (cursor.moveToNext()) { absolutePathOfImage = cursor.getString(column_index_data); - cursorImages = contentResolver.query(MEDIA_URI, fileProjection, fileSelection + "'" + - absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", null, + cursorImages = contentResolver.query(MEDIA_URI, FILE_PROJECTION, FILE_SELECTION + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(SEPARATOR)) + SEPARATOR + + WILDCARD + "'", null, fileSortOrder); if (cursorImages != null) { @@ -85,9 +91,8 @@ public static List getMediaFolders(ContentResolver contentResolver, MediaFolder mediaFolder = new MediaFolder(); folderName = cursor.getString(column_index_folder_name); mediaFolder.folderName = folderName; - mediaFolder.absolutePath = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + - folderName - .length()); + mediaFolder.absolutePath = absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(folderName) + + folderName.length()); mediaFolder.filePaths = new ArrayList<>(); column_index_data_image = cursorImages.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); @@ -99,9 +104,10 @@ public static List getMediaFolders(ContentResolver contentResolver, mediaFolder.numberOfFiles = contentResolver.query( MEDIA_URI, - fileProjection, - fileSelection + "'" - + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf("/")) + "/%'", + FILE_PROJECTION, + FILE_SELECTION + "'" + + absolutePathOfImage.substring(0, absolutePathOfImage.lastIndexOf(SEPARATOR)) + + SEPARATOR + WILDCARD + "'", null, null).getCount(); From 66c4a57ba4256e62e3528d51c98f87502e1754e5 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Wed, 26 Oct 2016 23:34:46 +0200 Subject: [PATCH 74/99] added javadoc --- .../android/datamodel/MediaFolder.java | 7 +++++ .../android/datamodel/SyncedFolder.java | 28 +++++++++++++++++++ .../datamodel/SyncedFolderProvider.java | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/datamodel/MediaFolder.java b/src/com/owncloud/android/datamodel/MediaFolder.java index a87bd810197f..e3e29861db4a 100644 --- a/src/com/owncloud/android/datamodel/MediaFolder.java +++ b/src/com/owncloud/android/datamodel/MediaFolder.java @@ -27,8 +27,15 @@ * Business object representing a media folder with all information that are gathered via media queries. */ public class MediaFolder { + /** name of the folder. */ public String folderName; + + /** absolute path of the folder. */ public String absolutePath; + + /** list of file paths of the folder's content */ public List filePaths = new ArrayList<>(); + + /** total number of files in the media folder. */ public long numberOfFiles; } diff --git a/src/com/owncloud/android/datamodel/SyncedFolder.java b/src/com/owncloud/android/datamodel/SyncedFolder.java index 3553a5ee0282..a931b1263e40 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolder.java +++ b/src/com/owncloud/android/datamodel/SyncedFolder.java @@ -21,6 +21,9 @@ package com.owncloud.android.datamodel; +/** + * Synced folder entity containing all information per synced folder. + */ public class SyncedFolder { public static final long UNPERSISTED_ID = Long.MIN_VALUE; private long id = UNPERSISTED_ID; @@ -33,6 +36,19 @@ public class SyncedFolder { private Integer uploadAction; private boolean enabled; + /** + * constructor for already persisted entity. + * + * @param id the primary key + * @param localPath local path + * @param remotePath remote path + * @param wifiOnly upload on wifi only flag + * @param chargingOnly upload on charging only + * @param subfolderByDate create sub-folders by date (month) + * @param account the account owning the synced folder + * @param uploadAction the action to be done after the upload + * @param enabled flag if synced folder config is active + */ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { this.id = id; @@ -46,6 +62,18 @@ public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOn this.enabled = enabled; } + /** + * constructor for new, to be persisted entity. + * + * @param localPath local path + * @param remotePath remote path + * @param wifiOnly upload on wifi only flag + * @param chargingOnly upload on charging only + * @param subfolderByDate create sub-folders by date (month) + * @param account the account owning the synced folder + * @param uploadAction the action to be done after the upload + * @param enabled flag if synced folder config is active + */ public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly, Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) { this.localPath = localPath; diff --git a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java index e74dd057d173..8e3740cf3036 100644 --- a/src/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/src/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -34,7 +34,7 @@ import java.util.Observable; /** - * Database provider for handling the persistence aspects of synced folders. + * Database provider for handling the persistence aspects of {@link SyncedFolder}s. */ public class SyncedFolderProvider extends Observable { static private final String TAG = SyncedFolderProvider.class.getSimpleName(); From 34003e922f268d6d01df4ab357617175b9fb9f7f Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Thu, 27 Oct 2016 12:04:36 +0200 Subject: [PATCH 75/99] moved to switch instead of icon --- res/layout/folder_sync_settings_layout.xml | 73 ++++++++++++------- ...SyncedFolderPreferencesDialogFragment.java | 14 ++-- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index 33f6b232fd18..f640eee6aeb2 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -26,19 +26,54 @@ android:orientation="vertical" android:padding="@dimen/standard_padding"> - + android:layout_height="wrap_content"> - + + + + + + + + + + + + + + + - - - - - - Date: Thu, 27 Oct 2016 15:32:49 +0200 Subject: [PATCH 76/99] only show folder path on topo with synced folder made bold --- res/layout/folder_sync_settings_layout.xml | 47 ++----------------- res/values/strings.xml | 1 + ...SyncedFolderPreferencesDialogFragment.java | 20 +++++--- .../owncloud/android/utils/DisplayUtils.java | 24 ++++++++-- 4 files changed, 41 insertions(+), 51 deletions(-) diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml index f640eee6aeb2..a751ee32ac94 100644 --- a/res/layout/folder_sync_settings_layout.xml +++ b/res/layout/folder_sync_settings_layout.xml @@ -47,11 +47,13 @@ android:textAppearance="@style/TextAppearance.AppCompat.Title"/> + android:ellipsize="middle" + android:maxLines="2" + android:text="@string/folder_sync_preferences_folder_path" + android:textColor="?android:attr/textColorSecondary"/> @@ -86,45 +88,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - - - - - - - - - Instant Upload Preferences Settings Instant upload has been revamped completely. Please see the main menu and re-configure your instant upload. Sorry for the inconvenience.\n\nEnjoy the new and extended instant upload capabilities! + For %1$s Instant upload --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 83d6cc67c11d..955f1f47408f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -479,7 +479,7 @@ Search This is a Nextcloud feature, please update. Learn more - Instant Upload + Instant upload Participate Help us testing Found a bug? Something is odd? @@ -501,7 +501,7 @@ Choose folder… Loading folders… No media folders found. - Instant Upload Preferences + Instant upload preferences Settings Instant upload has been revamped completely. Please see the main menu and re-configure your instant upload. Sorry for the inconvenience.\n\nEnjoy the new and extended instant upload capabilities! For %1$s From 9a6bf6e2e4ce75fd428fbd02ae9dde5725e8cc7b Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Tue, 1 Nov 2016 18:51:03 +0100 Subject: [PATCH 80/99] Instant upload --> Auto upload --- res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e0d581cf6895..a1a74687935f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -479,7 +479,7 @@ Search This is a Nextcloud feature, please update. Learn more - Instant upload + Auto upload Participate Help us testing Found a bug? Something is odd? @@ -501,9 +501,9 @@ Choose folder… Loading folders… No media folders found. - Instant upload preferences + Auto upload preferences Settings - Instant upload has been revamped completely. Please see the main menu and re-configure your instant upload. Sorry for the inconvenience.\n\nEnjoy the new and extended instant upload capabilities! + Instant upload has been revamped completely. Please see the main menu and re-configure your auto upload. Sorry for the inconvenience.\n\nEnjoy the new and extended auto upload capabilities! For %1$s disable + show info - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (PreferenceManager.instantPictureUploadEnabled(this) || PreferenceManager.instantPictureUploadEnabled - (this)) { - - // remove legacy shared preferences - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); - editor.remove("instant_uploading") - .remove("instant_video_uploading") - .remove("instant_upload_path") - .remove("instant_upload_path_use_subfolders") - .remove("instant_upload_on_wifi") - .remove("instant_upload_on_charging") - .remove("instant_video_upload_path") - .remove("instant_video_upload_path_use_subfolders") - .remove("instant_video_upload_on_wifi") - .remove("instant_video_uploading") - .remove("instant_video_upload_on_charging") - .remove("prefs_instant_behaviour").apply(); - - // show info pop-up - new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) - .setTitle(R.string.drawer_folder_sync) - .setMessage(R.string.folder_sync_new_info) - .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // show instant upload - Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); - dialog.dismiss(); - startActivity(folderSyncIntent); - } - }) - .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setIcon(R.drawable.ic_cloud_upload) - .show(); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + (PreferenceManager.instantPictureUploadEnabled(this) || + PreferenceManager.instantPictureUploadEnabled(this))) { + + // remove legacy shared preferences + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.remove("instant_uploading") + .remove("instant_video_uploading") + .remove("instant_upload_path") + .remove("instant_upload_path_use_subfolders") + .remove("instant_upload_on_wifi") + .remove("instant_upload_on_charging") + .remove("instant_video_upload_path") + .remove("instant_video_upload_path_use_subfolders") + .remove("instant_video_upload_on_wifi") + .remove("instant_video_uploading") + .remove("instant_video_upload_on_charging") + .remove("prefs_instant_behaviour").apply(); + + // show info pop-up + new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.drawer_folder_sync) + .setMessage(R.string.folder_sync_new_info) + .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // show instant upload + Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); + dialog.dismiss(); + startActivity(folderSyncIntent); + } + }) + .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setIcon(R.drawable.ic_cloud_upload) + .show(); } } diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java index 43c7353dee7c..c453f718f112 100644 --- a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -264,8 +264,9 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { @Override public void onDestroyView() { Log_OC.d(TAG, "destroy SyncedFolderPreferencesDialogFragment view"); - if (getDialog() != null && getRetainInstance()) + if (getDialog() != null && getRetainInstance()) { getDialog().setDismissMessage(null); + } super.onDestroyView(); } diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java index 2de5a640c9bd..f5d9f499ba57 100644 --- a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -147,7 +147,7 @@ public void setChargingOnly(Boolean mChargingOnly) { this.mChargingOnly = mChargingOnly; } - public boolean getEnabled() { + public Boolean getEnabled() { return mEnabled; } From 500707ce6df65ea0ff818400a807acdd72a5ec63 Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 7 Nov 2016 16:19:20 +0100 Subject: [PATCH 90/99] brackets --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index dcb891ba0bb0..c70cfd6f5669 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -121,7 +121,9 @@ private void setupContent() { * @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder */ private void load(final int perFolderMediaItemLimit) { - if (mAdapter.getItemCount() > 0) return; + if (mAdapter.getItemCount() > 0) { + return; + } setListShown(false); final Handler mHandler = new Handler(); new Thread(new Runnable() { From df60c9c8d7647fd589065f598f6b516c59b92c5a Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Mon, 7 Nov 2016 23:02:16 +0100 Subject: [PATCH 91/99] fixing codacy warnings --- src/com/owncloud/android/MainApp.java | 1 + .../owncloud/android/datamodel/MediaProvider.java | 1 - .../files/InstantUploadBroadcastReceiver.java | 2 +- .../android/ui/activity/FolderSyncActivity.java | 2 +- .../android/ui/adapter/FolderSyncAdapter.java | 14 +++++++------- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index 9c8a8ffd9336..18dd6b80a67f 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -67,6 +67,7 @@ public class MainApp extends Application { private static boolean mOnlyOnDevice = false; private static SyncedFolderObserverService mObserverService; + @SuppressWarnings("unused") private boolean mBound; diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 5fd5499dc1cb..7ee9f1df5aa4 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -40,7 +40,6 @@ public class MediaProvider { private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; private static final String[] FILE_PROJECTION = new String[]{MediaStore.MediaColumns.DATA}; private static final String FILE_SELECTION = MediaStore.Images.Media.BUCKET_ID + "="; - private static final Uri uri1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; private static final String[] FOLDER_PROJECTION = { "Distinct " + MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME }; private static final String FOLDER_SORT_ORDER = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " ASC"; diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index cb0d13c22023..cbca4ff5750d 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -132,7 +132,7 @@ private void handleNewPictureAction(Context context, Intent intent) { new FileUploader.UploadRequester(); int behaviour = getUploadBehaviour(context); - Boolean subfolderByDate = com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context); + Boolean subfolderByDate = PreferenceManager.instantPictureUploadPathUseSubfolders(context); SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); String uploadPathdef = context.getString(R.string.instant_upload_path); String uploadPath = pref.getString("instant_upload_path", uploadPathdef); diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index c70cfd6f5669..20fba35aefd4 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -356,7 +356,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { @Override public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection()); - boolean dirty = !(item.isEnabled() == syncedFolder.getEnabled()); + boolean dirty = item.isEnabled() != syncedFolder.getEnabled(); item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder .getUploadAction(), syncedFolder.getEnabled()); diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java index a9c84f1ac1e4..df6de99ca831 100644 --- a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -157,13 +157,13 @@ public interface ClickListener { } static class MainViewHolder extends RecyclerView.ViewHolder { - final ImageView image; - final TextView title; - final ImageButton menuButton; - final ImageButton syncStatusButton; - final LinearLayout counterBar; - final TextView counterValue; - final ImageView thumbnailDarkener; + private final ImageView image; + private final TextView title; + private final ImageButton menuButton; + private final ImageButton syncStatusButton; + private final LinearLayout counterBar; + private final TextView counterValue; + private final ImageView thumbnailDarkener; private MainViewHolder(View itemView) { super(itemView); From 4c56ce2b0df537ca5a9cf0a3e9075eccbb5eb4fc Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Thu, 10 Nov 2016 21:46:37 +0100 Subject: [PATCH 92/99] fix only one picture is uploaded --- src/com/owncloud/android/services/SyncedFolderJobService.java | 4 ++-- .../android/services/observer/SyncedFolderObserver.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/services/SyncedFolderJobService.java b/src/com/owncloud/android/services/SyncedFolderJobService.java index 7d9d624894b9..a4f5ff3405cc 100644 --- a/src/com/owncloud/android/services/SyncedFolderJobService.java +++ b/src/com/owncloud/android/services/SyncedFolderJobService.java @@ -51,8 +51,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public boolean onStartJob(JobParameters params) { - Log_OC.d(TAG, "startJob: " + params.getJobId()); - Context context = MainApp.getAppContext(); PersistableBundle bundle = params.getExtras(); String filePath = bundle.getString("filePath"); @@ -62,6 +60,8 @@ public boolean onStartJob(JobParameters params) { Account account = AccountUtils.getOwnCloudAccountByName(context, bundle.getString("account")); Integer uploadBehaviour = bundle.getInt("uploadBehaviour"); + Log_OC.d(TAG, "startJob: " + params.getJobId() + ", filePath: " + filePath); + File file = new File(filePath); String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath()); diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index f0bb95067343..8be4dc63ce38 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -23,7 +23,6 @@ class SyncedFolderObserver extends RecursiveFileObserver { private Context context; - private static final int MY_BACKGROUND_JOB = 0; public static final String TAG = "SyncedFolderObserver"; private SyncedFolder syncedFolder; @@ -55,8 +54,9 @@ public void onEvent(int event, String path) { bundle.putInt("subfolderByDate", syncedFolder.getSubfolderByDate() ? 1 : 0); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + Long date = new Date().getTime(); JobInfo job = new JobInfo.Builder( - MY_BACKGROUND_JOB, + date.intValue(), new ComponentName(context, SyncedFolderJobService.class)) .setRequiresCharging(syncedFolder.getChargingOnly()) .setRequiredNetworkType(syncedFolder.getWifiOnly() ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY) From e13a85fb5396dea06b28321547003e4129f6b9be Mon Sep 17 00:00:00 2001 From: AndyScherzinger Date: Thu, 10 Nov 2016 22:33:04 +0100 Subject: [PATCH 93/99] fix access --- src/com/owncloud/android/utils/RecursiveFileObserver.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/owncloud/android/utils/RecursiveFileObserver.java b/src/com/owncloud/android/utils/RecursiveFileObserver.java index e85b30d18af6..b482882c43f6 100644 --- a/src/com/owncloud/android/utils/RecursiveFileObserver.java +++ b/src/com/owncloud/android/utils/RecursiveFileObserver.java @@ -20,13 +20,13 @@ package com.owncloud.android.utils; +import android.os.FileObserver; + import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack; -import android.os.FileObserver; - public class RecursiveFileObserver extends FileObserver { private final List mObservers = new ArrayList<>(); @@ -38,7 +38,7 @@ public RecursiveFileObserver(String path) { this(path, ALL_EVENTS); } - RecursiveFileObserver(String path, int mask) { + public RecursiveFileObserver(String path, int mask) { super(path, mask); mPath = path; mMask = mask; From 0bff89949a73c02746a8ec09070bc092ed39e165 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 12:17:53 +0100 Subject: [PATCH 94/99] show pending jobs --- .../android/services/observer/SyncedFolderObserver.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index 8be4dc63ce38..a220c7fde1f1 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -54,6 +54,11 @@ public void onEvent(int event, String path) { bundle.putInt("subfolderByDate", syncedFolder.getSubfolderByDate() ? 1 : 0); JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + + for (JobInfo job: js.getAllPendingJobs()){ + Log_OC.d(TAG, "pending job: " + job.getExtras().get("filePath")); + } + Long date = new Date().getTime(); JobInfo job = new JobInfo.Builder( date.intValue(), @@ -61,6 +66,7 @@ public void onEvent(int event, String path) { .setRequiresCharging(syncedFolder.getChargingOnly()) .setRequiredNetworkType(syncedFolder.getWifiOnly() ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY) .setExtras(bundle) + .setPersisted(true) .build(); Integer result = js.schedule(job); From e33f946d35c7603160f75288e4eaca7698dbccf6 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 16:43:11 +0100 Subject: [PATCH 95/99] ignore media folders within our own app --- src/com/owncloud/android/datamodel/MediaProvider.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/datamodel/MediaProvider.java b/src/com/owncloud/android/datamodel/MediaProvider.java index 7ee9f1df5aa4..2ed18b9a9cfe 100644 --- a/src/com/owncloud/android/datamodel/MediaProvider.java +++ b/src/com/owncloud/android/datamodel/MediaProvider.java @@ -27,6 +27,9 @@ import android.provider.MediaStore; import android.util.Log; +import com.owncloud.android.MainApp; + +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -55,6 +58,7 @@ public static List getMediaFolders(ContentResolver contentResolver, // query media/image folders Cursor cursorFolders = contentResolver.query(MEDIA_URI, FOLDER_PROJECTION, null, null, FOLDER_SORT_ORDER); List mediaFolders = new ArrayList<>(); + String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder(); if (cursorFolders != null) { String folderName; @@ -100,7 +104,9 @@ public static List getMediaFolders(ContentResolver contentResolver, count.close(); } } - mediaFolders.add(mediaFolder); + if (!mediaFolder.absolutePath.startsWith(dataPath)) { + mediaFolders.add(mediaFolder); + } } cursorFolders.close(); } From 718c1cc28947a09dd0b5b892df41117c570d4a3b Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 16:44:22 +0100 Subject: [PATCH 96/99] remove debugging output --- .../android/services/observer/SyncedFolderObserver.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java index a220c7fde1f1..c2e81e050e0e 100644 --- a/src/com/owncloud/android/services/observer/SyncedFolderObserver.java +++ b/src/com/owncloud/android/services/observer/SyncedFolderObserver.java @@ -55,10 +55,6 @@ public void onEvent(int event, String path) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - for (JobInfo job: js.getAllPendingJobs()){ - Log_OC.d(TAG, "pending job: " + job.getExtras().get("filePath")); - } - Long date = new Date().getTime(); JobInfo job = new JobInfo.Builder( date.intValue(), From 34e85d0c2097dc50a3d87a66a47e81c17bf33125 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 16:46:29 +0100 Subject: [PATCH 97/99] sorting of media folder is now case insensitive --- src/com/owncloud/android/ui/activity/FolderSyncActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java index 20fba35aefd4..9ea678a1ec10 100644 --- a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -187,7 +187,7 @@ public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { } else if (f2 == null) { return 1; } else if (f1.isEnabled() && f2.isEnabled()) { - return f1.getFolderName().compareTo(f2.getFolderName()); + return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase()); } else if (f1.isEnabled()) { return -1; } else if (f2.isEnabled()) { @@ -203,7 +203,7 @@ public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { } else if (PRIORITIZED_FOLDER.equals(f2.getFolderName())) { return 1; } else { - return f1.getFolderName().compareTo(f2.getFolderName()); + return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase()); } } }); From 47c8afdcc06c258135aa74434043da9a5d0a82ae Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 17:46:04 +0100 Subject: [PATCH 98/99] hide Auto Sync for pre Nougat version --- src/com/owncloud/android/ui/activity/DrawerActivity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/owncloud/android/ui/activity/DrawerActivity.java b/src/com/owncloud/android/ui/activity/DrawerActivity.java index f0270034bc13..5ddcc4ca3c35 100644 --- a/src/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/com/owncloud/android/ui/activity/DrawerActivity.java @@ -248,6 +248,11 @@ protected void setupDrawerMenu(NavigationView navigationView) { navigationView.setItemIconTintList(null); } + // hide Auto Sync for pre Nougat version + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + navigationView.getMenu().removeItem(R.id.nav_folder_sync); + } + // setup actions for drawer menu items navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { From 93e861cb9da339e9fc996111229796d63550ebb0 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 16 Nov 2016 17:54:57 +0100 Subject: [PATCH 99/99] hide Auto Sync for pre Nougat version --- src/com/owncloud/android/ui/activity/DrawerActivity.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/DrawerActivity.java b/src/com/owncloud/android/ui/activity/DrawerActivity.java index 5ddcc4ca3c35..df3e764d6df6 100644 --- a/src/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/com/owncloud/android/ui/activity/DrawerActivity.java @@ -167,8 +167,8 @@ protected void setupDrawer() { setupQuotaElement(); - // show folder sync menu item only for Android 5+ - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // show folder sync menu item only for Android 7+ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { mNavigationView.getMenu().removeItem(R.id.nav_folder_sync); } } @@ -248,11 +248,6 @@ protected void setupDrawerMenu(NavigationView navigationView) { navigationView.setItemIconTintList(null); } - // hide Auto Sync for pre Nougat version - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - navigationView.getMenu().removeItem(R.id.nav_folder_sync); - } - // setup actions for drawer menu items navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() {