diff options
Diffstat (limited to 'app/src')
68 files changed, 1206 insertions, 501 deletions
diff --git a/app/src/main/assets/css/core/main.compact.css b/app/src/main/assets/css/core/main.compact.css index c91e892e..de4d4ec5 100644 --- a/app/src/main/assets/css/core/main.compact.css +++ b/app/src/main/assets/css/core/main.compact.css @@ -1,6 +1,6 @@ #viewport { background: #451515 !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: rgba(255, 0, 255, 0.02) !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: rgba(255, 0, 255, 0.02) !important; } ._cv_, ._2sq8 { background-color: rgba(255, 0, 255, 0.02) !important; } diff --git a/app/src/main/assets/css/core/main.scss b/app/src/main/assets/css/core/main.scss index 85ce793f..272df88f 100644 --- a/app/src/main/assets/css/core/main.scss +++ b/app/src/main/assets/css/core/main.scss @@ -7,7 +7,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, -._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, +._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, diff --git a/app/src/main/assets/css/themes/custom.compact.css b/app/src/main/assets/css/themes/custom.compact.css index e8add9c8..00663706 100644 --- a/app/src/main/assets/css/themes/custom.compact.css +++ b/app/src/main/assets/css/themes/custom.compact.css @@ -1,6 +1,6 @@ #viewport { background: $B$ !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: $BT$ !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: $BT$ !important; } ._cv_, ._2sq8 { background-color: $BT$ !important; } @@ -12,7 +12,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before, ._590n, ._4g8h, ._2cpp, ._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj, .excessItem, ._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51, ._4qax, .aclb, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, .acbk { background: $BBT$ !important; } -[style*="color"], body, input, ._42rv, ._4qau, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: $T$ !important; } +[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: $T$ !important; } ._15kl::before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid $D$ !important; } @@ -20,7 +20,7 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS ._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9, ._2u4w, ._3u9t, ._55fj, ._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._3on6, ._2om3, ._2ol-, ._56d8, .al, ._1gkq, ._5fjv, ._5fjw, ._4z83 { border-top: 1px solid $D$ !important; } -._15ny::after, ._z-w, ._8i2, ._2nk0, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid $D$ !important; } +._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid $D$ !important; } ._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, ._1_y5, ._lr0, ._5hgt, ._2cpp, ._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer, ._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f { border: 1px solid $D$ !important; } diff --git a/app/src/main/assets/css/themes/material_amoled.compact.css b/app/src/main/assets/css/themes/material_amoled.compact.css index 576e755a..8e57d751 100644 --- a/app/src/main/assets/css/themes/material_amoled.compact.css +++ b/app/src/main/assets/css/themes/material_amoled.compact.css @@ -1,6 +1,6 @@ #viewport { background: #000 !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #000 !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #000 !important; } ._cv_, ._2sq8 { background-color: #000 !important; } @@ -12,7 +12,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before, ._590n, ._4g8h, ._2cpp, ._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj, .excessItem, ._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51, ._4qax, .aclb, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, .acbk { background: rgba(89, 89, 89, 0.2) !important; } -[style*="color"], body, input, ._42rv, ._4qau, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } +[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } ._15kl::before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; } @@ -20,7 +20,7 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS ._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9, ._2u4w, ._3u9t, ._55fj, ._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._3on6, ._2om3, ._2ol-, ._56d8, .al, ._1gkq, ._5fjv, ._5fjw, ._4z83 { border-top: 1px solid rgba(255, 255, 255, 0.3) !important; } -._15ny::after, ._z-w, ._8i2, ._2nk0, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } +._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } ._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, ._1_y5, ._lr0, ._5hgt, ._2cpp, ._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer, ._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f { border: 1px solid rgba(255, 255, 255, 0.3) !important; } diff --git a/app/src/main/assets/css/themes/material_dark.compact.css b/app/src/main/assets/css/themes/material_dark.compact.css index 860d01e5..b96e1572 100644 --- a/app/src/main/assets/css/themes/material_dark.compact.css +++ b/app/src/main/assets/css/themes/material_dark.compact.css @@ -1,6 +1,6 @@ #viewport { background: #303030 !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #303030 !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #303030 !important; } ._cv_, ._2sq8 { background-color: #303030 !important; } @@ -12,7 +12,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before, ._590n, ._4g8h, ._2cpp, ._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj, .excessItem, ._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51, ._4qax, .aclb, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, .acbk { background: rgba(137, 137, 137, 0.2) !important; } -[style*="color"], body, input, ._42rv, ._4qau, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } +[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } ._15kl::before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; } @@ -20,7 +20,7 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS ._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9, ._2u4w, ._3u9t, ._55fj, ._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._3on6, ._2om3, ._2ol-, ._56d8, .al, ._1gkq, ._5fjv, ._5fjw, ._4z83 { border-top: 1px solid rgba(255, 255, 255, 0.3) !important; } -._15ny::after, ._z-w, ._8i2, ._2nk0, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } +._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } ._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, ._1_y5, ._lr0, ._5hgt, ._2cpp, ._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer, ._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f { border: 1px solid rgba(255, 255, 255, 0.3) !important; } diff --git a/app/src/main/assets/css/themes/material_glass.compact.css b/app/src/main/assets/css/themes/material_glass.compact.css index bd0f5671..f36c2994 100644 --- a/app/src/main/assets/css/themes/material_glass.compact.css +++ b/app/src/main/assets/css/themes/material_glass.compact.css @@ -1,6 +1,6 @@ #viewport { background: rgba(0, 0, 0, 0.3) !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: transparent !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: transparent !important; } ._cv_, ._2sq8 { background-color: transparent !important; } @@ -12,7 +12,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before, ._590n, ._4g8h, ._2cpp, ._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj, .excessItem, ._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51, ._4qax, .aclb, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, .acbk { background: rgba(128, 128, 128, 0.05) !important; } -[style*="color"], body, input, ._42rv, ._4qau, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } +[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #fff !important; } ._15kl::before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(255, 255, 255, 0.3) !important; } @@ -20,7 +20,7 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS ._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9, ._2u4w, ._3u9t, ._55fj, ._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._3on6, ._2om3, ._2ol-, ._56d8, .al, ._1gkq, ._5fjv, ._5fjw, ._4z83 { border-top: 1px solid rgba(255, 255, 255, 0.3) !important; } -._15ny::after, ._z-w, ._8i2, ._2nk0, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } +._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(255, 255, 255, 0.3) !important; } ._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, ._1_y5, ._lr0, ._5hgt, ._2cpp, ._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer, ._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f { border: 1px solid rgba(255, 255, 255, 0.3) !important; } diff --git a/app/src/main/assets/css/themes/material_light.compact.css b/app/src/main/assets/css/themes/material_light.compact.css index f2b086a3..2714fdb7 100644 --- a/app/src/main/assets/css/themes/material_light.compact.css +++ b/app/src/main/assets/css/themes/material_light.compact.css @@ -1,6 +1,6 @@ #viewport { background: #fafafa !important; } -body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #fafafa !important; } +body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r, ._59e9, ._5pz4, ._5lp4, ._5lp5, .container, .subpage, ._5n_f, #static_templates, ._22_8, .tlBody, #timelineBody, .timelineX, .timeline, .feed, .tlPrelude, .tlFeedPlaceholder, ._4_d0, .al, ._1gkq, ._5c5b, ._1qxg, ._5luf, ._2new, ._cld, ._3zvb, ._2nk0, .btnD, .btnI, ._11ub, ._5p7j, ._55wm, ._5rgs, ._5xuj, ._1sv1, ._45fu, ._18qg, ._1_ac, ._5w3g, ._3e18, ._10c_, ._2jl2, ._5q_r, ._5yt8, ._idb, ._2ip_, ._f6s, ._2l5v, ._8i2, ._kr5, ._d4i, ._577z, ._2u4w, ._3u9p, ._3u9t, ._2v9s, ._cw4, ._5_y-, ._5_y_, ._5_z3, ._cwy, ._5_z0, ._5_z1, ._5_z2, ._2mtc, ._206a, ._1_-1, ._1ybg, .appCenterCategorySelectorButton, ._5c9u, div._5y57::before, ._59f6._55so::before, .structuredPublisher, ._94v, ._vqv, ._5lp5, ._55wm, ._2om3, ._2ol-, ._1f9d, ._vee, ._31a-, ._3r8b, ._3r9d, .acw, ._4_xl, ._1p70, ._1p70, ._1ih_, ._51v6, ._u2c, ._484w, ._3ils, ._rm7, ._32qk, ._d01, ._2y60, ._5fu3, ._2foa, ._2y5_, ._38o9, ._1kb, .mAppCenterFatLabel, ._3f50, .mentions-placeholder, .mentions, .mentions-shadow, .mentions-measurer, .acg, ._59tu, ._52z5, ._4l9b, ._4gj3, .groupChromeView, ._uww, textarea, ._15n_, ._skt, ._5f28, ._14_j, ._3bg5, ._53_-, ._52x1 { background: #fafafa !important; } ._cv_, ._2sq8 { background-color: #fafafa !important; } @@ -12,7 +12,7 @@ body, #root, #header, [style*="background-color"], ._55wo, ._1upc, input, ._2f9r button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS, .touch::before, ._590n, ._4g8h, ._2cpp, ._5xo2, ._5u5a::before, ._4u3j, ._15ks, ._5hua, ._59tt, ._41ft, .jx-tokenizer, ._55fj, .excessItem, ._4e8n, ._5pxa._3uj9, ._5n_5, ._u2d, ._56bu::before, ._5h8f, ._d00, ._2066, ._2k51, ._4qax, .aclb, ._4756, ._w34, ._56bv::before, ._5769, ._34iv, ._z-w, .acbk { background: rgba(255, 255, 255, 0.2) !important; } -[style*="color"], body, input, ._42rv, ._4qau, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #000 !important; } +[style*="color"], body, input, ._42rv, ._4qau, ._dwm .descArea, ._z-z, ._z-v, ._1e8d, ._36nl, ._36nm, ._2_11, ._2_rf, ._2ip_, ._403p, ._5xu2, ._3ml8, ._3mla, ._43mh, .touch .btn, p, span, .fcg, button, ._52j9, ._52jb, ._52ja, ._5j35, ._rnk, ._24u0, ._1g06, ._14ye, .fcb, ._56cz._56c_, ._1gk_, ._55fj, ._45fu, ._18qg, ._1_ac, textarea, ._24pi, ._4en9, ._1kb, ._5p7j, ._2klz, ._5780, ._5781, ._5782, ._3u9u, ._3u9_, ._3u9s, ._1hcx, ._2066, ._1_-1, ._cv_, ._1nbx, ._2cuh, ._4ms9, ._4ms5, ._4ms6, ._31b4, ._31b5, ._5q_r, ._idb, ._27vp, ._4nwe, ._4nw9, ._27vi, .appCenterAppInfo, .appCenterPermissions, ._3c9l, ._3c9m, ._4jn_, ._32qt, ._3mom, ._3moo, ._-7o, ._d00, ._d01, ._559g, ._2new, .appCenterCategorySelectorButton, .mentions-input, .mentions-placeholder, .fcw, ._5-7t, .fcl, ._4qas, .thread-title, .title, ._46pa, ._336p, ._1rrd, ._2om4, ._3m1m, ._2om2, ._5n_e, .appListExplanation, ._5yt8, ._8he, ._2luw, ._5rgs, h1, h2, h3, h4, h5, h6 { color: #000 !important; } ._15kl::before, ._5j35::after, ._2k4b, ._3to7, ._4nw8 { border-left: 1px solid rgba(0, 0, 0, 0.3) !important; } @@ -20,7 +20,7 @@ button:not([style*=image]), button::before, .touch ._56bt, ._56be::before, .btnS ._1mx0, ._1rbr, ._5yt8, ._idb, ._cld, ._15n_, ._3-2-, ._27ve, ._2s20, ._gui, ._2s21 > *::after, ._32qk, ._d00, ._d01, ._38o9, ._2u4w, ._3u9t, ._55fj, ._52x1, ._3wjp, ._usq, ._2cul:before, ._13e_, .jewel .flyout, ._3bg5 ._52x6, ._3on6, ._2om3, ._2ol-, ._56d8, .al, ._1gkq, ._5fjv, ._5fjw, ._4z83 { border-top: 1px solid rgba(0, 0, 0, 0.3) !important; } -._15ny::after, ._z-w, ._8i2, ._2nk0, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(0, 0, 0, 0.3) !important; } +._15ny::after, ._z-w, ._8i2, ._2nk0, ._22_8, ._2u4w, ._577z:not(:last-child) ._ygd, ._3u9u, ._3mgz, ._52x6, ._2066, ._5luf, .mAppCenterFatLabel, .appCenterCategorySelectorButton, ._1q6v, ._5q_r, ._5yt8, ._ap1, ._52x1, ._59tu, ._usq, ._13e_, ._59f6._55so::before, ._4gj3, .jx-result, ._2om3, ._2ol-, ._1f9d, ._vef, ._55x2 > *, .al, ._44qk, ._1gkq, ._5rgs, ._5xuj, ._1sv1, ._idb, ._5lp5, ._3-2-, ._3to6, ._ir5, ._4nw6, ._4nwh, ._27ve, div._51v6::before, ._3c9h::before, ._2s20, ._gui, ._5jku, ._2foa, ._2y60, ._5fu3, ._4en9, ._1kb:not(:last-child) ._1kc, ._5pz4, ._5lp4, ._5lp5, ._3on6, ._5h6z, ._5h6x, ._2om4, ._5fjw > div, ._5fjv > :first-child, ._5fjw > :first-child, ._5fjv, ._5fjw, ._4z83 { border-bottom: 1px solid rgba(0, 0, 0, 0.3) !important; } ._d4i, ._f6s, .mentions-suggest-item, .mentions-suggest, ._1_y5, ._lr0, ._5hgt, ._2cpp, ._4e8n, ._uww, .mentions-placeholder, .mentions-shadow, .mentions-measurer, ._5whq, ._59tt, ._41ft::after, .jx-tokenizer, ._3uqf, ._4756, ._1rrd, ._5n_f { border: 1px solid rgba(0, 0, 0, 0.3) !important; } diff --git a/app/src/main/assets/js/menu.js b/app/src/main/assets/js/menu.js index 7394c824..a43b1820 100644 --- a/app/src/main/assets/js/menu.js +++ b/app/src/main/assets/js/menu.js @@ -4,6 +4,8 @@ if (!window.hasOwnProperty('frost_menu')) { window.frost_menu = true; var viewport = document.querySelector('#viewport'); var root = document.querySelector('#root'); + if (!viewport) console.log('Menu.js: viewport is null'); + if (!root) console.log('Menu.js: root is null'); var y = new MutationObserver(function(mutations) { viewport.removeAttribute('style'); root.removeAttribute('style'); @@ -21,18 +23,24 @@ if (!window.hasOwnProperty('frost_menu')) { console.log('Found side menu'); while (root.firstChild) root.removeChild(root.firstChild); - while (menu.childNodes.length) - root.appendChild(menu.childNodes[0]); - Frost.emit(0); + while (menu.childNodes.length) { + console.log('append'); + viewport.appendChild(menu.childNodes[0]); + } + if (typeof Frost !== 'undefined') Frost.emit(0); setTimeout(function() { y.disconnect(); console.log('Unhook styler'); }, 500); } }); - x.observe(document.querySelector('#mJewelNav'), { + var jewel = document.querySelector('#mJewelNav'); + if (!jewel) console.log('Menu.js: jewel is null'); + x.observe(jewel, { childList: true, subtree: true }); - document.querySelector('#bookmarks_jewel').querySelector('a').click(); + var menuA = document.querySelector('#bookmarks_jewel').querySelector('a'); + if (!menuA) console.log('Menu.js: jewel is null') + menuA.click(); } diff --git a/app/src/main/assets/js/menu.min.js b/app/src/main/assets/js/menu.min.js index 5403e03a..5b65ac9b 100644 --- a/app/src/main/assets/js/menu.min.js +++ b/app/src/main/assets/js/menu.min.js @@ -1,8 +1,10 @@ if(!window.hasOwnProperty("frost_menu")){ console.log("Registering frost_menu"),window.frost_menu=!0 -;var viewport=document.querySelector("#viewport"),root=document.querySelector("#root"),y=new MutationObserver(function(e){ -viewport.removeAttribute("style"), -root.removeAttribute("style") +;var viewport=document.querySelector("#viewport"),root=document.querySelector("#root") +;viewport||console.log("Menu.js: viewport is null"), +root||console.log("Menu.js: root is null") +;var y=new MutationObserver(function(e){ +viewport.removeAttribute("style"),root.removeAttribute("style") }) ;y.observe(viewport,{ attributes:!0 @@ -13,14 +15,19 @@ attributes:!0 var o=document.querySelector(".mSideMenu") ;if(null!==o){ for(x.disconnect(),console.log("Found side menu");root.firstChild;)root.removeChild(root.firstChild) -;for(;o.childNodes.length;)root.appendChild(o.childNodes[0]) -;Frost.emit(0),setTimeout(function(){ +;for(;o.childNodes.length;)console.log("append"), +viewport.appendChild(o.childNodes[0]) +;"undefined"!=typeof Frost&&Frost.emit(0),setTimeout(function(){ y.disconnect(),console.log("Unhook styler") },500) } -}) -;x.observe(document.querySelector("#mJewelNav"),{ +}),jewel=document.querySelector("#mJewelNav") +;jewel||console.log("Menu.js: jewel is null"), +x.observe(jewel,{ childList:!0, subtree:!0 -}),document.querySelector("#bookmarks_jewel").querySelector("a").click() +}) +;var menuA=document.querySelector("#bookmarks_jewel").querySelector("a") +;menuA||console.log("Menu.js: jewel is null"), +menuA.click() }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 371f9c33..4fabf8b8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -1,9 +1,12 @@ package com.pitchedapps.frost +import android.app.Activity import android.app.Application import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Bundle import android.widget.ImageView +import ca.allanwang.kau.logging.KL import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.signature.ApplicationVersionSignature @@ -12,13 +15,12 @@ import com.crashlytics.android.answers.Answers import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.utils.CrashReportingTree +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Showcase import com.raizlabs.android.dbflow.config.FlowConfig import com.raizlabs.android.dbflow.config.FlowManager import io.fabric.sdk.android.Fabric -import timber.log.Timber import java.util.* @@ -39,22 +41,18 @@ class FrostApp : Application() { Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs") // if (LeakCanary.isInAnalyzerProcess(this)) return // refWatcher = LeakCanary.install(this) - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) -// LeakCanary.enableDisplayLeakActivity(this) - } else { + if (!BuildConfig.DEBUG) { Fabric.with(this, Crashlytics(), Answers()) Crashlytics.setUserIdentifier(Prefs.frostId) - Timber.plant(CrashReportingTree()) } + KL.debug(BuildConfig.DEBUG) + L.debug(BuildConfig.DEBUG) Prefs.verboseLogging = false FbCookie() if (Prefs.installDate == -1L) Prefs.installDate = System.currentTimeMillis() if (Prefs.identifier == -1) Prefs.identifier = Random().nextInt(Int.MAX_VALUE) Prefs.lastLaunch = System.currentTimeMillis() - - super.onCreate() /** @@ -69,6 +67,24 @@ class FrostApp : Application() { .thumbnail(old).into(imageView) } }) + if (BuildConfig.DEBUG) + registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { + override fun onActivityPaused(activity: Activity) {} + override fun onActivityResumed(activity: Activity) {} + override fun onActivityStarted(activity: Activity) {} + + override fun onActivityDestroyed(activity: Activity) { + L.d("Activity ${activity.localClassName} destroyed") + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {} + + override fun onActivityStopped(activity: Activity) {} + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + L.d("Activity ${activity.localClassName} created") + } + }) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt index fbcd12cc..670e8669 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt @@ -1,10 +1,8 @@ package com.pitchedapps.frost.activities -import android.os.Bundle import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintSet import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -13,8 +11,6 @@ import ca.allanwang.kau.about.LibraryIItem import ca.allanwang.kau.adapters.FastItemThemedAdapter import ca.allanwang.kau.adapters.ThemableIItem import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.animators.FadeScaleAnimatorAdd -import ca.allanwang.kau.animators.KauAnimator import ca.allanwang.kau.utils.* import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library @@ -26,10 +22,8 @@ import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.typeface.IIcon import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs -import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread -import java.security.InvalidParameterException /** @@ -41,6 +35,7 @@ class AboutActivity : AboutActivityBase(null, { backgroundColor = Prefs.bgColor.withMinAlpha(200) cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor cutoutDrawableRes = R.drawable.frost_f_256 + faqPageTitleRes = R.string.faq_title faqXmlRes = R.xml.frost_faq faqParseNewLine = false }) { @@ -60,6 +55,7 @@ class AboutActivity : AboutActivityBase(null, { "kotterknife", "materialdialogs", "materialdrawer", + "rxjava", "subsamplingscaleimageview" ) @@ -68,6 +64,9 @@ class AboutActivity : AboutActivityBase(null, { return l } + var lastClick = -1L + var clickCount = 0 + override fun postInflateMainPage(adapter: FastItemThemedAdapter<IItem<*, *>>) { /** * Frost may not be a library but we're conveying the same info @@ -85,7 +84,22 @@ class AboutActivity : AboutActivityBase(null, { } } adapter.add(LibraryIItem(frost)).add(AboutLinks()) - + adapter.withOnClickListener { _, _, item, _ -> + if (item is LibraryIItem) { + val now = System.currentTimeMillis() + if (now - lastClick > 500) + clickCount = 0 + else + clickCount++ + lastClick = now + if (clickCount == 7 && !Prefs.debugSettings) { + Prefs.debugSettings = true + L.d("Debugging section enabled") + toast(R.string.debug_toast_enabled) + } + } + false + } } class AboutLinks : AbstractItem<AboutLinks, AboutLinks.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() { @@ -128,7 +142,7 @@ class AboutActivity : AboutActivityBase(null, { setImageDrawable(icon.toDrawable(context, 32)) scaleType = ImageView.ScaleType.CENTER background = context.resolveDrawable(android.R.attr.selectableItemBackgroundBorderless) - setOnClickListener({ onClick() }) + setOnClickListener { onClick() } container.addView(this) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt index 77a20d04..c7ca5ec7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -2,10 +2,16 @@ package com.pitchedapps.frost.activities import android.os.Bundle import ca.allanwang.kau.internal.KauBaseActivity +import com.github.pwittchen.reactivenetwork.library.rx2.Connectivity +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed import com.pitchedapps.frost.utils.setFrostTheme +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers /** * Created by Allan Wang on 2017-06-12. @@ -29,4 +35,41 @@ abstract class BaseActivity : KauBaseActivity() { setFrostTheme() } + private var networkDisposable: Disposable? = null + private var networkConsumer: ((Connectivity) -> Unit)? = null + + fun setNetworkObserver(consumer: (connectivity: Connectivity) -> Unit) { + this.networkConsumer = consumer + } + + fun observeNetworkConnectivity() { + val consumer = networkConsumer ?: return + networkDisposable = ReactiveNetwork.observeNetworkConnectivity(applicationContext) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + connectivity: Connectivity -> + connectivity.apply { + L.d("Network connectivity changed: isAvailable: $isAvailable isRoaming: $isRoaming") + consumer(connectivity) + } + } + } + + fun disposeNetworkConnectivity() { + if (!(networkDisposable?.isDisposed ?: true)) + networkDisposable?.dispose() + networkDisposable = null + } + + override fun onResume() { + super.onResume() + disposeNetworkConnectivity() + observeNetworkConnectivity() + } + + override fun onPause() { + super.onPause() + disposeNetworkConnectivity() + } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt index 6a39b269..31479d54 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -7,7 +7,6 @@ import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle -import android.os.Environment import android.support.design.widget.FloatingActionButton import android.support.v4.content.FileProvider import android.view.View @@ -34,11 +33,8 @@ import com.pitchedapps.frost.utils.* import com.sothree.slidinguppanel.SlidingUpPanelLayout import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread -import timber.log.Timber import java.io.File import java.io.IOException -import java.text.SimpleDateFormat -import java.util.* /** * Created by Allan Wang on 2017-07-15. @@ -99,8 +95,8 @@ class ImageActivity : KauBaseActivity() { }) fab.setOnClickListener { fabAction.onClick(this) } photo.setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() { - override fun onImageLoadError(e: Exception) { - L.e(e, "Image load error") + override fun onImageLoadError(e: Exception?) { + e.logFrostAnswers("Image load error") imageCallback(null, false) } }) @@ -155,7 +151,7 @@ class ImageActivity : KauBaseActivity() { callback(null) } else { tempFilePath = photoFile.absolutePath - Timber.d("Temp image path $tempFilePath") + L.d("Temp image path $tempFilePath") // File created; proceed with request val photoURI = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", @@ -252,7 +248,7 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC } activity.startActivity(intent) } catch (e: Exception) { - L.e(e, "Image share failed"); + e.logFrostAnswers("Image share failed") activity.snackbar(R.string.image_share_failed) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt index 47c286fa..67f07635 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -12,6 +12,7 @@ import ca.allanwang.kau.utils.bindView import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.fadeOut import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener @@ -44,6 +45,7 @@ class LoginActivity : BaseActivity() { val profileObservable = SingleSubject.create<Boolean>() val usernameObservable = SingleSubject.create<String>() + lateinit var profileLoader: RequestManager // Helper to set and enable swipeRefresh var refresh: Boolean @@ -68,6 +70,7 @@ class LoginActivity : BaseActivity() { loadInfo(cookie) }) } + profileLoader = Glide.with(profile) } fun loadInfo(cookie: CookieModel) { @@ -78,8 +81,8 @@ class LoginActivity : BaseActivity() { (foundImage, name) -> refresh = false if (!foundImage) { - L.eThrow("Could not get profile photo; Invalid userId?") - L.i("-\t$cookie") + L.e("Could not get profile photo; Invalid userId?") + L.i(null, cookie.toString()) } textview.text = String.format(getString(R.string.welcome), name) textview.fadeIn() @@ -102,14 +105,14 @@ class LoginActivity : BaseActivity() { fun loadProfile(id: Long) { - Glide.with(profile).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> { + profileLoader.load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> { override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { profileObservable.onSuccess(true) return false } override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean { - if (e != null) L.e(e, "Profile loading exception") + e.logFrostAnswers( "Profile loading exception") profileObservable.onSuccess(false) return false } @@ -119,12 +122,4 @@ class LoginActivity : BaseActivity() { fun loadUsername(cookie: CookieModel) { cookie.fetchUsername { usernameObservable.onSuccess(it) } } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == 999) { - L.d("Result found for activity with result $resultCode") - L.d("Intent data ${data?.extras.toString()}") - } else - super.onActivityResult(requestCode, resultCode, data) - } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt index e8148b55..759be983 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -50,7 +50,7 @@ import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbTabs import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.facebook.FbCookie.switchUser -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL import com.pitchedapps.frost.fragments.WebFragment import com.pitchedapps.frost.utils.* @@ -60,6 +60,7 @@ import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import com.pitchedapps.frost.views.BadgedIcon import com.pitchedapps.frost.views.FrostViewPager import com.pitchedapps.frost.web.SearchWebView +import com.pitchedapps.frost.web.shouldLoadImages import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -160,14 +161,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, // } setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager)) onCreateBilling() - if (Prefs.installDate < 1501454310304 && Showcase.intro) - materialDialogThemed { - title(R.string.intro_title) - content(R.string.intro_desc) - positiveText(R.string.kau_yes) - negativeText(R.string.kau_no) - onPositive { _, _ -> launchIntroActivity(cookies()) } - } + setNetworkObserver { + connectivity -> + shouldLoadImages = !connectivity.isRoaming + } } fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { @@ -206,10 +203,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, tabsForEachView { _, view -> when (view.iicon) { - FbTab.FEED.icon -> view.badgeText = feed - FbTab.FRIENDS.icon -> view.badgeText = requests - FbTab.MESSAGES.icon -> view.badgeText = messages - FbTab.NOTIFICATIONS.icon -> view.badgeText = notifications + FbItem.FEED.icon -> view.badgeText = feed + FbItem.FRIENDS.icon -> view.badgeText = requests + FbItem.MESSAGES.icon -> view.badgeText = messages + FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications } } } @@ -230,10 +227,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, translucentStatusBar = false sliderBackgroundColor = navBg drawerHeader = accountHeader { + customViewRes = R.layout.material_drawer_header textColor = Prefs.iconColor.toLong() backgroundDrawable = ColorDrawable(navHeader) selectionSecondLineShown = false - paddingBelow = false cookies().forEach { (id, name) -> profile(name = name ?: "") { iconUrl = PROFILE_PICTURE_URL(id) @@ -261,7 +258,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, identifier = -4L } onProfileChanged { _, profile, current -> - if (current) launchWebOverlay(FbTab.PROFILE.url) + if (current) launchWebOverlay(FbItem.PROFILE.url) else when (profile.identifier) { -2L -> { val currentCookie = loadFbCookie(Prefs.userId) @@ -295,25 +292,25 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, } } drawerHeader.setActiveProfile(Prefs.userId) - primaryFrostItem(FbTab.FEED_MOST_RECENT) - primaryFrostItem(FbTab.FEED_TOP_STORIES) - primaryFrostItem(FbTab.ACTIVITY_LOG) + primaryFrostItem(FbItem.FEED_MOST_RECENT) + primaryFrostItem(FbItem.FEED_TOP_STORIES) + primaryFrostItem(FbItem.ACTIVITY_LOG) divider() - primaryFrostItem(FbTab.PHOTOS) - primaryFrostItem(FbTab.GROUPS) - primaryFrostItem(FbTab.FRIENDS) - primaryFrostItem(FbTab.PAGES) + primaryFrostItem(FbItem.PHOTOS) + primaryFrostItem(FbItem.GROUPS) + primaryFrostItem(FbItem.FRIENDS) + primaryFrostItem(FbItem.PAGES) divider() - primaryFrostItem(FbTab.EVENTS) - primaryFrostItem(FbTab.BIRTHDAYS) - primaryFrostItem(FbTab.ON_THIS_DAY) + primaryFrostItem(FbItem.EVENTS) + primaryFrostItem(FbItem.BIRTHDAYS) + primaryFrostItem(FbItem.ON_THIS_DAY) divider() - primaryFrostItem(FbTab.NOTES) - primaryFrostItem(FbTab.SAVED) + primaryFrostItem(FbItem.NOTES) + primaryFrostItem(FbItem.SAVED) } } - fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) { + fun Builder.primaryFrostItem(item: FbItem) = this.primaryItem(item.titleId) { iicon = item.icon iconColor = Prefs.textColor.toLong() textColor = Prefs.textColor.toLong() @@ -349,6 +346,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, override fun searchOverlayDispose() { hiddenSearchView?.dispose() hiddenSearchView = null + searchView?.unBind { launchWebOverlay(FbItem.SEARCH.url); true } searchView = null } @@ -369,10 +367,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, if (Prefs.searchBar) { if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this) if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { - textObserver = { - observable, _ -> - observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) } - } + textCallback = { query, _ -> runOnUiThread { hiddenSearchView?.query(query) } } foregroundColor = Prefs.textColor backgroundColor = Prefs.bgColor.withMinAlpha(200) openListener = { hiddenSearchView?.pauseLoad = false } @@ -380,8 +375,8 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, onItemClick = { _, key, _, _ -> launchWebOverlay(key) } } } else { - searchOverlayDispose() - menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true } + if (searchView != null) searchOverlayDispose() + else menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbItem.SEARCH.url); true } } return true } @@ -461,7 +456,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract, val currentFragment get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment - inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbTab>) : FragmentPagerAdapter(fm) { + inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbItem>) : FragmentPagerAdapter(fm) { override fun getItem(position: Int): Fragment { val fragment = WebFragment(pages[position], position) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt index 7cbbe4df..196aa461 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -6,16 +6,12 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem -import ca.allanwang.kau.about.kauLaunchAbout import ca.allanwang.kau.kpref.activity.CoreAttributeContract import ca.allanwang.kau.kpref.activity.KPrefActivity import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.kpref.activity.items.KPrefItemBase import ca.allanwang.kau.ui.views.RippleCanvas -import ca.allanwang.kau.utils.finishSlideOut -import ca.allanwang.kau.utils.setMenuIcons -import ca.allanwang.kau.utils.string -import ca.allanwang.kau.utils.tint +import ca.allanwang.kau.utils.* import ca.allanwang.kau.xml.showChangelog import com.mikepenz.community_material_typeface_library.CommunityMaterial import com.mikepenz.google_material_typeface_library.GoogleMaterial @@ -38,7 +34,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (!onActivityResultBilling(requestCode, resultCode, data)) super.onActivityResult(requestCode, resultCode, data) - adapter.notifyDataSetChanged() + reloadList() } override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = { @@ -67,6 +63,11 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { iicon = GoogleMaterial.Icon.gmd_notifications } + subItems(R.string.network, getNetworkPrefs()) { + descRes = R.string.network_desc + iicon = GoogleMaterial.Icon.gmd_network_cell + } + subItems(R.string.experimental, getExperimentalPrefs()) { descRes = R.string.experimental_desc iicon = CommunityMaterial.Icon.cmd_flask_outline @@ -81,7 +82,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { plainText(R.string.about_frost) { descRes = R.string.about_frost_desc iicon = GoogleMaterial.Icon.gmd_info - onClick = { _, _, _ -> kauLaunchAbout(AboutActivity::class.java); true } + onClick = { _, _, _ -> startActivityForResult(AboutActivity::class.java, 9, true); true } } plainText(R.string.replay_intro) { @@ -89,6 +90,12 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { onClick = { _, _, _ -> launchIntroActivity(cookies()); true } } + subItems(R.string.debug_frost, getDebugPrefs()) { + descRes = R.string.debug_frost_desc + iicon = CommunityMaterial.Icon.cmd_android_debug_bridge + visible = { Prefs.debugSettings } + } + if (BuildConfig.DEBUG) { checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it }) } @@ -130,8 +137,6 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_settings, menu) toolbar.tint(Prefs.iconColor) - toolbarTitle.textColor = Prefs.iconColor - toolbarTitle.invalidate() setMenuIcons(menu, Prefs.iconColor, R.id.action_email to GoogleMaterial.Icon.gmd_email, R.id.action_changelog to GoogleMaterial.Icon.gmd_info) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt index fd8a3677..f3d90bcc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt @@ -5,8 +5,6 @@ import android.content.Intent import android.net.Uri import android.webkit.ValueCallback import android.webkit.WebChromeClient -import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase -import ca.allanwang.kau.mediapicker.MediaType import ca.allanwang.kau.mediapicker.kauLaunchMediaPicker import ca.allanwang.kau.mediapicker.kauOnMediaPickerResult import com.pitchedapps.frost.activities.ImagePickerActivity diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt index 901ba02d..92cdf503 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt @@ -2,16 +2,18 @@ package com.pitchedapps.frost.dbflow import android.os.Parcel import android.os.Parcelable +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.pitchedapps.frost.facebook.FACEBOOK_COM -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.logFrostAnswers import com.raizlabs.android.dbflow.annotation.ConflictAction import com.raizlabs.android.dbflow.annotation.Database import com.raizlabs.android.dbflow.annotation.PrimaryKey import com.raizlabs.android.dbflow.annotation.Table import com.raizlabs.android.dbflow.kotlinextensions.* import com.raizlabs.android.dbflow.structure.BaseModel -import org.jetbrains.anko.doAsync +import io.reactivex.schedulers.Schedulers import org.jsoup.Jsoup import paperparcel.PaperParcel import java.net.UnknownHostException @@ -64,20 +66,22 @@ fun removeCookie(id: Long) { } fun CookieModel.fetchUsername(callback: (String) -> Unit) { - doAsync { + ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe { + yes, _ -> + if (!yes) return@subscribe callback("") var result = "" try { - result = Jsoup.connect(FbTab.PROFILE.url) + result = Jsoup.connect(FbItem.PROFILE.url) .cookie(FACEBOOK_COM, cookie) .get().title() L.d("Fetch username found", result) } catch (e: Exception) { if (e !is UnknownHostException) - L.e(e, "Fetch username failed") + e.logFrostAnswers("Fetch username failed") } finally { if (result.isBlank() && (name?.isNotBlank() ?: false)) { callback(name!!) - return@doAsync + return@subscribe } if (name != result) { name = result diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt index 69c7f3d5..4b2d3403 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt @@ -1,7 +1,7 @@ package com.pitchedapps.frost.dbflow import android.content.Context -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.defaultTabs import com.pitchedapps.frost.utils.L import com.raizlabs.android.dbflow.annotation.Database @@ -22,15 +22,15 @@ object FbTabsDb { } @Table(database = FbTabsDb::class, allFields = true) -data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbTab = FbTab.FEED) : BaseModel() +data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel() -fun loadFbTabs(): List<FbTab> { +fun loadFbTabs(): List<FbItem> { val tabs: List<FbTabModel>? = SQLite.select().from(FbTabModel::class).orderBy(FbTabModel_Table.position, true).queryList() if (tabs?.isNotEmpty() ?: false) return tabs!!.map { it.tab } L.d("No tabs; loading default") return defaultTabs() } -fun List<FbTab>.saveAsync(c: Context) { +fun List<FbItem>.saveAsync(c: Context) { mapIndexed { index, fbTab -> FbTabModel(index, fbTab) }.replace(c, FbTabsDb.NAME) }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbTab.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt index d1f0b046..a4736091 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbTab.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt @@ -10,7 +10,7 @@ import com.pitchedapps.frost.web.FrostWebViewClient import com.pitchedapps.frost.web.FrostWebViewClientMenu import com.pitchedapps.frost.web.FrostWebViewCore -enum class FbTab(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null) { +enum class FbItem(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null) { ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"), BIRTHDAYS(R.string.birthdays, GoogleMaterial.Icon.gmd_cake, "events/birthdays"), CHAT(R.string.chat, GoogleMaterial.Icon.gmd_chat, "buddylist"), @@ -36,4 +36,4 @@ enum class FbTab(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: Stri val url = "$FB_URL_BASE$relativeUrl" } -fun defaultTabs(): List<FbTab> = listOf(FbTab.FEED, FbTab.MESSAGES, FbTab.NOTIFICATIONS, FbTab.MENU) +fun defaultTabs(): List<FbItem> = listOf(FbItem.FEED, FbItem.MESSAGES, FbItem.NOTIFICATIONS, FbItem.MENU) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt index 7cd93d14..69b2ba41 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt @@ -33,7 +33,7 @@ class FbUrlFormatter(url: String) { if (cleanedUrl.startsWith("#!/")) cleanedUrl = cleanedUrl.substring(2) if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url - L.v("Formatted url from $url to $cleanedUrl") + L.v(null, "Formatted url from $url to $cleanedUrl") cleaned = cleanedUrl } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt index dfdfa027..f2bcc328 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt @@ -3,6 +3,7 @@ package com.pitchedapps.frost.facebook import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.saveFbCookie import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.logFrostAnswers import io.reactivex.subjects.SingleSubject import org.jsoup.Jsoup import kotlin.concurrent.thread @@ -16,12 +17,12 @@ object UsernameFetcher { thread { var name = "" try { - name = Jsoup.connect(FbTab.PROFILE.url) + name = Jsoup.connect(FbItem.PROFILE.url) .cookie(FACEBOOK_COM, data.cookie) .get().title() L.d("User name found", name) } catch (e: Exception) { - L.e(e, "User name fetching failed") + e.logFrostAnswers("User name fetching failed") } finally { data.name = name saveFbCookie(data) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt index 239f5842..920052f9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt @@ -8,7 +8,7 @@ import android.view.View import android.view.ViewGroup import ca.allanwang.kau.utils.withArguments import com.pitchedapps.frost.activities.MainActivity -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.FeedSort import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.web.FrostWebView @@ -30,15 +30,15 @@ class WebFragment : Fragment() { const val REQUEST_TEXT_ZOOM = 17 const val REQUEST_REFRESH = 99 - operator fun invoke(data: FbTab, position: Int) = WebFragment().withArguments( + operator fun invoke(data: FbItem, position: Int) = WebFragment().withArguments( ARG_URL to data.url, ARG_POSITION to position, ARG_URL_ENUM to when (data) { //If is feed, check if sorting method is specified - FbTab.FEED -> when (FeedSort(Prefs.feedSort)) { + FbItem.FEED -> when (FeedSort(Prefs.feedSort)) { FeedSort.DEFAULT -> data - FeedSort.MOST_RECENT -> FbTab.FEED_MOST_RECENT - FeedSort.TOP -> FbTab.FEED_TOP_STORIES + FeedSort.MOST_RECENT -> FbItem.FEED_MOST_RECENT + FeedSort.TOP -> FbItem.FEED_TOP_STORIES } else -> data }) @@ -47,7 +47,7 @@ class WebFragment : Fragment() { // val refresh: SwipeRefreshLayout by lazy { frostWebView.refresh } val web: FrostWebViewCore by lazy { frostWebView.web } val url: String by lazy { arguments.getString(ARG_URL) } - val urlEnum: FbTab by lazy { arguments.getSerializable(ARG_URL_ENUM) as FbTab } + val urlEnum: FbItem by lazy { arguments.getSerializable(ARG_URL_ENUM) as FbItem } val position: Int by lazy { arguments.getInt(ARG_POSITION) } lateinit var frostWebView: FrostWebView private var firstLoad = true diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt index 0992a9cb..733bc151 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt @@ -3,7 +3,10 @@ package com.pitchedapps.frost.injectors import android.graphics.Color import android.webkit.WebView import ca.allanwang.kau.utils.* +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs +import java.io.FileNotFoundException +import java.util.* /** * Created by Allan Wang on 2017-05-31. @@ -14,33 +17,38 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract { MATERIAL_LIGHT, MATERIAL_DARK, MATERIAL_AMOLED, MATERIAL_GLASS, CUSTOM, ROUND_ICONS("components") ; - var file = "${name.toLowerCase()}.compact.css" + var file = "${name.toLowerCase(Locale.CANADA)}.compact.css" var injector: JsInjector? = null override fun inject(webView: WebView, callback: ((String) -> Unit)?) { if (injector == null) { - var content = webView.context.assets.open("css/$folder/$file").bufferedReader().use { it.readText() } - if (this == CUSTOM) { - var bbt = Prefs.bgColor - val bt: String - if (Color.alpha(bbt) == 255) { - bbt = bbt.adjustAlpha(0.2f).colorToForeground(0.35f) - bt = Prefs.bgColor.toRgbaString() - } else { - bbt = bbt.adjustAlpha(0.05f).colorToForeground(0.5f) - bt = "transparent" + try { + var content = webView.context.assets.open("css/$folder/$file").bufferedReader().use { it.readText() } + if (this == CUSTOM) { + var bbt = Prefs.bgColor + val bt: String + if (Color.alpha(bbt) == 255) { + bbt = bbt.adjustAlpha(0.2f).colorToForeground(0.35f) + bt = Prefs.bgColor.toRgbaString() + } else { + bbt = bbt.adjustAlpha(0.05f).colorToForeground(0.5f) + bt = "transparent" + } + content = content + .replace("\$T\$", Prefs.textColor.toRgbaString()) + .replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString()) + .replace("\$B\$", Prefs.bgColor.toRgbaString()) + .replace("\$BT\$", bt) + .replace("\$BBT\$", bbt.toRgbaString()) + .replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString()) + .replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString()) + .replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString()) } - content = content - .replace("\$T\$", Prefs.textColor.toRgbaString()) - .replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString()) - .replace("\$B\$", Prefs.bgColor.toRgbaString()) - .replace("\$BT\$", bt) - .replace("\$BBT\$", bbt.toRgbaString()) - .replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString()) - .replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString()) - .replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString()) + injector = JsBuilder().css(content).build() + } catch (e: FileNotFoundException) { + L.e(e, "CssAssets file not found") + injector = JsInjector(JsActions.EMPTY.function) } - injector = JsBuilder().css(content).build() } injector!!.inject(webView, callback) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt index fae1846b..3fa03bcc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt @@ -14,7 +14,8 @@ enum class JsActions(body: String) : InjectorContract { * see [com.pitchedapps.frost.web.FrostJSI.loadLogin] */ LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"), - BASE_HREF("document.write(\"<base href='$FB_URL_BASE'/>\");"), + BASE_HREF("""document.write("<base href='$FB_URL_BASE'/>");"""), + FETCH_BODY("""setTimeout(function(){var e=document.querySelector("main");e||(e=document.querySelector("body")),Frost.handleHtml(e.outerHTML)},1e2);"""), /** * Used as a pseudoinjector for maybe functions */ diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt index d2201c52..27b0f92a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt @@ -1,6 +1,9 @@ package com.pitchedapps.frost.injectors import android.webkit.WebView +import com.pitchedapps.frost.utils.L +import java.io.FileNotFoundException +import java.util.* /** * Created by Allan Wang on 2017-05-31. @@ -11,13 +14,18 @@ enum class JsAssets : InjectorContract { MENU, CLICK_A, CONTEXT_A, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER, NOTIF_MSG ; - var file = "${name.toLowerCase()}.min.js" + var file = "${name.toLowerCase(Locale.CANADA)}.min.js" var injector: JsInjector? = null override fun inject(webView: WebView, callback: ((String) -> Unit)?) { if (injector == null) { - val content = webView.context.assets.open("js/$file").bufferedReader().use { it.readText() } - injector = JsBuilder().js(content).build() + try { + val content = webView.context.assets.open("js/$file").bufferedReader().use { it.readText() } + injector = JsBuilder().js(content).build() + } catch (e: FileNotFoundException) { + L.e(e, "JsAssets file not found") + injector = JsInjector(JsActions.EMPTY.function) + } } injector!!.inject(webView, callback) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index 2453d3b0..d3dfe79c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -21,7 +21,6 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.dbflow.fetchUsername import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L @@ -31,6 +30,12 @@ import org.jetbrains.anko.runOnUiThread /** * Created by Allan Wang on 2017-07-08. + * + * Logic for build notifications, scheduling notifications, and showing notifications + */ + +/** + * Wrap the default builder with our icon and accent color */ val Context.frostNotification: NotificationCompat.Builder get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply { @@ -39,6 +44,9 @@ val Context.frostNotification: NotificationCompat.Builder color = color(R.color.frost_notification_accent) } +/** + * Assign global changes to the notification after it is built + */ @Suppress("DEPRECATION") //The update feature is for Android O and seems to still be in beta fun Notification.frostConfig() = apply { @@ -54,6 +62,7 @@ val NotificationCompat.Builder.withBigText: NotificationCompat.BigTextStyle * Created by Allan Wang on 2017-07-08. * * Custom target to set the content view and update a given notification + * 40dp is the size of the right avatar */ class FrostNotificationTarget(val context: Context, val notifId: Int, @@ -67,6 +76,9 @@ class FrostNotificationTarget(val context: Context, } } +internal const val FROST_NOTIFICATION_GROUP = "frost" +internal const val FROST_MESSAGE_NOTIFICATION_GROUP = "frost_im" + /** * Notification data holder */ @@ -77,39 +89,35 @@ data class NotificationContent(val data: CookieModel, val text: String, val timestamp: Long, val profileUrl: String) { - fun createNotification(context: Context, verifiedUser: Boolean = false) { - //in case we haven't found the name, we will try one more time before passing the notification - if (!verifiedUser && data.name?.isBlank() ?: true) { - data.fetchUsername { - data.name = it - createNotification(context, true) - } - } else { - val intent = Intent(context, FrostWebActivity::class.java) - intent.data = Uri.parse(href.formattedFbUrl) - intent.putExtra(ARG_USER_ID, data.id) - val group = "frost_${data.id}" - val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) - val notifBuilder = context.frostNotification - .setContentTitle(title ?: context.string(R.string.frost_name)) - .setContentText(text) - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_SOCIAL) - .setSubText(data.name) - .setGroup(group) - - if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) - L.v("Notif load $this") - NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig()) - - if (profileUrl.isNotBlank()) { - context.runOnUiThread { - Glide.with(context) - .asBitmap() - .load(profileUrl) - .withRoundIcon() - .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) - } + fun createNotification(context: Context) = createNotification(context, FROST_NOTIFICATION_GROUP) + + fun createMessageNotification(context: Context) = createNotification(context, FROST_MESSAGE_NOTIFICATION_GROUP) + + private fun createNotification(context: Context, groupPrefix: String) { + val intent = Intent(context, FrostWebActivity::class.java) + intent.data = Uri.parse(href.formattedFbUrl) + intent.putExtra(ARG_USER_ID, data.id) + val group = "${groupPrefix}_${data.id}" + val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) + val notifBuilder = context.frostNotification + .setContentTitle(title ?: context.string(R.string.frost_name)) + .setContentText(text) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) + .setSubText(data.name) + .setGroup(group) + + if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) + L.v("Notif load", this.toString()) + NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig()) + + if (profileUrl.isNotBlank()) { + context.runOnUiThread { + Glide.with(context) + .asBitmap() + .load(profileUrl) + .withRoundIcon() + .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index fe7758cc..5859a306 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -1,26 +1,33 @@ package com.pitchedapps.frost.services +import android.app.Notification +import android.app.PendingIntent import android.app.job.JobParameters import android.app.job.JobService import android.content.Context +import android.content.Intent +import android.net.Uri import android.support.v4.app.NotificationManagerCompat import ca.allanwang.kau.utils.string import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R +import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.lastNotificationTime import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.facebook.FACEBOOK_COM -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.injectors.JsAssets +import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom -import com.pitchedapps.frost.web.MessageWebView +import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor +import io.reactivex.schedulers.Schedulers import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.concurrent.Future @@ -30,6 +37,9 @@ import java.util.concurrent.Future * * Service to manage notifications * Will periodically check through all accounts in the db and send notifications when appropriate + * + * Note that general notifications are parsed directly with Jsoup, + * but instant messages are done so with a headless webview as it is generated from JS */ class NotificationService : JobService() { @@ -58,7 +68,7 @@ class NotificationService : JobService() { fun finish(params: JobParameters?) { val time = System.currentTimeMillis() - startTime - L.d("Notification service has finished in $time ms") + L.i("Notification service has finished in $time ms") frostAnswersCustom("NotificationTime", "Type" to "Service", "IM Included" to Prefs.notificationsInstantMessages, @@ -68,8 +78,8 @@ class NotificationService : JobService() { future = null } - override fun onStartJob(params: JobParameters?): Boolean { + L.i("Fetching notifications") future = doAsync { if (Prefs.notificationAllAccounts) { val cookies = loadFbCookiesSync() @@ -83,9 +93,15 @@ class NotificationService : JobService() { L.d("Finished main notifications") if (Prefs.notificationsInstantMessages) { val currentCookie = loadFbCookie(Prefs.userId) - if (currentCookie != null) - uiThread { MessageWebView(this@NotificationService, params, currentCookie) } - } else finish(params) + if (currentCookie != null) { + fetchMessageNotifications(currentCookie) { + L.i("Notif IM fetching finished ${if (it) "succesfully" else "unsuccessfully"}") + finish(params) + } + return@doAsync + } + } + finish(params) } return true } @@ -95,13 +111,21 @@ class NotificationService : JobService() { return null } + /* + * ---------------------------------------------------------------- + * General notification logic. + * Fetch notifications -> Filter new ones -> Parse notifications -> + * Show notifications -> Show group notification + * ---------------------------------------------------------------- + */ + fun fetchGeneralNotifications(data: CookieModel) { - L.i("Notif fetch for $data") - val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() + L.d("Notif fetch", data.toString()) + val doc = Jsoup.connect(FbItem.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() //aclb for unread, acw for read val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb") var notifCount = 0 -// val prevLatestEpoch = 1498931565L // for testing + //val prevLatestEpoch = 1498931565L // for testing val prevNotifTime = lastNotificationTime(data.id) val prevLatestEpoch = prevNotifTime.epoch L.v("Notif Prev Latest Epoch $prevLatestEpoch") @@ -122,7 +146,6 @@ class NotificationService : JobService() { summaryNotification(data.id, notifCount) } - fun parseNotification(data: CookieModel, element: Element): NotificationContent? { val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag") val abbr = element.getElementsByTag("abbr") @@ -134,13 +157,36 @@ class NotificationService : JobService() { if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out //fetch profpic val p = element.select("i.img[style*=url]") - val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: "" + val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: "" return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl) } - fun fetchMessageNotifications(data: CookieModel, content: String) { - L.i("Notif IM fetch for $data") - val doc = Jsoup.parseBodyFragment(content) + fun summaryNotification(userId: Long, count: Int) + = summaryNotification(userId, count, R.string.notifications, FbItem.NOTIFICATIONS.url, FROST_NOTIFICATION_GROUP) + + /* + * ---------------------------------------------------------------- + * Instant message notification logic. + * Fetch notifications -> Filter new ones -> Parse notifications -> + * Show notifications -> Show group notification + * ---------------------------------------------------------------- + */ + + inline fun fetchMessageNotifications(data: CookieModel, crossinline callback: (success: Boolean) -> Unit) { + launchHeadlessHtmlExtractor(FbItem.MESSAGES.url, JsAssets.NOTIF_MSG) { + it.observeOn(Schedulers.newThread()).subscribe { + (html, errorRes) -> + L.d("Notf IM html received") + if (errorRes != -1) return@subscribe callback(false) + fetchMessageNotifications(data, html) + callback(true) + } + } + } + + fun fetchMessageNotifications(data: CookieModel, html: String) { + L.d("Notif IM fetch", data.toString()) + val doc = Jsoup.parseBodyFragment(html) val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb") var notifCount = 0 val prevNotifTime = lastNotificationTime(data.id) @@ -152,7 +198,7 @@ class NotificationService : JobService() { val notif = parseMessageNotification(data, elem) ?: return@unread L.v("Notif im timestamp ${notif.timestamp}") if (notif.timestamp <= prevLatestEpoch) return@unread - notif.createNotification(this@NotificationService) + notif.createMessageNotification(this@NotificationService) if (notif.timestamp > newLatestEpoch) newLatestEpoch = notif.timestamp notifCount++ @@ -160,7 +206,7 @@ class NotificationService : JobService() { if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") frostAnswersCustom("Notifications", "Type" to "Message", "Count" to notifCount) - summaryNotification(data.id, notifCount) + summaryMessageNotification(data.id, notifCount) } fun parseMessageNotification(data: CookieModel, element: Element): NotificationContent? { @@ -174,11 +220,14 @@ class NotificationService : JobService() { if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out //fetch convo pic val p = element.select("i.img[style*=url]") - val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: "" - L.v("url ${a.attr("href")}") - return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl.formattedFbUrl) + val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: "" + L.v("url", a.attr("href")) + return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl) } + fun summaryMessageNotification(userId: Long, count: Int) + = summaryNotification(userId, count, R.string.messages, FbItem.MESSAGES.url, FROST_MESSAGE_NOTIFICATION_GROUP) + private fun Context.debugNotification(text: String) { if (!BuildConfig.DEBUG) return val notifBuilder = frostNotification @@ -187,15 +236,21 @@ class NotificationService : JobService() { NotificationManagerCompat.from(this).notify(999, notifBuilder.build().frostConfig()) } - fun summaryNotification(userId: Long, count: Int) { + private fun summaryNotification(userId: Long, count: Int, contentRes: Int, pendingUrl: String, groupPrefix: String) { if (count <= 1) return + val intent = Intent(this, FrostWebActivity::class.java) + intent.data = Uri.parse(pendingUrl) + intent.putExtra(ARG_USER_ID, userId) + val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) val notifBuilder = frostNotification .setContentTitle(string(R.string.frost_name)) - .setContentText("$count notifications") - .setGroup("frost_$userId") + .setContentText("$count ${string(contentRes)}") + .setGroup("${groupPrefix}_$userId") .setGroupSummary(true) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) - NotificationManagerCompat.from(this).notify("frost_$userId", userId.toInt(), notifBuilder.build().frostConfig()) + NotificationManagerCompat.from(this).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build().frostConfig()) } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt index 52f7412f..9e53889e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt @@ -13,6 +13,7 @@ import com.pitchedapps.frost.utils.Prefs */ class UpdateReceiver : BroadcastReceiver() { + //todo check action warning override fun onReceive(context: Context, intent: Intent) { L.d("Frost has updated") context.scheduleNotifications(Prefs.notificationFreq) //Update notifications diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt index 2af67602..bf524835 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt @@ -31,6 +31,10 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = { descRes = R.string.search_bar_desc } + checkbox(R.string.force_message_bottom, { Prefs.messageScrollToBottom }, { Prefs.messageScrollToBottom = it }) { + descRes = R.string.force_message_bottom_desc + } + checkbox(R.string.exit_confirmation, { Prefs.exitConfirmation }, { Prefs.exitConfirmation = it }) { descRes = R.string.exit_confirmation_desc } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt new file mode 100644 index 00000000..f8dc81d1 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt @@ -0,0 +1,151 @@ +package com.pitchedapps.frost.settings + +import android.content.Context +import android.support.annotation.UiThread +import ca.allanwang.kau.email.sendEmail +import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import ca.allanwang.kau.utils.string +import com.afollestad.materialdialogs.MaterialDialog +import com.pitchedapps.frost.R +import com.pitchedapps.frost.activities.SettingsActivity +import com.pitchedapps.frost.facebook.FACEBOOK_COM +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.FbItem +import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.InjectorContract +import com.pitchedapps.frost.injectors.JsActions +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.cleanHtml +import com.pitchedapps.frost.utils.materialDialogThemed +import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor +import com.pitchedapps.frost.web.query +import io.reactivex.disposables.Disposable +import org.jetbrains.anko.AnkoAsyncContext +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.runOnUiThread +import org.jetbrains.anko.uiThread +import org.jsoup.Jsoup +import org.jsoup.nodes.Document + +/** + * Created by Allan Wang on 2017-06-30. + * + * A sub pref section that is enabled through a hidden preference + * Each category will load a page, extract the contents, remove private info, and create a report + */ +fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = { + + plainText(R.string.experimental_disclaimer) { + descRes = R.string.debug_disclaimer_info + } + + Debugger.values().forEach { + plainText(it.data.titleId) { + iicon = it.data.icon + onClick = { itemView, _, _ -> it.debug(itemView.context); true } + } + } + +} + +private enum class Debugger(val data: FbItem, val injector: InjectorContract?, vararg query: String) { + NOTIFICATIONS(FbItem.NOTIFICATIONS, null, "#notifications_list"), + SEARCH(FbItem.SEARCH, JsActions.FETCH_BODY); + + val query = if (query.isNotEmpty()) arrayOf(*query, "#root", "main", "body") else emptyArray() + + fun debug(context: Context) { + val dialog = context.materialDialogThemed { + title("Debugging") + progress(true, 0) + canceledOnTouchOutside(false) + positiveText(R.string.kau_cancel) + onPositive { dialog, _ -> dialog.cancel() } + } + if (injector != null) dialog.extractHtml(injector) + else dialog.debugAsync { + loadJsoup() + } + } + + fun MaterialDialog.debugAsync(task: AnkoAsyncContext<MaterialDialog>.() -> Unit) { + doAsync({ t: Throwable -> + val msg = t.message + L.e("Debugger failed: $msg") + context.runOnUiThread { + cancel() + context.materialDialogThemed { + title(R.string.debug_incomplete) + if (msg != null) content(msg) + } + } + }, task) + } + + /** + * Wait for html to be returned from headless webview + * + * from [debug] to [simplifyJsoup] if [query] is not empty, or [createReport] otherwise + */ + @UiThread + private fun MaterialDialog.extractHtml(injector: InjectorContract) { + setContent("Fetching webpage") + var disposable: Disposable? = null + setOnCancelListener { disposable?.dispose() } + context.launchHeadlessHtmlExtractor(data.url, injector) { + disposable = it.subscribe { + (html, errorRes) -> + debugAsync { + if (errorRes == -1) { + L.i("Debug report successful", html) + if (query.isNotEmpty()) simplifyJsoup(Jsoup.parseBodyFragment(html)) + else createReport(html) + } else { + throw Throwable(context.string(errorRes)) + } + } + } + } + } + + /** + * Get data directly from the link and search for our queries, returning the outerHTML + * of the first query found + * + * from [debug] to [simplifyJsoup] + */ + private fun AnkoAsyncContext<MaterialDialog>.loadJsoup() { + uiThread { + it.setContent("Load Jsoup") + it.setOnCancelListener(null) + it.debugAsync { + val connection = Jsoup.connect(data.url).cookie(FACEBOOK_COM, FbCookie.webCookie).userAgent(USER_AGENT_BASIC) + val doc = connection.get() + simplifyJsoup(doc) + } + } + } + + /** + * Takes snippet of given document that matches the first query in the [query] items + * before sending it to [createReport] + */ + private fun AnkoAsyncContext<MaterialDialog>.simplifyJsoup(doc: Document) { + weakRef.get() ?: return + val q = query.first { doc.select(it).isNotEmpty() } + createReport(doc.select(q).outerHtml()) + } + + private fun AnkoAsyncContext<MaterialDialog>.createReport(html: String) { + val cleanHtml = html.cleanHtml() + uiThread { + val c = it.context + it.dismiss() + c.sendEmail(c.string(R.string.dev_email), + "${c.string(R.string.debug_report_email_title)} $name") { + addItem("Query List", query.contentToString()) + footer = cleanHtml + } + } + } +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 594cbe01..a1b459fb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -1,9 +1,11 @@ package com.pitchedapps.frost.settings import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import ca.allanwang.kau.logging.KL import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.activities.SettingsActivity +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Showcase @@ -22,13 +24,17 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { // Experimental content starts here ------------------ - checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it }) { - descRes = R.string.notification_messages_desc - } + // Experimental content ends here -------------------- - checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) { + checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { + Prefs.verboseLogging = it + KL.debug(it) + KL.showPrivateText = false + L.debug(it) + KL.showPrivateText = false + }) { descRes = R.string.verbose_logging_desc } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt new file mode 100644 index 00000000..30ab2579 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt @@ -0,0 +1,17 @@ +package com.pitchedapps.frost.settings + +import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import com.pitchedapps.frost.R +import com.pitchedapps.frost.activities.SettingsActivity +import com.pitchedapps.frost.utils.Prefs + +/** + * Created by Allan Wang on 2017-08-08. + */ +fun SettingsActivity.getNetworkPrefs(): KPrefAdapterBuilder.() -> Unit = { + + checkbox(R.string.network_media_on_metered, { Prefs.loadMediaOnMeteredNetwork }, { Prefs.loadMediaOnMeteredNetwork = it }) { + descRes = R.string.network_media_on_metered_desc + } + +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt index a5aa84d3..b1e5015f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -55,6 +55,10 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { descRes = R.string.notification_all_accounts_desc } + checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it }) { + descRes = R.string.notification_messages_desc + } + checkbox(R.string.notification_sound, { Prefs.notificationSound }, { Prefs.notificationSound = it }) checkbox(R.string.notification_vibrate, { Prefs.notificationVibrate }, { Prefs.notificationVibrate = it }) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt new file mode 100644 index 00000000..da8672f4 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt @@ -0,0 +1,34 @@ +package com.pitchedapps.frost.utils + +import org.jsoup.Jsoup +import org.jsoup.nodes.Attribute +import org.jsoup.nodes.Element +import org.jsoup.safety.Whitelist + +/** + * Created by Allan Wang on 2017-08-10. + * + * Parses html with Jsoup and cleans the data, emitting just the frame containing debugging info + * + * Removes text, removes unnecessary nodes + */ +fun String.cleanHtml() = cleanText().cleanJsoup() + +internal fun String.cleanText(): String = replace(Regex(">(?s).+?<"), "><") + +internal fun String.cleanJsoup(): String = Jsoup.clean(this, PrivacyWhitelist()) + +class PrivacyWhitelist : Whitelist() { + + val blacklistAttrs = arrayOf("style", "aria-label", "rel") + val blacklistTags = arrayOf("body", "html", "head", "i", "b", "u", "style", "script", + "br", "p", "span", "ul", "ol", "li") + + override fun isSafeAttribute(tagName: String, el: Element, attr: Attribute): Boolean { + val key = attr.key + if (key == "href") attr.setValue("-") + return key !in blacklistAttrs + } + + override fun isSafeTag(tag: String) = tag !in blacklistTags +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt index 16a3d2ae..d5c1a6fb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt @@ -1,9 +1,8 @@ package com.pitchedapps.frost.utils -import android.util.Log -import ca.allanwang.kau.logging.TimberLogger +import ca.allanwang.kau.logging.KauLogger import com.crashlytics.android.Crashlytics -import timber.log.Timber +import com.pitchedapps.frost.BuildConfig /** @@ -16,25 +15,15 @@ import timber.log.Timber * Debug and Error logs must not reveal person info * Person info logs can be marked as info or verbose */ -object L : TimberLogger("Frost") { +object L : KauLogger("Frost") { - /** - * Helper function to separate private info - */ - fun d(tag: String, personal: String?) { - L.d(tag) - L.i("-\t$personal") - } -} - -internal class CrashReportingTree : Timber.Tree() { - override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) { - when (priority) { - Log.VERBOSE, Log.INFO -> return - Log.DEBUG -> if (!Prefs.verboseLogging) return + override fun logImpl(priority: Int, message: String?, privateMessage: String?, t: Throwable?) { + if (BuildConfig.DEBUG) { + super.logImpl(priority, message, privateMessage, t) + } else { + if (message != null) + Crashlytics.log(priority, "Frost", message) + if (t != null) Crashlytics.logException(t) } - if (message != null) - Crashlytics.log(priority, "Frost", message) - if (t != null) Crashlytics.logException(t) } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt index b053b9dd..9b8064a4 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -97,8 +97,7 @@ object Prefs : KPref() { var notificationAllAccounts: Boolean by kpref("notification_all_accounts", true) - //todo remove from experimental once stabilized - var notificationsInstantMessages: Boolean by kpref("notification_im", Showcase.experimentalDefault) + var notificationsInstantMessages: Boolean by kpref("notification_im", false) var notificationVibrate: Boolean by kpref("notification_vibrate", true) @@ -106,6 +105,8 @@ object Prefs : KPref() { var notificationLights: Boolean by kpref("notification_lights", true) + var messageScrollToBottom: Boolean by kpref("message_scroll_to_bottom", false) + /** * Cache like value to determine if user has or had pro * In most cases, [com.pitchedapps.frost.utils.iab.IS_FROST_PRO] should be looked at instead @@ -128,4 +129,8 @@ object Prefs : KPref() { var viewpagerSwipe: Boolean by kpref("viewpager_swipe", true) + var loadMediaOnMeteredNetwork: Boolean by kpref("media_on_metered_network", true) + + var debugSettings: Boolean by kpref("debug_settings", false) + } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 496a6b5b..e79816f3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -25,7 +25,7 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.* import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FACEBOOK_COM -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.formattedFbUrl import java.io.IOException import java.util.* @@ -56,8 +56,8 @@ fun Activity.cookies(): ArrayList<CookieModel> { fun Context.launchWebOverlay(url: String) { val argUrl = url.formattedFbUrl - L.v("Launch received $url") - L.i("Launch web overlay: $argUrl") + L.v("Launch received", url) + L.i("Launch web overlay", argUrl) startActivity(WebOverlayActivity::class.java, false, intentBuilder = { putExtra(ARG_URL, argUrl) }) @@ -74,7 +74,7 @@ fun Activity.launchIntroActivity(cookieList: ArrayList<CookieModel>) = launchNewTask(IntroActivity::class.java, cookieList, true) fun WebOverlayActivity.url(): String { - return intent.extras?.getString(ARG_URL) ?: FbTab.FEED.url + return intent.extras?.getString(ARG_URL) ?: FbItem.FEED.url } fun Context.materialDialogThemed(action: MaterialDialog.Builder.() -> Unit): MaterialDialog { @@ -132,6 +132,15 @@ fun frostAnswersCustom(name: String, vararg events: Pair<String, Any>) { } } +/** + * Helper method to quietly keep track of throwable issues + */ +fun Throwable?.logFrostAnswers(text: String) { + val msg = if (this == null) text else "$text: $message" + L.e(msg) + frostAnswersCustom("Errors", "text" to text, "message" to (this?.message ?: "NA")) +} + fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) { Snackbar.make(this, text, Snackbar.LENGTH_LONG).apply { builder() diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt index bad7f8fd..7f6e8a6d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt @@ -9,6 +9,13 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswers +import com.pitchedapps.frost.utils.logFrostAnswers +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.onComplete +import org.jetbrains.anko.uiThread +import java.lang.ref.WeakReference +import java.math.BigDecimal +import java.util.* /** * Created by Allan Wang on 2017-07-22. @@ -33,31 +40,48 @@ interface FrostBilling : BillingProcessor.IBillingHandler { abstract class IABBinder : FrostBilling { var bp: BillingProcessor? = null - var activity: Activity? = null - - override fun Activity.onCreateBilling() { - activity = this - bp = BillingProcessor.newBillingProcessor(this, PUBLIC_BILLING_KEY, this@IABBinder) - bp?.initialize() + lateinit var activityRef: WeakReference<Activity> + val activity + get() = activityRef.get() + + override final fun Activity.onCreateBilling() { + activityRef = WeakReference(this) + doAsync { + bp = BillingProcessor.newBillingProcessor(this@onCreateBilling, PUBLIC_BILLING_KEY, this@IABBinder) + bp?.initialize() + } } override fun onDestroyBilling() { + activityRef.clear() bp?.release() bp = null - activity = null } - override fun onBillingInitialized() = L.d("IAB initialized") + override fun onBillingInitialized() = L.i("IAB initialized") override fun onPurchaseHistoryRestored() = L.d("IAB restored") override fun onProductPurchased(productId: String, details: TransactionDetails?) { - L.d("IAB $productId purchased") - frostAnswers { - logPurchase(PurchaseEvent() - .putItemId(productId) - .putSuccess(true) - ) + bp.doAsync { + L.i("IAB $productId purchased") + val listing = weakRef.get()?.getPurchaseListingDetails(productId) ?: return@doAsync + val currency = try { + Currency.getInstance(listing.currency) + } catch (e: Exception) { + null + } + frostAnswers { + logPurchase(PurchaseEvent().apply { + putItemId(productId) + putSuccess(true) + if (currency != null) { + putCurrency(Currency.getInstance(Locale.getDefault())) + putItemType(productId) + putItemPrice(BigDecimal.valueOf(listing.priceValue)) + } + }) + } } } @@ -67,13 +91,14 @@ abstract class IABBinder : FrostBilling { .putCustomAttribute("result", errorCode.toString()) .putSuccess(false)) } - L.e(error, "IAB error $errorCode") + error.logFrostAnswers("IAB error $errorCode") } override fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean = bp?.handleActivityResult(requestCode, resultCode, data) ?: false override fun purchasePro() { + val bp = this.bp if (bp == null) { frostAnswers { logPurchase(PurchaseEvent() @@ -83,10 +108,12 @@ abstract class IABBinder : FrostBilling { L.eThrow("IAB null bp on purchase attempt") return } - if (!(bp?.isOneTimePurchaseSupported ?: false)) - activity?.playStorePurchaseUnsupported() + val a = activity ?: return + + if (!BillingProcessor.isIabServiceAvailable(a) || !bp.isOneTimePurchaseSupported) + a.playStorePurchaseUnsupported() else - bp?.purchase(activity, FROST_PRO) + bp.purchase(a, FROST_PRO) } } @@ -107,15 +134,18 @@ class IABSettings : IABBinder() { * Attempts to get pro, or launch purchase flow if user doesn't have it */ override fun restorePurchases() { - if (bp == null) return - val load = bp?.loadOwnedPurchasesFromGoogle() ?: return - L.d("IAB settings load from google $load") - if (!(bp?.isPurchased(FROST_PRO) ?: return)) { - if (Prefs.pro) activity.playStoreNoLongerPro() - else purchasePro() - } else { - if (!Prefs.pro) activity.playStoreFoundPro() - else activity?.purchaseRestored() + bp.doAsync { + val load = weakRef.get()?.loadOwnedPurchasesFromGoogle() ?: return@doAsync + L.d("IAB settings load from google $load") + uiThread { + if (!(weakRef.get()?.isPurchased(FROST_PRO) ?: return@uiThread)) { + if (Prefs.pro) activity.playStoreNoLongerPro() + else purchasePro() + } else { + if (!Prefs.pro) activity.playStoreFoundPro() + else activity?.purchaseRestored() + } + } } } } @@ -142,13 +172,17 @@ class IABMain : IABBinder() { override fun restorePurchases() { if (restored || bp == null) return restored = true - val load = bp?.loadOwnedPurchasesFromGoogle() ?: false - L.d("IAB main load from google $load") - if (!(bp?.isPurchased(FROST_PRO) ?: false)) { - if (Prefs.pro) activity.playStoreNoLongerPro() - } else { - if (!Prefs.pro) activity.playStoreFoundPro() + bp.doAsync { + val load = weakRef.get()?.loadOwnedPurchasesFromGoogle() ?: false + L.d("IAB main load from google $load") + onComplete { + if (!(weakRef.get()?.isPurchased(FROST_PRO) ?: false)) { + if (Prefs.pro) activity.playStoreNoLongerPro() + } else { + if (!Prefs.pro) activity.playStoreFoundPro() + } + onDestroyBilling() + } } - onDestroyBilling() } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt index df0f04fd..e997731b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt @@ -2,6 +2,7 @@ package com.pitchedapps.frost.utils.iab import android.app.Activity import ca.allanwang.kau.utils.restart +import ca.allanwang.kau.utils.startLink import ca.allanwang.kau.utils.startPlayStoreLink import ca.allanwang.kau.utils.string import com.crashlytics.android.answers.PurchaseEvent @@ -69,9 +70,11 @@ fun Activity.playStorePurchaseUnsupported() { materialDialogThemed { title(R.string.uh_oh) content(R.string.play_store_unsupported) - positiveText(R.string.kau_ok) - neutralText(R.string.kau_play_store) - onNeutral { _, _ -> startPlayStoreLink(R.string.play_store_package_id) } + negativeText(R.string.kau_close) + positiveText(R.string.kau_play_store) + neutralText(R.string.paypal) + onPositive { _, _ -> startPlayStoreLink(R.string.play_store_package_id) } + onNeutral { _, _ -> startLink(string(R.string.dev_paypal)) } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt index 6bc27256..61711092 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt @@ -34,15 +34,9 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { val activityContract = (webCore.context as? ActivityWebContract) val context = webCore.context!! - companion object { - val consoleBlacklist = setOf( - "edge-chat" - ) - } - override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { if (consoleBlacklist.any { consoleMessage.message().contains(it) }) return true - L.i("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") + L.d("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") return true } @@ -63,10 +57,10 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { } override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { - L.d("Requesting geolocation") + L.i("Requesting geolocation") context.kauRequestPermissions(PERMISSION_ACCESS_FINE_LOCATION) { granted, _ -> - L.d("Geolocation response received; ${if (granted) "granted" else "denied"}") + L.i("Geolocation response received; ${if (granted) "granted" else "denied"}") callback(origin, granted, true) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 018ad737..f24a7a51 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -81,12 +81,14 @@ class FrostJSI(val webView: FrostWebViewCore) { } @JavascriptInterface - fun handleHtml(html: String) { + fun handleHtml(html: String?) { + html ?: return webView.post { webView.frostWebClient.handleHtml(html) } } @JavascriptInterface - fun handleHeader(html: String) { + fun handleHeader(html: String?) { + html ?: return headerObservable?.onNext(html) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt index 3f2891d0..1a907f7f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -5,6 +5,7 @@ import android.webkit.WebResourceResponse import android.webkit.WebView import ca.allanwang.kau.utils.use import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs import okhttp3.HttpUrl import java.io.ByteArrayInputStream @@ -15,17 +16,17 @@ import java.io.ByteArrayInputStream * Handler to decide when a request should be done by us * This is the crux of Frost's optimizations for the web browser */ -val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } +private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } //these hosts will redirect to a blank resource -val blacklistHost: Set<String> by lazy { +private val blacklistHost: Set<String> by lazy { setOf( "edge-chat.facebook.com" ) } //these hosts will return null and skip logging -val whitelistHost: Set<String> by lazy { +private val whitelistHost: Set<String> by lazy { setOf( "static.xx.fbcdn.net", "m.facebook.com", @@ -35,13 +36,13 @@ val whitelistHost: Set<String> by lazy { //these hosts will skip ad inspection //this list does not have to include anything from the two above -val adWhitelistHost: Set<String> by lazy { +private val adWhitelistHost: Set<String> by lazy { setOf( "scontent-sea1-1.xx.fbcdn.net" ) } -var adblock: Set<String>? = null +private var adblock: Set<String>? = null fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null @@ -53,7 +54,8 @@ fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): Web if (adblock == null) adblock = view.context.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() } if (adblock?.any { url.contains(it) } ?: false) return blankResource } - L.v("Intercept Request ${host} ${url}") + if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource + L.v("Intercept Request", "$host $url") return null } @@ -64,16 +66,25 @@ fun WebResourceRequest.query(action: (url: String) -> Boolean): Boolean { return action(url?.path ?: return false) } +val WebResourceRequest.isImage: Boolean + get() = query { it.contains(".jpg") || it.contains(".png") } + +val WebResourceRequest.isMedia: Boolean + get() = query { it.contains(".jpg") || it.contains(".png") || it.contains("video") } + /** * Generic filter passthrough * If Resource is already nonnull, pass it, otherwise check if filter is met and override the response accordingly */ -fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean): WebResourceResponse? - = this ?: if (request.query { filter(it) }) blankResource else null +fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean) + = filter(request.query { filter(it) }) + +fun WebResourceResponse?.filter(filter: Boolean): WebResourceResponse? + = this ?: if (filter) blankResource else null fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse? = filter(request) { it.endsWith(".css") } fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse? - = filter(request) { it.contains(".jpg") || it.contains(".png") } + = filter(request.isImage) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt index 79ca1fdf..89ad766d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -11,7 +11,7 @@ import android.widget.FrameLayout import android.widget.ProgressBar import ca.allanwang.kau.utils.* import com.pitchedapps.frost.R -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostDownload @@ -53,7 +53,7 @@ class FrostWebView @JvmOverloads constructor( } @SuppressLint("SetJavaScriptEnabled") - fun setupWebview(url: String, enum: FbTab? = null) { + fun setupWebview(url: String, enum: FbItem? = null) { with(web) { baseUrl = url baseEnum = enum @@ -77,7 +77,7 @@ class FrostWebView @JvmOverloads constructor( //Some urls have postJavascript injections so make sure we load the base url override fun onRefresh() { when (web.baseUrl) { - FbTab.MENU.url -> web.loadBaseUrl(true) + FbItem.MENU.url -> web.loadBaseUrl(true) else -> web.reload(true) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index 94bff3c3..5f679c65 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -1,8 +1,8 @@ package com.pitchedapps.frost.web import android.content.Context -import android.content.Intent import android.graphics.Bitmap +import android.graphics.Color import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView @@ -14,10 +14,12 @@ import com.pitchedapps.frost.activities.WebOverlayActivity import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FB_URL_BASE import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.injectors.* import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.IS_FROST_PRO import io.reactivex.subjects.Subject +import org.jetbrains.anko.withAlpha /** * Created by Allan Wang on 2017-05-31. @@ -42,18 +44,19 @@ open class BaseWebViewClient : WebViewClient() { open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { val refreshObservable: Subject<Boolean> = webCore.refreshObservable + val isMain = webCore.baseEnum != null override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) if (url == null) return - L.i("FWV Loading $url") -// L.v("Cookies ${CookieManager.getInstance().getCookie(url)}") + L.i("FWV Loading", url) refreshObservable.onNext(true) if (!url.contains(FACEBOOK_COM)) return if (url.contains("logout.php")) FbCookie.logout(Prefs.userId, { launchLogin(view.context) }) else if (url.contains("login.php")) FbCookie.reset({ launchLogin(view.context) }) } + fun launchLogin(c: Context) { if (c is MainActivity && c.cookies().isNotEmpty()) c.launchNewTask(SelectorActivity::class.java, c.cookies()) @@ -61,44 +64,52 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient c.launchNewTask(LoginActivity::class.java) } + fun injectBackgroundColor() + = webCore.setBackgroundColor(if (isMain) Color.TRANSPARENT else Prefs.bgColor.withAlpha(255)) + + + override fun onPageCommitVisible(view: WebView, url: String?) { + super.onPageCommitVisible(view, url) + injectBackgroundColor() + view.jsInject( + CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons), + CssHider.HEADER, + CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), + Prefs.themeInjector, + CssHider.NON_RECENT.maybe(webCore.url?.contains("?sk=h_chr") ?: false)) + } + override fun onPageFinished(view: WebView, url: String?) { - super.onPageFinished(view, url) - if (url == null) return - L.i("Page finished $url") + url ?: return + L.i("Page finished", url) if (!url.contains(FACEBOOK_COM)) { refreshObservable.onNext(false) return } - view.jsInject( - CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons), - CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO), - CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO) - ) onPageFinishedActions(url) } open internal fun onPageFinishedActions(url: String) { + if (url.startsWith("${FbItem.MESSAGES.url}/read/") && Prefs.messageScrollToBottom) + webCore.pageDown(true) injectAndFinish() } internal fun injectAndFinish() { L.d("Page finished reveal") - webCore.jsInject(CssHider.HEADER, - CssHider.NON_RECENT.maybe(webCore.url.contains("?sk=h_chr")), - Prefs.themeInjector, - callback = { - refreshObservable.onNext(false) - webCore.jsInject( - JsActions.LOGIN_CHECK, - JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled), - JsAssets.TEXTAREA_LISTENER, - JsAssets.CONTEXT_A, - JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) - ) - }) - } - - open fun handleHtml(html: String) { + refreshObservable.onNext(false) + injectBackgroundColor() + webCore.jsInject( + JsActions.LOGIN_CHECK, + JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled), + JsAssets.TEXTAREA_LISTENER, + CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO), + JsAssets.CONTEXT_A, + JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null) + ) + } + + open fun handleHtml(html: String?) { L.d("Handle Html") } @@ -112,26 +123,26 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient * returns false if we are already in an overlaying activity */ private fun launchRequest(request: WebResourceRequest): Boolean { - L.d("Launching Url", request.url.toString()) + L.d("Launching Url", request.url?.toString() ?: "null") if (webCore.context is WebOverlayActivity) return false webCore.context.launchWebOverlay(request.url.toString()) return true } - private fun launchImage(request: WebResourceRequest, text: String? = null): Boolean { - L.d("Launching Image", request.url.toString()) - webCore.context.launchImageActivity(request.url.toString(), text) + private fun launchImage(url: String, text: String? = null): Boolean { + L.d("Launching Image", url) + webCore.context.launchImageActivity(url, text) if (webCore.canGoBack()) webCore.goBack() return true } override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - L.i("Url Loading ${request.url}") - val path = request.url.path ?: return super.shouldOverrideUrlLoading(view, request) - L.v("Url Loading Path $path") + L.i("Url Loading", request.url?.toString()) + val path = request.url?.path ?: return super.shouldOverrideUrlLoading(view, request) + L.v("Url Loading Path", path) if (path.startsWith("/composer/")) return launchRequest(request) if (request.url.toString().contains("scontent-sea1-1.xx.fbcdn.net") && (path.endsWith(".jpg") || path.endsWith(".png"))) - return launchImage(request) + return launchImage(request.url.toString()) if (view.context.resolveActivityForUri(request.url)) return true return super.shouldOverrideUrlLoading(view, request) } @@ -163,6 +174,7 @@ class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(web } override fun onPageFinishedActions(url: String) { + L.d("Should inject ${url.shouldInjectMenu}") if (!url.shouldInjectMenu) injectAndFinish() } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt index d8edc15c..6dbc7c8d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt @@ -14,7 +14,7 @@ import ca.allanwang.kau.utils.circularReveal import ca.allanwang.kau.utils.fadeIn import ca.allanwang.kau.utils.fadeOut import ca.allanwang.kau.utils.isVisible -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.utils.Prefs import io.reactivex.Scheduler import io.reactivex.android.schedulers.AndroidSchedulers @@ -41,7 +41,7 @@ class FrostWebViewCore @JvmOverloads constructor( var baseUrl: String? = null - var baseEnum: FbTab? = null //only viewpager items should pass the base enum + var baseEnum: FbItem? = null //only viewpager items should pass the base enum internal lateinit var frostWebClient: FrostWebViewClient init { @@ -76,7 +76,7 @@ class FrostWebViewCore @JvmOverloads constructor( if (isVisible) fadeOut(duration = 200L) } else if (loading) { dispose?.dispose() - if (animate && Prefs.animate) circularReveal(offset = 150L) + if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY) else fadeIn(duration = 100L) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt new file mode 100644 index 00000000..50f2f6bc --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt @@ -0,0 +1,88 @@ +package com.pitchedapps.frost.web + +import android.annotation.SuppressLint +import android.content.Context +import android.webkit.JavascriptInterface +import android.webkit.WebView +import ca.allanwang.kau.utils.gone +import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.USER_AGENT_BASIC +import com.pitchedapps.frost.injectors.InjectorContract +import com.pitchedapps.frost.utils.L +import io.reactivex.Single +import io.reactivex.SingleEmitter +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import org.jetbrains.anko.runOnUiThread +import java.util.concurrent.TimeUnit + +/** + * Created by Allan Wang on 2017-08-12. + * + * Launches a headless html request and returns a result pair + * When successful, the pair will contain the html content and -1 + * When unsuccessful, the pair will contain an empty string and a StringRes for the given error + * + * All errors are rerouted to success calls, so no exceptions should occur. + * The headless extractor will also destroy itself on cancellation or when the request is finished + */ +fun Context.launchHeadlessHtmlExtractor(url: String, injector: InjectorContract, action: (Single<Pair<String, Int>>) -> Unit) { + val single = Single.create<Pair<String, Int>> { e: SingleEmitter<Pair<String, Int>> -> + val extractor = HeadlessHtmlExtractor(this, url, injector, e) + e.setCancellable { + runOnUiThread { extractor.destroy() } + e.onSuccess("" to R.string.html_extraction_cancelled) + } + }.subscribeOn(AndroidSchedulers.mainThread()) + .timeout(20, TimeUnit.SECONDS, Schedulers.io(), { it.onSuccess("" to R.string.html_extraction_timeout) }) + .onErrorReturn { "" to R.string.html_extraction_error } + action(single) +} + +/** + * Given a link and some javascript, will load the link and load the JS on completion + * The JS is expected to call [HeadlessHtmlExtractor.HtmlJSI.handleHtml], which will be sent + * to the [emitter] + */ +@SuppressLint("ViewConstructor") +private class HeadlessHtmlExtractor( + context: Context, url: String, val injector: InjectorContract, val emitter: SingleEmitter<Pair<String, Int>> +) : WebView(context) { + + val startTime = System.currentTimeMillis() + + init { + L.v("Created HeadlessHtmlExtractor for $url") + gone() + setupWebview(url) + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebview(url: String) { + settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_BASIC + webViewClient = HeadlessWebViewClient(url, injector) // basic client that loads our JS once the page has loaded + webChromeClient = QuietChromeClient() // basic client that disables logging + addJavascriptInterface(HtmlJSI(), "Frost") + loadUrl(url) + } + + inner class HtmlJSI { + @JavascriptInterface + fun handleHtml(html: String?) { + val time = System.currentTimeMillis() - startTime + emitter.onSuccess((html ?: "") to -1) + post { + L.d("HeadlessHtmlExtractor fetched $url in $time ms") + settings.javaScriptEnabled = false + settings.blockNetworkLoads = true + destroy() + } + } + } + + override fun destroy() { + super.destroy() + L.d("HeadlessHtmlExtractor destroyed") + } +}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 31be4450..aea25337 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -60,7 +60,7 @@ class LoginWebView @JvmOverloads constructor( view.jsInject(CssHider.HEADER.maybe(containsFacebook), CssHider.CORE.maybe(containsFacebook), Prefs.themeInjector.maybe(containsFacebook), - callback = { if (!view.isVisible) view.fadeIn(offset = 150L) }) + callback = { if (!view.isVisible) view.fadeIn(offset = WEB_LOAD_DELAY) }) } fun checkForLogin(url: String?, onFound: (id: Long, cookie: String) -> Unit) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt deleted file mode 100644 index 53fa0657..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.pitchedapps.frost.web - -import android.annotation.SuppressLint -import android.app.job.JobParameters -import android.webkit.JavascriptInterface -import android.webkit.WebView -import ca.allanwang.kau.utils.gone -import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.facebook.FbTab -import com.pitchedapps.frost.facebook.USER_AGENT_BASIC -import com.pitchedapps.frost.injectors.JsAssets -import com.pitchedapps.frost.services.NotificationService -import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.frostAnswersCustom -import org.jetbrains.anko.doAsync - -/** - * Created by Allan Wang on 2017-07-17. - * - * Bare boned headless view made solely to extract conversation info - */ -@SuppressLint("ViewConstructor") -class MessageWebView(val service: NotificationService, val params: JobParameters?, val cookie: CookieModel) : WebView(service) { - - private val startTime = System.currentTimeMillis() - private var isCancelled = false - - init { - gone() - setupWebview() - } - - @SuppressLint("SetJavaScriptEnabled") - private fun setupWebview() { - settings.javaScriptEnabled = true - settings.userAgentString = USER_AGENT_BASIC - webViewClient = HeadlessWebViewClient("MessageNotifs", JsAssets.NOTIF_MSG) - webChromeClient = QuietChromeClient() - addJavascriptInterface(MessageJSI(), "Frost") - loadUrl(FbTab.MESSAGES.url) - } - - fun finish() { - if (isCancelled) return - isCancelled = true - post { destroy() } - service.finish(params) - } - - override fun destroy() { - L.d("MessageWebView destroyed") - super.destroy() - } - - inner class MessageJSI { - @JavascriptInterface - fun handleHtml(html: String) { - if (isCancelled) return - if (html.length < 10) return finish() - val time = System.currentTimeMillis() - startTime - L.d("Notif messages fetched in $time ms") - frostAnswersCustom("NotificationTime", "Type" to "IM Headless", "Duration" to time) - doAsync { service.fetchMessageNotifications(cookie, html); finish() } - } - } - -}
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt index 05d56f92..da6d8ad3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt @@ -6,7 +6,7 @@ import android.webkit.JavascriptInterface import android.webkit.WebView import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.utils.gone -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.injectors.JsAssets import com.pitchedapps.frost.injectors.JsBuilder @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit */ class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) { - val searchSubject = PublishSubject.create<String>() + val searchSubject = PublishSubject.create<String>()!! init { gone() @@ -39,11 +39,11 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co * Contains the last item's href (search more) as well as the number of items found * This holder is synchronized */ - var previousResult: Pair<String?, Int> = Pair(null, 0) + var previousResult: Pair<String, Int> = Pair("", 0) fun saveResultFrame(result: List<Pair<List<String>, String>>) { synchronized(previousResult) { - previousResult = Pair(result.lastOrNull()?.second, result.size) + previousResult = Pair(result.last().second, result.size) } } @@ -56,17 +56,22 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co addJavascriptInterface(SearchJSI(), "Frost") searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) .map { - Jsoup.parse(it).select("a:not([rel*='keywords(']):not([href=#])[rel]").map { + val doc = Jsoup.parse(it) + L.d(doc.getElementById("main-search_input")?.html()) + val searchQuery = doc.getElementById("main-search-input")?.text() ?: "Null input" + L.d("Search query", searchQuery) + doc.select("a:not([rel*='keywords(']):not([href=#])[rel]").map { element -> //split text into separate items - L.v("Search element ${element.attr("href")}") - val texts = element.select("div").map { (it.text()) }.filter { it.isNotBlank() } + L.v("Search element", element.attr("href")) + val texts = element.select("div").map { it.text() }.filter { !it.isNullOrBlank() } val pair = Pair(texts, element.attr("href")) - L.v("Search element potential $pair") + L.v("Search element potential", pair.toString()) pair }.filter { it.first.isNotEmpty() } } - .filter { content -> Pair(content.lastOrNull()?.second, content.size) != previousResult } + .filter { it.isNotEmpty() } + .filter { Pair(it.last().second, it.size) != previousResult } .subscribe { content: List<Pair<List<String>, String>> -> saveResultFrame(content) @@ -90,7 +95,7 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co } override fun reload() { - super.loadUrl(FbTab.SEARCH.url) + super.loadUrl(FbItem.SEARCH.url) } /** @@ -104,7 +109,8 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co inner class SearchJSI { @JavascriptInterface - fun handleHtml(html: String) { + fun handleHtml(html: String?) { + html ?: return L.d("Search received response ${contract.isSearchOpened}") if (!contract.isSearchOpened) pauseLoad = true searchSubject.onNext(html) @@ -117,11 +123,14 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co L.d("Search loaded successfully") } 1 -> { //something is not found in the search view; this is effectively useless - L.eThrow("Search subject error; reverting to full overlay") + L.e("Search subject error; reverting to full overlay") Prefs.searchBar = false searchSubject.onComplete() contract.searchOverlayDispose() } + 2 -> { + L.v("Search emission received") + } } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt new file mode 100644 index 00000000..ad1fe467 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt @@ -0,0 +1,11 @@ +package com.pitchedapps.frost.web + +/** + * Created by Allan Wang on 2017-08-08. + * + * Global variables that are define states or constants for web contents + */ +const val WEB_LOAD_DELAY = 50L +var shouldLoadImages = false + +val consoleBlacklist = setOf("edge-chat")
\ No newline at end of file diff --git a/app/src/main/res/layout/material_drawer_header.xml b/app/src/main/res/layout/material_drawer_header.xml new file mode 100644 index 00000000..21cd20a1 --- /dev/null +++ b/app/src/main/res/layout/material_drawer_header.xml @@ -0,0 +1,119 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="@dimen/material_drawer_account_header_height" + android:clickable="true" + tools:ignore="all"> + + <!-- + Resolves padding + Check with <a href="https://github.com/mikepenz/MaterialDrawer/issues/1907">Issue</a> + --> + + <ImageView + android:id="@+id/material_drawer_account_header_background" + android:layout_width="match_parent" + android:layout_height="@dimen/material_drawer_account_header_height" + android:scaleType="centerCrop" /> + + <RelativeLayout + android:id="@+id/material_drawer_account_header" + android:layout_width="match_parent" + android:layout_height="@dimen/material_drawer_account_header_height"> + + <com.mikepenz.materialdrawer.view.BezelImageView + android:id="@+id/material_drawer_account_header_current" + android:layout_width="@dimen/material_drawer_account_header_selected" + android:layout_height="@dimen/material_drawer_account_header_selected" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:layout_marginBottom="@dimen/material_drawer_account_header_horizontal_bottom" + android:layout_marginLeft="@dimen/material_drawer_vertical_padding" + android:layout_marginTop="@dimen/material_drawer_account_header_horizontal_top" + android:clickable="true" + android:elevation="2dp" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignTop="@+id/material_drawer_account_header_current" + android:gravity="right"> + + <com.mikepenz.materialdrawer.view.BezelImageView + android:id="@+id/material_drawer_account_header_small_first" + android:layout_width="@dimen/material_drawer_account_header_secondary" + android:layout_height="@dimen/material_drawer_account_header_secondary" + android:layout_marginRight="@dimen/material_drawer_vertical_padding" + android:clickable="true" + android:elevation="2dp" + android:visibility="gone" /> + + <com.mikepenz.materialdrawer.view.BezelImageView + android:id="@+id/material_drawer_account_header_small_second" + android:layout_width="@dimen/material_drawer_account_header_secondary" + android:layout_height="@dimen/material_drawer_account_header_secondary" + android:layout_marginRight="@dimen/material_drawer_vertical_padding" + android:clickable="true" + android:elevation="2dp" + android:visibility="gone" /> + + <com.mikepenz.materialdrawer.view.BezelImageView + android:id="@+id/material_drawer_account_header_small_third" + android:layout_width="@dimen/material_drawer_account_header_secondary" + android:layout_height="@dimen/material_drawer_account_header_secondary" + android:layout_marginRight="@dimen/material_drawer_vertical_padding" + android:clickable="true" + android:elevation="2dp" + android:visibility="gone" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/material_drawer_account_header_text_section" + android:layout_width="match_parent" + android:layout_height="56dp" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentRight="true" + android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom" + android:layout_toLeftOf="@+id/material_drawer_account_header_text_switcher" + android:gravity="center_vertical|bottom" + android:orientation="vertical" + android:paddingEnd="56dp" + android:paddingLeft="0dp" + android:paddingRight="56dp" + android:paddingStart="0dp"> + + <TextView + android:id="@+id/material_drawer_account_header_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/material_drawer_vertical_padding" + android:fontFamily="sans-serif-medium" + android:lines="1" + android:maxLines="1" + android:textSize="@dimen/material_drawer_account_header_text" /> + + <TextView + android:id="@+id/material_drawer_account_header_email" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/material_drawer_vertical_padding" + android:fontFamily="sans-serif" + android:lines="1" + android:maxLines="1" + android:textSize="@dimen/material_drawer_account_header_text" /> + + </LinearLayout> + + <ImageView + android:id="@+id/material_drawer_account_header_text_switcher" + android:layout_width="@dimen/material_drawer_account_header_dropdown" + android:layout_height="@dimen/material_drawer_account_header_dropdown" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_marginBottom="@dimen/material_drawer_account_header_dropdown_margin_bottom" + android:layout_marginRight="@dimen/material_drawer_vertical_padding" /> + + </RelativeLayout> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 412d5f34..85826588 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,11 @@ <resources> <string name="dev_email" translatable="false">pitchedapps@gmail.com</string> + <string name="dev_paypal" translatable="false">https://www.paypal.me/Allanw9</string> + <string name="paypal" translatable="false">PayPal</string> <string name="play_store_package_id" translatable="false">com.pitchedapps.frost</string> + <!--Tabs--> + <string name="feed">Feed</string> <string name="most_recent">Most Recent</string> <string name="top_stories">Top Stories</string> @@ -23,6 +27,8 @@ <string name="notes">Notes</string> <string name="on_this_day">On This Day</string> + <!--Login--> + <string name="loading_account">Getting everything ready…</string> <string name="welcome">Welcome %s</string> <string name="select_facebook_account">Select Facebook Account</string> @@ -38,47 +44,23 @@ <string name="feature_request">Feature Request</string> <string name="subject">Subject</string> <string name="share">Share</string> - <string name="share_link">Share Link</string> - <string name="debug_link">Debug Link</string> - <string name="debug_link_subject">Frost for Facebook: Link Debug</string> - <string name="debug_link_content">Write here. Note that your link may contain private information, but I won\'t be able to see it as the post isn\'t public. The url will still help with debugging though.</string> - <string name="debug_link_desc">If a link isn\'t loading properly, you can email me so I can help debug it. Clicking okay will open an email request</string> - <string name="open_link">Open Link</string> - <string name="copy_link">Copy Link</string> - <string name="copy_text">Copy Text</string> - <string name="debug_image_link_subject">Frost for Facebook: Image Link Debug</string> <string name="web_overlay_swipe_hint">Swipe right to go back to the previous window.</string> <string name="profile_picture">Profile Picture</string> - <string name="custom_pro">Custom [Pro]</string> - <string name="uh_oh">Uh Oh</string> - <string name="reload">Reload</string> - <string name="play_store_not_pro">It seems like you are a pro user, but we couldn\'t find your purchasing info. If this error persists, please try clearing the Play Store cache and reinstalling the app.</string> - <string name="play_store_unsupported">It seems like app version can\'t purchase pro. Please reinstall from the play store if this is a persisting issue.</string> - <string name="play_store_not_found_pro_query">This is a pro feature, but this app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue.</string> - <string name="play_store_billing_error">Something went wrong. Please try again later.</string> - <string name="play_thank_you">Thank you!</string> - <string name="play_purchased_pro">Thank you for your support! Enjoy the pro version.</string> - <string name="play_already_purchased">Already Purchased</string> - <string name="play_already_purchased_content">Looks like you\'ve already purchased %s. Enjoy!</string> - <string name="found_pro">Found Frost Pro!</string> - <string name="found_pro_desc">Looks like you have frost pro! We\'ll reload the app so you can enjoy the awesome features!</string> - <string name="restoring_purchases">Restoring purchases…</string> - <string name="purchases_restored">Purchases Restored</string> - <string name="purchases_restored_with_pro">Frost Pro has been restored. Enjoy the features!</string> - <string name="purchases_restored_without_pro">It seems like you don\'t have pro. If this is a persistent issue, contact me and attach your purchase receipt.</string> - - <string name="login_id_failed">Login failed; id not found</string> - <string name="iab_still_in_progress">IAB query is still in progress</string> <string name="new_message">New Message</string> <string name="no_text">No text</string> - <string name="image_download_success">Image downloaded</string> - <string name="image_download_fail">Image failed to download</string> - <string name="image_share_failed">Failed to share image</string> - <string name="downloading_video">Downloading Video</string> - <string name="downloaded_video">Video Downloaded</string> - <string name="downloading_file">Downloading File</string> - <string name="downloaded_file">File Downloaded</string> + <!--About--> + + <string name="frost_description">Frost is a fully themable, + fully functional alternative to the official Facebook app, made from scratch and proudly open sourced.</string> + + <string name="faq_title">Frost FAQ</string> + + <!--HTML Extractor--> + + <string name="html_extraction_error">An error occurred in the html extraction.</string> + <string name="html_extraction_cancelled">The request has been cancelled.</string> + <string name="html_extraction_timeout">The request has timed out.</string> </resources> diff --git a/app/src/main/res/values/strings_about.xml b/app/src/main/res/values/strings_about.xml deleted file mode 100644 index 68590718..00000000 --- a/app/src/main/res/values/strings_about.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <string name="frost_description">Frost is a fully themable, - fully functional alternative to the official Facebook app, made from scratch and proudly open sourced.</string> - - <string name="faq_title">Frost FAQ</string> - - <string name="faq_feature">Can you add feature xxx?</string> - <string name="faq_feature_desc">I\'m always opened to suggestions, - and if a feature will enhance your experience, I\'d like to hear it. - However, please consider taking a look at my - <a href="https://github.com/AllanWang/Frost-for-Facebook/issues">issue tracker</a></string> - - <string name="faq_cannot_scroll_horizontally">I can\'t scroll horizontally in the webviews.</string> - <string name="faq_cannot_scroll_horizontally_desc">This is known since the viewpager takes priority for horizontal scrolling. - Frost has addressed this by allowing horizontal web scrolls if you tap and hold before scrolling.</string> - - <string name="faq_more_frequent_notifications">Can I get more frequent notifications?</string> - <string name="faq_more_frequent_notifications_desc">I made the decision to prioritize battery life by using a newer job scheduler for Android. - This means that your framework picks the best time to fetch the notifications, and the lowest window is 15 minutes. - This is also why I don\'t require the wakelock permission</string> -</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_download.xml b/app/src/main/res/values/strings_download.xml new file mode 100644 index 00000000..383daf56 --- /dev/null +++ b/app/src/main/res/values/strings_download.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="image_download_success">Image downloaded</string> + <string name="image_download_fail">Image failed to download</string> + <string name="image_share_failed">Failed to share image</string> + + <string name="downloading_video">Downloading Video</string> + <string name="downloaded_video">Video Downloaded</string> + <string name="downloading_file">Downloading File</string> + <string name="downloaded_file">File Downloaded</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_play_store.xml b/app/src/main/res/values/strings_play_store.xml new file mode 100644 index 00000000..8d37ee16 --- /dev/null +++ b/app/src/main/res/values/strings_play_store.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<resources> + <string name="found_pro">Found Frost Pro!</string> + <string name="found_pro_desc">Looks like you have frost pro! We\'ll reload the app so you can enjoy the awesome features!</string> + + <string name="play_already_purchased">Already Purchased</string> + <string name="play_already_purchased_content">Looks like you\'ve already purchased %s. Enjoy!</string> + <string name="play_purchased_pro">Thank you for your support! Enjoy the pro version.</string> + <string name="play_store_billing_error">Something went wrong. Please try again later.</string> + <string name="play_store_not_found_pro_query">This is a pro feature, but this app doesn\'t seem to be installed from the Play Store. Please reinstall if this is an issue.</string> + <string name="play_store_not_pro">It seems like you are a pro user, but we couldn\'t find your purchasing info. If this error persists, please try clearing the Play Store cache and reinstalling the app.</string> + <string name="play_store_unsupported">It seems like this app version can\'t purchase pro. Please reinstall from the play store if this is a persistent issue. + \nIf you would like to donate without any additional features, you may do so through PayPal.</string> + <string name="play_thank_you">Thank you!</string> + + <string name="purchases_restored">Purchases Restored</string> + <string name="purchases_restored_with_pro">Frost Pro has been restored. Enjoy the features!</string> + <string name="purchases_restored_without_pro">It seems like you don\'t have pro. If this is a persistent issue, contact me and attach your purchase receipt.</string> + + <string name="restoring_purchases">Restoring purchases…</string> + + <string name="custom_pro">Custom [Pro]</string> + <string name="uh_oh">Uh Oh</string> + <string name="reload">Reload</string> +</resources> diff --git a/app/src/main/res/values/strings_pref_behaviour.xml b/app/src/main/res/values/strings_pref_behaviour.xml index 13924a2d..6563ef69 100644 --- a/app/src/main/res/values/strings_pref_behaviour.xml +++ b/app/src/main/res/values/strings_pref_behaviour.xml @@ -9,6 +9,10 @@ <string name="overlay_full_screen_swipe_desc">Swipe right from anywhere on the overlaying web to close the browser. If disabled, only swiping from the left edge will move it.</string> <string name="viewpager_swipe">Viewpager Swipe</string> <string name="viewpager_swipe_desc">Allow swiping between the pages in the main view to switch tabs. By default, the swiping automatically stops when you long press on an item, such as the like button. Disabling this will prevent page swiping altogether.</string> + <string name="search_bar">Search Bar</string> + <string name="search_bar_desc">Enable the search bar instead of a search overlay</string> + <string name="force_message_bottom">Force Message Bottom</string> + <string name="force_message_bottom_desc">When loading a message thread, trigger a scroll to the bottom of the page rather than loading the page as is.</string> <string name="exit_confirmation">Exit Confirmation</string> <string name="exit_confirmation_desc">Show confirmation dialog before exiting the app</string> <string name="analytics">Analytics</string> diff --git a/app/src/main/res/values/strings_pref_debug.xml b/app/src/main/res/values/strings_pref_debug.xml new file mode 100644 index 00000000..d65adf29 --- /dev/null +++ b/app/src/main/res/values/strings_pref_debug.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="debug_toast_enabled">Debugging section is enabled! Go back to settings.</string> + + <string name="debug_disclaimer_info">Though most private content is automatically removed in the report, some sensitive info may still be visible. + \nPlease have a look at the debug report before sending it. + \n\nClicking one of the options below will prepare an email response with the web page data. + </string> + + <string name="debug_incomplete">Incomplete report</string> + + <string name="debug_report_email_title">Frost for Facebook: Debug Report</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_pref_experimental.xml b/app/src/main/res/values/strings_pref_experimental.xml index 0f545b3e..10c66f99 100644 --- a/app/src/main/res/values/strings_pref_experimental.xml +++ b/app/src/main/res/values/strings_pref_experimental.xml @@ -5,8 +5,7 @@ <string name="experimental_disclaimer_info">Experimental features may be unstable and may never make it to production. Use at your own risk, send feedback, and feel free to disable them if they don\'t work well.</string> <string name="experimental_by_default">Experimental by Default</string> <string name="experimental_by_default_desc">Feeling risky or just want to help with debugging? Checking this will enable future experimental functions be default.</string> - <string name="search_bar">Search Bar</string> - <string name="search_bar_desc">Enable the search bar instead of a search overlay</string> + <string name="verbose_logging">Verbose Logging</string> <string name="verbose_logging_desc">Enable verbose logging to help with crash reports. Logging will only be sent once an error is encountered, so repeat the issue to notify the dev. This will automatically be disabled if the app restarts.</string> <string name="restart_frost">Restart Frost</string> diff --git a/app/src/main/res/values/strings_pref_networks.xml b/app/src/main/res/values/strings_pref_networks.xml new file mode 100644 index 00000000..29eca24a --- /dev/null +++ b/app/src/main/res/values/strings_pref_networks.xml @@ -0,0 +1,4 @@ +<resources> + <string name="network_media_on_metered">Load images on metered network.</string> + <string name="network_media_on_metered_desc">If a metered network is detected, Frost will automatically stop all images and videos from loading.</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_pref_notifications.xml b/app/src/main/res/values/strings_pref_notifications.xml index 00c30b57..3cd953f6 100644 --- a/app/src/main/res/values/strings_pref_notifications.xml +++ b/app/src/main/res/values/strings_pref_notifications.xml @@ -19,5 +19,4 @@ <string name="notification_vibrate">Notification vibration</string> <string name="notification_lights">Notification lights</string> - </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_preferences.xml b/app/src/main/res/values/strings_preferences.xml index c2ba0b36..05335345 100644 --- a/app/src/main/res/values/strings_preferences.xml +++ b/app/src/main/res/values/strings_preferences.xml @@ -10,6 +10,9 @@ <string name="behaviour">Behaviour</string> <string name="behaviour_desc">Define how the app interacts in certain settings</string> + <string name="network">Network</string> + <string name="network_desc">Define options that affect metered networks</string> + <string name="experimental">Experimental</string> <string name="experimental_desc">Enable early access to potentially unstable features</string> @@ -19,6 +22,8 @@ <string name="about_frost">About Frost for Facebook</string> <string name="about_frost_desc">Version, Credits, and FAQs</string> + <string name="debug_frost">Frost Debugger</string> + <string name="debug_frost_desc">Send html data to help with debugging.</string> <string name="replay_intro">Replay Introduction</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_temp.xml b/app/src/main/res/values/strings_temp.xml deleted file mode 100644 index 526b102d..00000000 --- a/app/src/main/res/values/strings_temp.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="intro_title">Frost now has an intro screen</string> - <string name="intro_desc">Would you like to see it? You can always replay it under settings</string> - -</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings_web_context.xml b/app/src/main/res/values/strings_web_context.xml new file mode 100644 index 00000000..3b93a202 --- /dev/null +++ b/app/src/main/res/values/strings_web_context.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="share_link">Share Link</string> + <string name="debug_link">Debug Link</string> + <string name="debug_link_subject">Frost for Facebook: Link Debug</string> + <string name="debug_link_content">Write here. Note that your link may contain private information, but I won\'t be able to see it as the post isn\'t public. The url will still help with debugging though.</string> + <string name="debug_link_desc">If a link isn\'t loading properly, you can email me so I can help debug it. Clicking okay will open an email request</string> + <string name="open_link">Open Link</string> + <string name="copy_link">Copy Link</string> + <string name="copy_text">Copy Text</string> + <string name="debug_image_link_subject">Frost for Facebook: Image Link Debug</string> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml index e7681107..a1fedd42 100644 --- a/app/src/main/res/xml/frost_changelog.xml +++ b/app/src/main/res/xml/frost_changelog.xml @@ -11,19 +11,29 @@ <!--<version title="Beta Updates" />--> <version title="Beta Updates"/> - <item text="Fixed notification titles" /> - <item text="Added support for downloading videos" /> - <item text="" /> + <item text="Create more robust IM notification fetcher with a timeout" /> + <item text="Add hidden debugging options for certain views" /> + <item text="Separate IM and general notification groups" /> + <item text="Add click actions to group notifications. They will launch the message page or the notification page respectively" /> + <item text="Add behaviour setting to force message threads to scroll to the bottom after loading." /> + <item text="Add faq for disabling video auto play" /> <item text="" /> <item text="" /> - <version title="v1.5.0"/> + <version title="v1.4.2"/> <item text="Experimental: Add notifications for messages; report to me if this drains your battery" /> <item text="Add FAQ in the about section" /> <item text="Add video uploading" /> <item text="Add open link option in context menu" /> <item text="Add geolocation" /> <item text="Update theme" /> + <item text="Fix notification titles" /> + <item text="ALPHA: Add support for downloading videos (hit the download button)" /> + <item text="Deny intents for login so the page loads properly (thank you @Zenexer)" /> + <item text="Reduce injection offset and move injectors to an earlier method" /> + <item text="Add option to disable media loading on metered network" /> + <item text="Fix menu section" /> + <item text="Add more background setters to help transparent themes" /> <version title="v1.4.1"/> <item text="Add intro pages" /> diff --git a/app/src/main/res/xml/frost_faq.xml b/app/src/main/res/xml/frost_faq.xml index e46d2d50..1830ed33 100644 --- a/app/src/main/res/xml/frost_faq.xml +++ b/app/src/main/res/xml/frost_faq.xml @@ -14,4 +14,12 @@ <answer><![CDATA[I made the decision to prioritize battery life by using a newer job scheduler for Android. This means that your framework picks the best time to fetch the notifications, and the lowest window is 15 minutes. This is also why I don't require the wakelock permission]]></answer> + + <question><![CDATA[Can I disable auto play for videos?]]></question> + <answer><![CDATA[Facebook already has a toggle. <br/> Go to menu → account settings → videos <br/> and change it from there.]]></answer> + + <!-- + <question><![CDATA[]]></question> + <answer><![CDATA[]]></answer> + --> </resources>
\ No newline at end of file diff --git a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt index c9d27a1c..91e2149c 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/MiscTest.kt @@ -2,6 +2,7 @@ package com.pitchedapps.frost import com.pitchedapps.frost.injectors.CssHider import org.junit.Test +import kotlin.test.assertEquals /** * Created by Allan Wang on 2017-06-14. @@ -9,7 +10,12 @@ import org.junit.Test class MiscTest { @Test - fun asdf() { + fun headerFunction() { print(CssHider.HEADER.injector.function) } + + @Test + fun nullPair() { + assertEquals(Pair<String?, Int>(null, 2), Pair<String?, Int>(null, 2)) + } }
\ No newline at end of file diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt new file mode 100644 index 00000000..4ec09ea6 --- /dev/null +++ b/app/src/test/kotlin/com/pitchedapps/frost/utils/JsoupCleanerTest.kt @@ -0,0 +1,56 @@ +package com.pitchedapps.frost.utils + +import org.junit.Test +import kotlin.test.assertEquals + +/** + * Created by Allan Wang on 2017-08-10. + */ +class JsoupCleanerTest { + + val whitespaceRegex = Regex("\\s+") + + fun String.cleanWhitespace() = replace("\n", "").replace(whitespaceRegex, " ").replace("> <", "><") + + private fun String.assertCleanHtml(expected: String) { + assertEquals(expected.cleanWhitespace(), cleanHtml().cleanWhitespace()) + } + + private fun String.assertCleanJsoup(expected: String) { + assertEquals(expected.cleanWhitespace(), cleanJsoup().cleanWhitespace()) + } + + private fun String.assertCleanText(expected: String) { + assertEquals(expected.cleanWhitespace(), cleanText().cleanWhitespace()) + } + + @Test + fun noChange() { + "<a><aa> HI </aa></a>".assertCleanJsoup("<a><aa> HI </aa></a>") + } + + @Test + fun basicText() { + """<div class="test">Hello world</div>""".assertCleanHtml("""<div class="test"></div>""") + } + + @Test + fun multiLineText() { + """<div class="test">Hello + world</div>""".assertCleanHtml("""<div class="test"></div>""") + } + + @Test + fun textRemoval() { + """<div>Hello<a>World</a></div>""".assertCleanText("<div><a></a></div>") + } + + @Test + fun kau() { + val html = """<div class="col s12 m6"> <div id="kau" class="card medium sticky-action"> <div class="card-image waves-effect waves-block waves-light"> <img class="activator" src="images/kau.jpg"> <span class="card-title activator background-gradient">KAU</span> </div><div class="card-content"><p>An extensive collection of Kotlin Android Utils</p></div><div class="card-action"> <a href="https://github.com/AllanWang/KAU" target="_blank" class="inline-block">Github</a> <a href="https://allanwang.github.io/KAU/" target="_blank" class="inline-block">Page</a> </div><div class="card-reveal"> <span class="card-title grey-text text-darken-4">KAU<i class="material-icons right">close</i></span> <ul class="browser-default"> <li>Huge package of one line extension functions</li><li>Custom UI views</li><li>Adapter items and animators</li><li>SearchView</li><li>Custom delegates</li></ul> </div></div></div>""" + val expected = """<div class="col s12 m6"><div id="kau" class="card medium sticky-action"><div class="card-image waves-effect waves-block waves-light"><img class="activator" src="images/kau.jpg"></div><div class="card-action"><a href="-" target="_blank" class="inline-block"></a><a href="-" target="_blank" class="inline-block"></a></div></div></div>""" + html.assertCleanHtml(expected) + } + +} + |