diff --git a/Awful.apk/build.gradle b/Awful.apk/build.gradle index 057f95ab0..e94ff7466 100644 --- a/Awful.apk/build.gradle +++ b/Awful.apk/build.gradle @@ -2,7 +2,7 @@ buildscript { ext.kotlin_version = '1.9.21' dependencies { - classpath 'com.android.tools.build:gradle:8.2.0' + classpath 'com.android.tools.build:gradle:8.7.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -25,8 +25,8 @@ android { defaultConfig { applicationId = "com.ferg.awfulapp" minSdkVersion 24 - targetSdkVersion 34 - resConfigs 'en' + targetSdkVersion 35 + resourceConfigurations += ['en'] // Stops the Gradle plugin’s automatic rasterization of vectors vectorDrawables.useSupportLibrary = true @@ -66,9 +66,9 @@ android { //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg' } } - - kotlin { - jvmToolchain(17) + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } task copyThreadTags { @@ -105,8 +105,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' // these are all needed to override some old versions that are dependencies... somewhere implementation 'androidx.media:media:1.7.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' @@ -116,9 +116,9 @@ dependencies { implementation 'com.android.volley:volley:1.2.1' - implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.code.gson:gson:2.11.0' - implementation 'org.jsoup:jsoup:1.15.4' + implementation 'org.jsoup:jsoup:1.18.1' implementation 'com.jakewharton.threetenabp:threetenabp:1.2.4' implementation 'com.samskivert:jmustache:1.15' @@ -134,16 +134,12 @@ dependencies { implementation 'com.bignerdranch.android:expandablerecyclerview:2.1.1' implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'com.github.bumptech.glide:glide:4.16.0' - ksp 'com.github.bumptech.glide:ksp:4.16.0' - - implementation 'com.github.chrisbanes:PhotoView:2.3.0' - implementation 'com.github.rubensousa:BottomSheetBuilder:1.5.1' + implementation 'androidx.preference:preference:1.2.1' - testImplementation 'junit:junit:4.12' + testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest-library:1.3' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' // updating this might cause the status and navigation bar to become blue, test for this implementation 'androidx.core:core-splashscreen:1.0.0-alpha02' diff --git a/Awful.apk/src/main/AndroidManifest.xml b/Awful.apk/src/main/AndroidManifest.xml index 5af3bda84..bc1a41d0a 100644 --- a/Awful.apk/src/main/AndroidManifest.xml +++ b/Awful.apk/src/main/AndroidManifest.xml @@ -7,8 +7,8 @@ --> + @@ -254,6 +255,11 @@ android:windowSoftInputMode="adjustResize" android:configChanges="orientation|keyboardHidden|screenSize" /> + diff --git a/Awful.apk/src/main/assets/changelog.html b/Awful.apk/src/main/assets/changelog.html index b2130e722..fc87fb681 100644 --- a/Awful.apk/src/main/assets/changelog.html +++ b/Awful.apk/src/main/assets/changelog.html @@ -6,7 +6,17 @@
-

3.9.2

+

3.9.9

+
    +
  • Replaced the display image zoom thing so that it doesn't just enlarge the image but also actually zooms in. The wonders of modern technology.
  • +
  • Added function to increase the number of bad threads on the forums. May dog have mercy on our souls.
  • +
  • Fixed file attachments again (Android 13 and up this time).
  • +
  • Fixed the theme font functionality. You can now make (most of) the app look like a 2009 Samsung phone again.
  • +
  • Added OpenDislexic font. Gee Whiz, what a coincidence.
  • +
+
+
+

3.9.4

  • Made imgur upload work again. You can finally uninstall that awful app. No, I mean theirs. I should have written terrible app, that's clearer.
  • Removed crash report library because Google considers it collecting user data. Now collecting user data via the base functionality again like in 2012. Remember to include your username in the crash reports. Or don't, that's ok too.
  • diff --git a/Awful.apk/src/main/assets/css/amberpos.css b/Awful.apk/src/main/assets/css/amberpos.css index e14a5cec9..dd52e92c6 100644 --- a/Awful.apk/src/main/assets/css/amberpos.css +++ b/Awful.apk/src/main/assets/css/amberpos.css @@ -80,4 +80,14 @@ body { */ .postmenu:after { content: "\e901"; +} + + +#zoom-close { + background: black; + border: 2px solid #eacf4c; +} + +#zoom-close:after { + color: #eacf4c; } \ No newline at end of file diff --git a/Awful.apk/src/main/assets/css/general.css b/Awful.apk/src/main/assets/css/general.css index 34343e3b4..5ab959448 100644 --- a/Awful.apk/src/main/assets/css/general.css +++ b/Awful.apk/src/main/assets/css/general.css @@ -317,4 +317,53 @@ video.playing ~ .video-link { .postcontent .signature { padding-top: 10px; +} + +#zoom { + display: none; +} + +#zoom img { + display: block; + max-width:100%; + max-height:100%; + cursor: move; + touch-action: none; + } + +#zoom.zoom-enabled { + z-index: 1; + background-color: black; + position: fixed; + overflow: hidden; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#zoom-close { + position: fixed; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + top: 20px; + right: 20px; + background: #00000099; + width: 50px; + height: 50px; + z-index: 2; +} + +#zoom-close:after { + display: block; + content: '\00d7'; + font-size: 50px; + color: white; } \ No newline at end of file diff --git a/Awful.apk/src/main/assets/css/oled.css b/Awful.apk/src/main/assets/css/oled.css index 207fa2df3..9dfc8c7b3 100644 --- a/Awful.apk/src/main/assets/css/oled.css +++ b/Awful.apk/src/main/assets/css/oled.css @@ -97,4 +97,12 @@ body { height: 1px; background-color: #e7e7e7; margin: 0px; +} + +#zoom-close { + background: black; +} + +#zoom-close:after { + color: #e7e7e7; } \ No newline at end of file diff --git a/Awful.apk/src/main/assets/css/yospos.css b/Awful.apk/src/main/assets/css/yospos.css index 2291f6d4b..af39594a5 100644 --- a/Awful.apk/src/main/assets/css/yospos.css +++ b/Awful.apk/src/main/assets/css/yospos.css @@ -83,4 +83,13 @@ body { */ .postmenu:after { content: "\e901"; +} + +#zoom-close { + background: black; + border: 2px solid #0F0; +} + +#zoom-close:after { + color: #0F0; } \ No newline at end of file diff --git a/Awful.apk/src/main/assets/fonts/open_dyslexic.ttf.mp3 b/Awful.apk/src/main/assets/fonts/open_dyslexic.ttf.mp3 new file mode 100644 index 000000000..0ff4c0b58 Binary files /dev/null and b/Awful.apk/src/main/assets/fonts/open_dyslexic.ttf.mp3 differ diff --git a/Awful.apk/src/main/assets/javascript/hammer.js b/Awful.apk/src/main/assets/javascript/hammer.js new file mode 100644 index 000000000..edadee159 --- /dev/null +++ b/Awful.apk/src/main/assets/javascript/hammer.js @@ -0,0 +1,7 @@ +/*! Hammer.JS - v2.0.8 - 2016-04-23 + * http://hammerjs.github.io/ + * + * Copyright (c) 2016 Jorik Tangelder; + * Licensed under the MIT license */ +!function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance e.preventDefault(), false); + displayDefaultWidth = image.offsetWidth; + displayDefaultHeight = image.offsetHeight; + rangeX = Math.max(0, displayDefaultWidth - containerWidth); + rangeY = Math.max(0, displayDefaultHeight - containerHeight); + } + + + function updateImage(x, y, scale) { + const transform = 'translateX(' + x + 'px) translateY(' + y + 'px) translateZ(0px) scale(' + scale + ',' + scale + ')'; + image.style.transform = transform; + } + + function updateRange() { + rangeX = Math.max(0, Math.round(displayDefaultWidth * imageCurrentScale) - containerWidth); + rangeY = Math.max(0, Math.round(displayDefaultHeight * imageCurrentScale) - containerHeight); + + rangeMaxX = Math.round(rangeX / 2); + rangeMinX = 0 - rangeMaxX; + + rangeMaxY = Math.round(rangeY / 2); + rangeMinY = 0 - rangeMaxY; + } + + const hammertime = new Hammer(zoom,{ inputClass: Hammer.TouchMouseInput }); + + hammertime.get('pinch').set({ enable: true }); + hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); + + hammertime.on('pan', function(ev) { + imageCurrentX = clamp(imageX + ev.deltaX, rangeMinX, rangeMaxX); + imageCurrentY = clamp(imageY + ev.deltaY, rangeMinY, rangeMaxY); + updateImage(imageCurrentX, imageCurrentY, imageScale); + }); + + hammertime.on('pinch pinchmove',function (ev) { + imageCurrentScale = clampScale(ev.scale * imageScale); + updateRange(); + imageCurrentX = clamp(imageX + ev.deltaX, rangeMinX, rangeMaxX); + imageCurrentY = clamp(imageY + ev.deltaY, rangeMinY, rangeMaxY); + updateImage(imageCurrentX, imageCurrentY, imageCurrentScale); + }); + + hammertime.on('panend pancancel pinchend pinchcancel', function(){ + imageScale = imageCurrentScale; + imageX = imageCurrentX; + imageY = imageCurrentY; + }); +} + +/** + * Exists the zoom overlay + */ +function exitImageZoom() { + if(!document.getElementById('zoom')){ return } + document.getElementById('zoom').remove(); + document.getElementById('zoom-close').remove(); + listener.setZoomEnabled(false); +} + /** * Load an image url and replace links with the image. Handles paused gifs and basic text links. * @param {String} url The image URL @@ -364,7 +494,7 @@ function changeFontFace(font) { var styleElement = document.createElement('style'); styleElement.id = 'font-face'; styleElement.setAttribute('type', 'text/css'); - styleElement.textContent = '@font-face { font-family: userselected; src: url(\'content://com.ferg.awfulapp.webprovider/' + font + '\'); }'; + styleElement.textContent = '@font-face { font-family: userselected; src: url(\'file:///android_asset/' + font + '\'); }'; document.head.appendChild(styleElement); } } @@ -392,13 +522,13 @@ function freezeGif(image) { * @param {Element} image Gif image to monitor */ function prepareFreezeGif(image) { - if (!image.complete) { - image.addEventListener('load', function freezeLoadHandler() { - freezeGif(image); - }); - } else { - freezeGif(image); - } + if (!image.complete) { + image.addEventListener('load', function freezeLoadHandler() { + freezeGif(image); + }); + } else { + freezeGif(image); + } } /** @@ -467,9 +597,9 @@ function handleQuoteLink(link, event) { * @param {Element} info The HTMLElement of the postinfo */ function toggleInfo(info) { - var posterTitle = info.querySelector('.postinfo-title'); - var posterRegDate = info.querySelector('.postinfo-regdate'); - if (!posterTitle) { return; } + var posterTitle = info.querySelector('.postinfo-title'); + var posterRegDate = info.querySelector('.postinfo-regdate'); + if (!posterTitle) { return; } if (posterTitle.classList.contains('extended')) { if (info.querySelector('.avatar') !== null) { @@ -484,9 +614,9 @@ function toggleInfo(info) { posterTitle.classList.remove('extended'); posterTitle.setAttribute('aria-hidden', 'true'); if (posterRegDate) { - posterRegDate.classList.remove('extended'); - posterRegDate.setAttribute('aria-hidden', 'true'); - } + posterRegDate.classList.remove('extended'); + posterRegDate.setAttribute('aria-hidden', 'true'); + } } else { if (info.querySelector('.avatar') !== null) { if (info.querySelector('canvas') !== null) { @@ -503,9 +633,9 @@ function toggleInfo(info) { posterTitle.classList.add('extended'); posterTitle.setAttribute('aria-hidden', 'false'); if (posterRegDate) { - posterRegDate.classList.add('extended'); - posterRegDate.setAttribute('aria-hidden', 'false'); - } + posterRegDate.classList.add('extended'); + posterRegDate.setAttribute('aria-hidden', 'false'); + } } } @@ -514,7 +644,7 @@ function toggleInfo(info) { * @param {Element} postMenu The HTMLElement of the postmenu */ function showPostMenu(postMenu) { -// temp hack to create the right menu for rap sheet entries without making its own CSS class etc + // temp hack to create the right menu for rap sheet entries without making its own CSS class etc if (postMenu.hasAttribute('badPostUrl')) { showPunishmentMenu(postMenu); return; @@ -716,16 +846,16 @@ function handleTouchLeave() { * Hides all instances of the given avatar on the page */ function hideAvatar(avatarUrl) { - document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) { - avatarTag.classList.add('hide-avatar'); - }); + document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function (avatarTag) { + avatarTag.classList.add('hide-avatar'); + }); } /** * Shows all instances of the given avatar on the page */ function showAvatar(avatarUrl) { - document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function(avatarTag) { - avatarTag.classList.remove('hide-avatar'); - }); + document.querySelectorAll('[src="' + avatarUrl + '"]').forEach(function (avatarTag) { + avatarTag.classList.remove('hide-avatar'); + }); } \ No newline at end of file diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulActivity.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulActivity.kt index 85d8861ab..a4f4a5463 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulActivity.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulActivity.kt @@ -250,11 +250,25 @@ abstract class AwfulActivity : AppCompatActivity(), AwfulPreferences.AwfulPrefer fun showPostComposer(threadId: Int, postType: Int, sourcePostId: Int) { // TODO: this should probably all be refactored into types like the NavigationEvents (maybe even rolled in with them) - discrete Posting events with the specific associated data for each startActivityForResult( - Intent(this, PostReplyActivity::class.java) - .putExtra(Constants.REPLY_THREAD_ID, threadId) - .putExtra(Constants.EDITING, postType) - .putExtra(Constants.REPLY_POST_ID, sourcePostId), - PostReplyFragment.REQUEST_POST + Intent(this, PostReplyActivity::class.java) + .putExtra(Constants.REPLY_THREAD_ID, threadId) + .putExtra(Constants.EDITING, postType) + .putExtra(Constants.REPLY_POST_ID, sourcePostId), + PostReplyFragment.REQUEST_POST + ) + } + + /** + * Display the thread composer. + * + * @param forumId the ID of the forum the thread is in + */ + fun showThreadComposer(forumId: Int) { + // TODO: this should probably all be refactored into types like the NavigationEvents (maybe even rolled in with them) - discrete Posting events with the specific associated data for each + startActivityForResult( + Intent(this, PostThreadActivity::class.java) + .putExtra(Constants.POST_FORUM_ID, forumId), + PostThreadFragment.REQUEST_THREAD ) } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulDialogFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulDialogFragment.java index dcb790d24..e743d6645 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulDialogFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulDialogFragment.java @@ -82,6 +82,7 @@ protected View inflateView(int resId, ViewGroup container, LayoutInflater inflat if(progressBar instanceof AwfulProgressBar){ this.progressBar = (AwfulProgressBar) progressBar; } + getAwfulActivity().setPreferredFont(v); return v; } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulFragment.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulFragment.kt index 23fe9703f..1e7b004ce 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulFragment.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulFragment.kt @@ -32,14 +32,14 @@ import android.content.ClipboardManager import android.content.Context import android.os.Bundle import android.os.Handler -import androidx.annotation.StringRes -import androidx.fragment.app.Fragment -import androidx.loader.app.LoaderManager import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import androidx.loader.app.LoaderManager import com.android.volley.Request import com.android.volley.VolleyError import com.ferg.awfulapp.network.NetworkUtils @@ -105,6 +105,7 @@ abstract class AwfulFragment : Fragment(), AwfulPreferences.AwfulPreferenceUpdat progressBar = v.findViewById(R.id.progress_bar) probationBar = v.findViewById(R.id.probation_bar) probationBar?.setListener { navigate(NavigationEvent.LepersColony(prefs.userId)) } + awfulActivity!!.setPreferredFont(v) return v } @@ -139,6 +140,10 @@ abstract class AwfulFragment : Fragment(), AwfulPreferences.AwfulPreferenceUpdat awfulActivity?.apply { runOnUiThread { showPostComposer(threadId, type, postId) } } } + fun displayPostThreadDialog(forumId: Int) { + awfulActivity?.apply { runOnUiThread { showThreadComposer(forumId) } } + } + protected fun setProgress(percent: Int) { progressPercent = percent if (progressPercent > 0) { diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulLoginActivity.java b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulLoginActivity.java index 66c393e24..2cc5bf996 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulLoginActivity.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/AwfulLoginActivity.java @@ -163,6 +163,7 @@ private void loginClick() { final String password = NetworkUtils.encodeHtml(mPassword.getText().toString()); mDialog = ProgressDialog.show(AwfulLoginActivity.this, "Logging In", "Hold on...", true); + setPreferredFont(mDialog.findViewById(android.R.id.title)); final AwfulLoginActivity self = this; NetworkUtils.queueRequest(new LoginRequest(this, username, password).build(null, new AwfulRequest.AwfulResultCallback() { @Override diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/EmoteFragment.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/EmoteFragment.kt index ce7f4d884..220725302 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/EmoteFragment.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/EmoteFragment.kt @@ -2,21 +2,22 @@ package com.ferg.awfulapp import android.database.Cursor import android.os.Bundle -import com.google.android.material.tabs.TabLayout -import androidx.core.app.* -import androidx.loader.content.CursorLoader -import androidx.loader.content.Loader -import androidx.viewpager.widget.ViewPager import android.text.Editable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.* -import androidx.fragment.app.DialogFragment +import android.widget.EditText +import android.widget.GridView +import android.widget.ImageButton +import android.widget.TextView +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter import androidx.loader.app.LoaderManager +import androidx.loader.content.CursorLoader +import androidx.loader.content.Loader +import androidx.viewpager.widget.ViewPager import com.android.volley.VolleyError import com.ferg.awfulapp.constants.Constants import com.ferg.awfulapp.preferences.AwfulPreferences @@ -29,6 +30,7 @@ import com.ferg.awfulapp.task.EmoteRequest import com.ferg.awfulapp.thread.AwfulEmote import com.ferg.awfulapp.util.PassiveTextWatcher import com.ferg.awfulapp.util.bind +import com.google.android.material.tabs.TabLayout import timber.log.Timber /** @@ -85,14 +87,16 @@ private object EmoteHistory { * * This must be created by a parent fragment that implements [EmotePickerListener] */ -class EmotePicker : DialogFragment() { +class EmotePicker : AwfulDialogFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return inflater.inflate(R.layout.emote_picker_container_fragment, container, false) + val view = inflater.inflate(R.layout.emote_picker_container_fragment, container, false); + awfulActivity.setPreferredFont(view) + return view; } override fun onActivityCreated(aSavedState: Bundle?) { @@ -110,6 +114,10 @@ class EmotePicker : DialogFragment() { } } + override fun getTitle(): String { + return "Emote" + } + fun onEmoteChosen(emoteCode: String) { Toast.makeText(activity, emoteCode, Toast.LENGTH_SHORT).show() (parentFragment as EmotePickerListener).onEmoteChosen(emoteCode) @@ -205,8 +213,12 @@ abstract class EmoteGridFragment : AwfulFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? = - inflater.inflate(layoutId, container, false) + ): View? { + val view = inflater.inflate(layoutId, container, false); + awfulActivity?.setPreferredFont(view) + return view; + } + /** diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/FontManager.java b/Awful.apk/src/main/java/com/ferg/awfulapp/FontManager.java index 5b8c29485..6f871c55f 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/FontManager.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/FontManager.java @@ -4,11 +4,16 @@ import android.graphics.Typeface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import android.text.SpannableStringBuilder; +import android.text.style.TypefaceSpan; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.ferg.awfulapp.preferences.AwfulPreferences; +import com.google.android.material.textfield.TextInputLayout; import org.apache.commons.lang3.text.WordUtils; @@ -113,9 +118,11 @@ public void setCurrentFont(String fontName) { * {@link Typeface#ITALIC}, or {@link Typeface#BOLD_ITALIC}, */ public void setTypefaceToCurrentFont(View view, int flags) { - if (view instanceof TextView) + if (view instanceof TextView) { setTextViewTypefaceToCurrentFont((TextView) view, flags); - else if (view instanceof ViewGroup) { + } else if(view instanceof TextInputLayout){ + setTextViewTypefaceToCurrentFont((TextInputLayout) view); + } else if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) @@ -174,6 +181,26 @@ private void setTextViewTypefaceToCurrentFont(TextView textView, int textStyle) Timber.w("Couldn't set typeface as currentFont is null"); } + /** + * Set a TextView's typeface to the current font. + * + * @param textLayout TextView to set + */ + private void setTextViewTypefaceToCurrentFont(TextInputLayout textLayout) { + + if (currentFont != null) + textLayout.setTypeface(currentFont); + else + Timber.w("Couldn't set typeface as currentFont is null"); + } + + public void setMenuItemFont(MenuItem item) { + SpannableStringBuilder title = new SpannableStringBuilder(item.getTitle()); + TypefaceSpan face = new TypefaceSpan(currentFont); + title.setSpan(face, 0, title.length(), 0); + item.setTitle(title); + } + /** * Check if the passed text style is valid. * diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumDisplayFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumDisplayFragment.java index ce0b4a247..20b115ab0 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumDisplayFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumDisplayFragment.java @@ -36,12 +36,15 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.loader.app.LoaderManager; import androidx.loader.content.CursorLoader; import androidx.loader.content.Loader; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -53,6 +56,7 @@ import com.ferg.awfulapp.constants.Constants; import com.ferg.awfulapp.network.NetworkUtils; import com.ferg.awfulapp.preferences.AwfulPreferences; +import com.ferg.awfulapp.preferences.Keys; import com.ferg.awfulapp.provider.AwfulProvider; import com.ferg.awfulapp.provider.ColorProvider; import com.ferg.awfulapp.provider.DatabaseHelper; @@ -157,6 +161,7 @@ public void onPageNumberClicked() { refreshProbationBar(); + getAwfulActivity().setPreferredFont(result); return result; } @@ -239,6 +244,42 @@ public void onStop() { // TODO: cancel network reqs? } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.forum_display_fragment, menu); + + MenuItem postThread = menu.findItem(R.id.post_thread); + postThread.setVisible(getForumId() != Constants.USERCP_ID); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.post_thread: + displayPostThreadDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void displayPostThreadDialog() { + if(AwfulPreferences.getInstance().postWarningAccepted) { + displayPostThreadDialog(getForumId()); + return; + } + new AlertDialog.Builder(getAwfulActivity()) + .setIcon(R.drawable.ic_gavel_dark_24dp) + .setTitle("Warning") + .setMessage(R.string.post_warning) + .setPositiveButton("I accept", (dialog, which) -> { + displayPostThreadDialog(getForumId()); + AwfulPreferences.getInstance().setPreference(Keys.POST_WARNING_ACCEPTED, true); + }) + .setNegativeButton("Nope", (dialog, which) -> dialog.dismiss()) + .setCancelable(false) + .show(); + + } @Override public void onCreateContextMenu(ContextMenu aMenu, View aView, ContextMenuInfo aMenuInfo) { diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexActivity.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexActivity.kt index b783e23ca..0a8bf955d 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexActivity.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexActivity.kt @@ -275,7 +275,9 @@ class ForumsIndexActivity : } fun allowSwipe() { - runOnUiThread { forumsPager.setSwipeEnabled(true) } + if(!mPrefs.lockScrolling) { + runOnUiThread { forumsPager.setSwipeEnabled(true) } + } } override fun onWindowFocusChanged(hasFocus: Boolean) { diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexFragment.java index 4e1b5f9ca..155438d5c 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/ForumsIndexFragment.java @@ -95,6 +95,7 @@ public View onCreateView(@NonNull LayoutInflater aInflater, ViewGroup aContainer updateViewColours(); refreshProbationBar(); forumsListSwitcher.setInAnimation(AnimationUtils.makeInAnimation(getContext(), true)); + getAwfulActivity().setPreferredFont(view); return view; } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/MessageFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/MessageFragment.java index 4cd24ea95..18f01ebae 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/MessageFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/MessageFragment.java @@ -135,6 +135,8 @@ public View onCreateView(LayoutInflater aInflater, ViewGroup aContainer, Bundle }else{ syncPM(); } + + getAwfulActivity().setPreferredFont(result); return result; } @@ -224,7 +226,8 @@ private void showSubmitDialog() { (dialog, button) -> { if (mDialog == null && getActivity() != null) { mDialog = ProgressDialog.show(getActivity(), "Sending", "Hopefully it didn't suck...", true, true); - } + getAwfulActivity().setPreferredFont(mDialog.findViewById(android.R.id.title)); + } saveReply(); sendPM(); }) @@ -399,6 +402,7 @@ public void onLoadFinished(Loader aLoader, Cursor aData) { }else{ mSubject.setText(title); } + getAwfulActivity().setPreferredFont(mSubject); String author = aData.getString(aData.getColumnIndex(AwfulMessage.AUTHOR)); mUsername.setText("Sender: " + author); String recip = aData.getString(aData.getColumnIndex(AwfulMessage.RECIPIENT)); @@ -407,11 +411,13 @@ public void onLoadFinished(Loader aLoader, Cursor aData) { }else{ mRecipient.setText(author); } + }else{ if(recipient != null){ mRecipient.setText(recipient); } } + getAwfulActivity().setPreferredFont(mRecipient); } @Override diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/NavigationDrawer.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/NavigationDrawer.kt index 3997ada86..bc4d9bed2 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/NavigationDrawer.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/NavigationDrawer.kt @@ -11,6 +11,7 @@ import androidx.appcompat.widget.Toolbar import android.view.MenuItem import android.widget.ImageView import android.widget.TextView +import androidx.core.view.forEach import com.android.volley.VolleyError import com.android.volley.toolbox.ImageLoader import com.ferg.awfulapp.ForumDisplayFragment.NULL_FORUM_ID @@ -84,6 +85,10 @@ class NavigationDrawer(val activity: AwfulActivity, toolbar: Toolbar, val prefs: prefs.registerCallback { _, _ -> refresh() } AnnouncementsManager.getInstance().registerListener { _, _, _, _ -> refresh() } refresh() + activity.setPreferredFont(navigationMenu) + navigationMenu.menu.forEach { + FontManager.getInstance().setMenuItemFont(it) + } } private fun menuItem(resId: Int) = navigationMenu.menu.findItem(resId) @@ -151,6 +156,9 @@ class NavigationDrawer(val activity: AwfulActivity, toolbar: Toolbar, val prefs: val unread = AnnouncementsManager.getInstance().unreadCount announcementsItem.title = getString(R.string.announcements) + if (unread == 0) "" else " ($unread)" + + activity.setPreferredFont(navigationMenu) + FontManager.getInstance().setMenuItemFont(announcementsItem) } fun setCurrentForumAndThread(forumId: Int?, threadId: Int?) { @@ -195,6 +203,7 @@ private class HierarchyTextUpdater( private val appContext = context.applicationContext private val forumMenuItem = WeakReference(forumItem) private val threadMenuItem = WeakReference(threadItem) + private val fm = FontManager.getInstance() override fun doInBackground(vararg params: Void?): Void? { threadName = StringProvider.getThreadName(appContext, threadId) @@ -205,6 +214,8 @@ private class HierarchyTextUpdater( override fun onPostExecute(aVoid: Void?) { forumMenuItem.get()?.title = forumName ?: "" threadMenuItem.get()?.title = threadName ?: "" + fm.setMenuItemFont(forumMenuItem.get()) + fm.setMenuItemFont(threadMenuItem.get()) } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/PostReplyFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/PostReplyFragment.java index aa8cb900b..017c85dc9 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/PostReplyFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/PostReplyFragment.java @@ -172,6 +172,7 @@ public View onCreateView(LayoutInflater aInflater, ViewGroup aContainer, Bundle super.onCreateView(aInflater, aContainer, aSavedState); Timber.v("onCreateView"); View view = inflateView(R.layout.post_reply, aContainer, aInflater); + getAwfulActivity().setPreferredFont(view); return view; } @@ -226,13 +227,15 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { int permissionCheck = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { this.attachmentData = data; - requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, Constants.AWFUL_PERMISSION_READ_EXTERNAL_STORAGE); - } else { - addAttachment(data); + if (AwfulUtils.isTiramisu33()) { + requestPermissions(new String[]{Manifest.permission.READ_MEDIA_IMAGES}, Constants.AWFUL_PERMISSION_READ_MEDIA_IMAGES); + } else { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, Constants.AWFUL_PERMISSION_READ_EXTERNAL_STORAGE); + } + return; } - } else { - addAttachment(data); } + addAttachment(data); } } } @@ -242,6 +245,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case Constants.AWFUL_PERMISSION_READ_EXTERNAL_STORAGE: + case Constants.AWFUL_PERMISSION_READ_MEDIA_IMAGES: // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { addAttachment(); @@ -278,7 +282,7 @@ private void refreshThreadInfo() { */ private void loadReply(int mReplyType, int mThreadId, int mPostId) { progressDialog = ProgressDialog.show(getActivity(), "Loading", "Fetching Message...", true, true); - + getAwfulActivity().setPreferredFont(progressDialog.findViewById(android.R.id.title)); // create a callback to handle the reply data from the site AwfulRequest.AwfulResultCallback loadCallback = new AwfulRequest.AwfulResultCallback() { @Override @@ -408,7 +412,7 @@ private void displayDraftAlert(@NonNull SavedDraft draft) { String message = String.format(template, type.toLowerCase(), previewText, epochToSimpleDuration(draft.timestamp)); String positiveLabel = (mReplyType == TYPE_QUOTE) ? "Add" : "Use"; - new AlertDialog.Builder(activity) + AlertDialog use = new AlertDialog.Builder(activity) .setIcon(R.drawable.ic_reply_dark) .setTitle(type) .setMessage(Html.fromHtml(message)) @@ -424,6 +428,11 @@ private void displayDraftAlert(@NonNull SavedDraft draft) { // avoid accidental draft losses by forcing a decision .setCancelable(false) .show(); + + getAwfulActivity().setPreferredFont(use.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.button2)); } @@ -436,12 +445,13 @@ private void displayDraftAlert(@NonNull SavedDraft draft) { * Display a dialog allowing the user to submit or preview their post */ private void showSubmitDialog() { - new AlertDialog.Builder(getActivity()) + AlertDialog submit = new AlertDialog.Builder(getActivity()) .setTitle(String.format("Confirm %s?", mReplyType == TYPE_EDIT ? "Edit" : "Post")) .setPositiveButton(R.string.submit, (dialog, button) -> { if (progressDialog == null && getActivity() != null) { progressDialog = ProgressDialog.show(getActivity(), "Posting", "Hopefully it didn't suck...", true, true); + getAwfulActivity().setPreferredFont(progressDialog.findViewById(android.R.id.title)); } saveReply(); submitPost(); @@ -450,6 +460,12 @@ private void showSubmitDialog() { .setNegativeButton(R.string.cancel, (dialog, button) -> { }) .show(); + + getAwfulActivity().setPreferredFont(submit.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button2)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button3)); } @@ -644,7 +660,7 @@ void onNavigateBack() { leave(RESULT_CANCELLED); return; } - new AlertDialog.Builder(activity) + AlertDialog save = new AlertDialog.Builder(activity) .setIcon(R.drawable.ic_reply_dark) .setMessage(String.format("Save this %s?", mReplyType == TYPE_EDIT ? "edit" : "post")) .setPositiveButton(R.string.save, (dialog, button) -> { @@ -662,6 +678,11 @@ void onNavigateBack() { .setCancelable(true) .show(); + getAwfulActivity().setPreferredFont(save.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button2)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button3)); } @@ -1044,6 +1065,7 @@ private void updateThreadTitle() { if (threadTitleView != null) { threadTitleView.setText(mThreadTitle == null ? "" : mThreadTitle); } + getAwfulActivity().setPreferredFont(threadTitleView); } @Override diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/ImageViewFragment.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadActivity.java similarity index 52% rename from Awful.apk/src/main/java/com/ferg/awfulapp/ImageViewFragment.kt rename to Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadActivity.java index 3c6c52641..e73c3e17a 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/ImageViewFragment.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadActivity.java @@ -1,7 +1,7 @@ /******************************************************************************** * Copyright (c) 2011, Scott Ferguson * All rights reserved. - + *

    * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright @@ -12,7 +12,7 @@ * * Neither the name of the software nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - + *

    * THIS SOFTWARE IS PROVIDED BY SCOTT FERGUSON ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -23,55 +23,60 @@ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + *******************************************************************************/ -package com.ferg.awfulapp +package com.ferg.awfulapp; -import android.graphics.Bitmap -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import com.bumptech.glide.Glide +import android.os.Bundle; +import android.view.MenuItem; +import com.ferg.awfulapp.databinding.PostThreadActivityBinding; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.FragmentManager; +public class PostThreadActivity extends AwfulActivity { -/** - * Loads and displays an image in a zoomable view. - */ -class ImageViewFragment : AwfulFragment() { - private var imageUrl = "No image url" + Toolbar mToolbar; + PostThreadFragment threadFragment; - companion object { - const val EXTRA_IMAGE_URL = "image url" - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflateView(R.layout.image_view_fragment, container, inflater) - } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + PostThreadActivityBinding binding = PostThreadActivityBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + mToolbar = binding.toolbar; - override fun onActivityCreated(aSavedState: Bundle?) { - super.onActivityCreated(aSavedState) - val mImageView = requireActivity().findViewById(R.id.iv_photo) as ImageView - val imageUrl = requireActivity().intent.getStringExtra(EXTRA_IMAGE_URL); + setSupportActionBar(mToolbar); + setUpActionBar(); - Glide - .with(mImageView) - .load(imageUrl) - .into(mImageView); + FragmentManager fm = getSupportFragmentManager(); + threadFragment = (PostThreadFragment) fm.findFragmentById(R.id.thread_fragment); + } + @Override + protected void onStart() { + super.onStart(); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onLeaveActivity(); + break; + } + return super.onOptionsItemSelected(item); + } - setActionBarTitle(imageUrl!!) + @Override + public void onBackPressed() { + onLeaveActivity(); } - override fun getTitle(): String = imageUrl + private void onLeaveActivity() { + threadFragment.onNavigateBack(); + } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadFragment.java new file mode 100644 index 000000000..554225d25 --- /dev/null +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/PostThreadFragment.java @@ -0,0 +1,1090 @@ +/** + * ***************************************************************************** + * Copyright (c) 2011, Scott Ferguson + * All rights reserved. + *

    + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the software nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + *

    + * THIS SOFTWARE IS PROVIDED BY SCOTT FERGUSON ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL SCOTT FERGUSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ***************************************************************************** + */ + +package com.ferg.awfulapp; + +import android.Manifest; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.Html; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.Toast; + +import com.android.volley.VolleyError; +import com.ferg.awfulapp.constants.Constants; +import com.ferg.awfulapp.network.NetworkUtils; +import com.ferg.awfulapp.preferences.AwfulPreferences; +import com.ferg.awfulapp.provider.AwfulProvider; +import com.ferg.awfulapp.provider.ColorProvider; +import com.ferg.awfulapp.task.AwfulRequest; +import com.ferg.awfulapp.task.PreviewThreadRequest; +import com.ferg.awfulapp.task.SendThreadRequest; +import com.ferg.awfulapp.task.ThreadRequest; +import com.ferg.awfulapp.thread.AwfulForum; +import com.ferg.awfulapp.thread.AwfulMessage; +import com.ferg.awfulapp.thread.AwfulThread; +import com.ferg.awfulapp.reply.MessageComposer; +import com.ferg.awfulapp.util.AwfulUtils; +import com.ferg.awfulapp.widget.ThreadIconPicker; +import com.google.android.material.snackbar.Snackbar; + +import org.apache.commons.lang3.StringUtils; +import org.threeten.bp.Duration; +import org.threeten.bp.Instant; + +import java.io.File; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.CursorLoader; +import androidx.loader.content.Loader; +import timber.log.Timber; + +import static com.ferg.awfulapp.constants.Constants.ATTACHMENT_MAX_BYTES; +import static com.ferg.awfulapp.constants.Constants.ATTACHMENT_MAX_HEIGHT; +import static com.ferg.awfulapp.constants.Constants.ATTACHMENT_MAX_WIDTH; +import static com.ferg.awfulapp.thread.AwfulMessage.REPLY_DISABLE_SMILIES; +import static com.ferg.awfulapp.thread.AwfulMessage.REPLY_SIGNATURE; + +public class PostThreadFragment extends AwfulFragment { + + public static final int REQUEST_THREAD = 5; + public static final int RESULT_POSTED = 6; + public static final int RESULT_CANCELLED = 7; + public static final int ADD_ATTACHMENT = 9; + private static final String TAG = "PostThreadFragment"; + + // UI components + private MessageComposer messageComposer; + @Nullable + private ProgressDialog progressDialog; + + private ThreadIconPicker threadIconPicker; + + private EditText subject; + + // internal state + @Nullable + private SavedDraft savedDraft = null; + @Nullable + private ContentValues threadData = null; + private boolean saveRequired = true; + @Nullable + private Intent attachmentData; + + // async stuff + private ContentResolver mContentResolver; + @NonNull + private final DraftThreadLoaderCallback draftLoaderCallback = new DraftThreadLoaderCallback(); + @NonNull + private final ForumInfoCallback forumInfoCallback = new ForumInfoCallback(); + + // thread metadata + private int mForumId; + + // User's thread data + @Nullable + private String mFileAttachment; + private boolean disableEmotes = false; + private boolean postSignature = false; + + + /////////////////////////////////////////////////////////////////////////// + // Activity and fragment initialisation + /////////////////////////////////////////////////////////////////////////// + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Timber.v("onCreate"); + setHasOptionsMenu(true); + setRetainInstance(false); + } + + + @Override + public View onCreateView(LayoutInflater aInflater, ViewGroup aContainer, Bundle aSavedState) { + super.onCreateView(aInflater, aContainer, aSavedState); + Timber.v("onCreateView"); + View view = inflateView(R.layout.post_thread, aContainer, aInflater); + getAwfulActivity().setPreferredFont(view); + + return view; + } + + @Override + public void onActivityCreated(Bundle aSavedState) { + super.onActivityCreated(aSavedState); + Timber.v("onActivityCreated"); + Activity activity = getActivity(); + + messageComposer = (MessageComposer) getChildFragmentManager().findFragmentById(R.id.message_composer_fragment); + messageComposer.setBackgroundColor(ColorProvider.BACKGROUND.getColor()); + messageComposer.setTextColor(ColorProvider.PRIMARY_TEXT.getColor()); + + // grab all the important thread params + Intent intent = activity.getIntent(); + mForumId = intent.getIntExtra(Constants.POST_FORUM_ID, 0); + setActionBarTitle(getTitle()); + + threadIconPicker = (ThreadIconPicker) getFragmentManager().findFragmentById(R.id.thread_icon_picker); + threadIconPicker.useForumIcons(mForumId); + + subject = (EditText) activity.findViewById(R.id.thread_subject); + + // perform some sanity checking + boolean badRequest = false; + if (mForumId < 0 || mForumId == 0) { + // we always need a valid forum ID + badRequest = true; + } + if (badRequest) { + Toast.makeText(activity, "Can't create thread! Bad parameters", Toast.LENGTH_LONG).show(); + String template = "Failed to init thread activity%n Forum ID: %d"; + Timber.w(template, mForumId); + activity.finish(); + } + + mContentResolver = activity.getContentResolver(); + // load any related stored draft before starting the thread request + // TODO: 06/04/2017 probably better to handle this as two separate, completable requests - combine thread and draft data when they're both finished, instead of assuming the draft loader finishes first + getStoredDraft(); + refreshForumInfo(); + loadThread(mForumId); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == ADD_ATTACHMENT) { + if (AwfulUtils.isMarshmallow23()) { + int permissionCheck = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE); + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + this.attachmentData = data; + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, Constants.AWFUL_PERMISSION_READ_EXTERNAL_STORAGE); + } else { + addAttachment(data); + } + } else { + addAttachment(data); + } + } + } + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode) { + case Constants.AWFUL_PERMISSION_READ_EXTERNAL_STORAGE: + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + addAttachment(); + } else { + Toast.makeText(getActivity(), R.string.no_file_permission_attachment, Toast.LENGTH_LONG).show(); + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + + private void getStoredDraft() { + restartLoader(Constants.THREAD_DRAFT_LOADER_ID, null, draftLoaderCallback); + } + + private void refreshForumInfo() { + restartLoader(Constants.FORUM_LOADER_ID, null, forumInfoCallback); + } + + + /////////////////////////////////////////////////////////////////////////// + // Fetching data/drafts and populating editor + /////////////////////////////////////////////////////////////////////////// + + + /** + * Initiate a new thread by passing a request to the site and handling its response. + * + * @param mForumId The ID of the forum + */ + private void loadThread(int mForumId) { + progressDialog = ProgressDialog.show(getActivity(), "Loading", "Fetching Message...", true, true); + getAwfulActivity().setPreferredFont(progressDialog.findViewById(android.R.id.title)); + + // create a callback to handle the thread data from the site + AwfulRequest.AwfulResultCallback loadCallback = new AwfulRequest.AwfulResultCallback() { + @Override + public void success(ContentValues result) { + threadData = result; + // set any options and update the menu + postSignature = getCheckedAndRemove(REPLY_SIGNATURE, result); + disableEmotes = getCheckedAndRemove(REPLY_DISABLE_SMILIES, result); + invalidateOptionsMenu(); + dismissProgressDialog(); + handleDraft(); + } + + @Override + public void failure(VolleyError error) { + dismissProgressDialog(); + //allow time for the error to display, then close the window + getHandler().postDelayed(() -> leave(RESULT_CANCELLED), 3000); + } + }; + queueRequest(new ThreadRequest(getActivity(), mForumId).build(this, loadCallback)); + } + + /** + * Removes a key from a ContentValues, returning true if it was set to "checked" + */ + private boolean getCheckedAndRemove(@NonNull String key, @NonNull ContentValues values) { + if (!values.containsKey(key)) { + return false; + } + boolean checked = "checked".equals(values.getAsString(key)); + values.remove(key); + return checked; + } + + + /** + * Take care of any saved draft, allowing the user to use it if appropriate. + */ + private void handleDraft() { + // this implicitly relies on the Draft Thread Loader having already finished, assigning to savedDraft if it found any draft data + if (savedDraft == null) { + return; + } + /* + This is where we decide whether to load an existing draft, or ignore it. + The saved draft will end up getting replaced/deleted anyway (when the post is either posted or saved), + this just decides whether it's relevant to the current context, and the user needs to know about it. + + We basically ignore a draft if: + - we're currently editing a post, and the draft isn't an edit + - the draft is an edit, but not for this post + in both cases we need to avoid replacing the original post (that we're trying to edit) with some other post's draft + */ + + // got a useful draft, let the user decide what to do with it + displayDraftAlert(savedDraft); + } + + + /** + * Display a dialog allowing the user to use or discard an existing draft. + * + * @param draft a draft message relevant to this post + */ + private void displayDraftAlert(@NonNull SavedDraft draft) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + String template = "You have a %s:" + + "
    %s:

    " + + "%s" + + "

    " + + "Saved %s ago"; + + String type = "Saved Thread"; + + final int MAX_PREVIEW_LENGTH = 140; + String previewText = StringUtils.substring(draft.content, 0, MAX_PREVIEW_LENGTH).replaceAll("\\n", "
    "); + if (draft.content.length() > MAX_PREVIEW_LENGTH) { + previewText += "..."; + } + + String message = String.format(template, type, draft.subject , previewText, epochToSimpleDuration(draft.timestamp)); + AlertDialog use = new AlertDialog.Builder(activity) + .setIcon(R.drawable.ic_reply_dark) + .setTitle(type) + .setMessage(Html.fromHtml(message)) + .setPositiveButton("Use", (dialog, which) -> { + String newContent = draft.content; + // If we're quoting something, stick it after the draft thread (and add some whitespace too) + messageComposer.setText(newContent, true); + subject.setText(draft.subject); + if(draft.iconId != null && draft.iconUrl != null && draft.iconUrl.length() > 0){ + threadIconPicker.useIcon(draft.iconId, draft.iconUrl); + } + }) + .setNegativeButton(R.string.discard, (dialog, which) -> deleteSavedThread()) + // avoid accidental draft losses by forcing a decision + .setCancelable(false) + .show(); + + getAwfulActivity().setPreferredFont(use.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(use.findViewById(android.R.id.button2)); + } + + + /////////////////////////////////////////////////////////////////////////// + // Send/preview posts + /////////////////////////////////////////////////////////////////////////// + + + /** + * Display a dialog allowing the user to submit or preview their post + */ + private void showSubmitDialog() { + AlertDialog submit = new AlertDialog.Builder(getActivity()) + .setTitle("Confirm Post?") + .setPositiveButton(R.string.submit, + (dialog, button) -> { + if (progressDialog == null && getActivity() != null) { + progressDialog = ProgressDialog.show(getActivity(), "Posting", "Hopefully it didn't suck...", true, true); + getAwfulActivity().setPreferredFont(progressDialog.findViewById(android.R.id.title)); + } + saveThread(); + submitThread(); + }) + .setNeutralButton(R.string.preview, (dialog, button) -> previewPost()) + .setNegativeButton(R.string.cancel, (dialog, button) -> { + }).show(); + + getAwfulActivity().setPreferredFont(submit.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button2)); + getAwfulActivity().setPreferredFont(submit.findViewById(android.R.id.button3)); + + } + + + /** + * Actually submit the post/edit to the site. + */ + private void submitThread() { + ContentValues cv = prepareCV(); + if (cv == null) { + return; + } + AwfulRequest.AwfulResultCallback postCallback = new AwfulRequest.AwfulResultCallback() { + @Override + public void success(Void result) { + dismissProgressDialog(); + deleteSavedThread(); + saveRequired = false; + + Context context = getContext(); + if (context != null) { + Toast.makeText(context, context.getString(R.string.post_sent), Toast.LENGTH_LONG).show(); + } + mContentResolver.notifyChange(AwfulThread.CONTENT_URI, null); + leave(RESULT_POSTED); + } + + @Override + public void failure(VolleyError error) { + dismissProgressDialog(); + saveThread(); + } + }; + queueRequest(new SendThreadRequest(getActivity(), cv).build(this, postCallback)); + } + + + /** + * Request a preview of the current post from the site, and display it. + */ + private void previewPost() { + ContentValues cv = prepareCV(); + Activity activity = getActivity(); + FragmentManager fragmentManager = getFragmentManager(); + if (cv == null || activity == null || fragmentManager == null) { + return; + } + + final PreviewFragment previewFrag = new PreviewFragment(); + previewFrag.setStyle(DialogFragment.STYLE_NO_TITLE, 0); + previewFrag.show(fragmentManager, "Post Preview"); + + AwfulRequest.AwfulResultCallback previewCallback = new AwfulRequest.AwfulResultCallback() { + @Override + public void success(final String result) { + previewFrag.setContent(result); + } + + @Override + public void failure(VolleyError error) { + // love dialogs and callbacks very elegant + if (!previewFrag.isStateSaved() && previewFrag.getActivity() != null && !previewFrag.getActivity().isFinishing()) { + previewFrag.dismiss(); + } + if (getView() != null) { + Snackbar.make(getView(), "Preview failed.", Snackbar.LENGTH_LONG) + .setAction("Retry", v -> previewPost()).show(); + } + } + }; + + + queueRequest(new PreviewThreadRequest(getActivity(), cv).build(this, previewCallback)); + } + + + /** + * Create a ContentValues representing the current post and its options. + *

    + * Returns null if the data is invalid, e.g. an empty post + * + * @return The post data, or null if there was an error. + */ + @Nullable + private ContentValues prepareCV() { + if (threadData == null || threadData.getAsInteger(AwfulMessage.ID) == null) { + // TODO: if this ever happens, the ID never gets set (and causes an NPE in SendPostRequest) - handle this in a better way? + // Could use the mThreadId value, but that might be incorrect at this point and post to the wrong thread? Is null thread data an exceptional event? + Log.e(TAG, "No thread data in sendPost() - no thread ID to post to!"); + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, "Unknown thread ID - can't post!", Toast.LENGTH_LONG).show(); + } + return null; + } + ContentValues cv = new ContentValues(threadData); + if (isOPEmpty()) { + dismissProgressDialog(); + getAlertView().setTitle(R.string.message_empty) + .setSubtitle(R.string.message_empty_subtext) + .show(); + return null; + } + if (!TextUtils.isEmpty(mFileAttachment)) { + cv.put(AwfulMessage.REPLY_ATTACHMENT, mFileAttachment); + } + if (postSignature) { + cv.put(REPLY_SIGNATURE, Constants.YES); + } + if (disableEmotes) { + cv.put(AwfulMessage.REPLY_DISABLE_SMILIES, Constants.YES); + } + + cv.put(AwfulMessage.POST_SUBJECT, subject.getText().toString()); + cv.put(AwfulMessage.POST_ICON_ID, threadIconPicker.getIcon().iconId); + cv.put(AwfulMessage.POST_ICON_URL, threadIconPicker.getIcon().iconUrl); + cv.put(AwfulMessage.POST_CONTENT, messageComposer.getText()); + return cv; + } + + + /////////////////////////////////////////////////////////////////////////// + // Lifecycle/navigation stuff + /////////////////////////////////////////////////////////////////////////// + + + @Override + public void onResume() { + super.onResume(); + Timber.v("onResume"); + } + + @Override + public void onPause() { + super.onPause(); + Timber.v("onPause"); + cleanupTasks(); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + Log.e(TAG, "onDestroyView"); + // final cleanup - some should have already been done in onPause (draft saving etc) + getLoaderManager().destroyLoader(Constants.THREAD_DRAFT_LOADER_ID); + getLoaderManager().destroyLoader(Constants.FORUM_LOADER_ID); + } + + /** + * Tasks to perform when the thread window moves from the foreground. + * Basically saves a draft if required, and hides elements like the keyboard + */ + private void cleanupTasks() { + autoSave(); + dismissProgressDialog(); + messageComposer.hideKeyboard(); + } + + + /** + * Finish the thread activity, performing cleanup and returning a result code to the activity that created it. + */ + private void leave(int activityResult) { + final AwfulActivity activity = getAwfulActivity(); + if (activity != null) { + activity.setResult(activityResult); + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null && getView() != null) { + imm.hideSoftInputFromWindow(getView().getApplicationWindowToken(), 0); + } + activity.finish(); + } + } + + + /** + * Call this when the user tries to leave the activity, so the Save/Discard dialog can be shown if necessary. + */ + void onNavigateBack() { + Activity activity = getActivity(); + if (activity == null) { + return; + } else if (isOPEmpty()) { + leave(RESULT_CANCELLED); + return; + } + AlertDialog save = new AlertDialog.Builder(activity) + .setIcon(R.drawable.ic_reply_dark) + .setMessage("Save this thread?") + .setPositiveButton(R.string.save, (dialog, button) -> { + // let #autoSave handle it on leaving + saveRequired = true; + leave(RESULT_CANCELLED); + }) + .setNegativeButton(R.string.discard, (dialog, which) -> { + deleteSavedThread(); + saveRequired = false; + leave(RESULT_CANCELLED); + }) + .setNeutralButton(R.string.cancel, (dialog, which) -> { + }) + .setCancelable(true) + .show(); + + + getAwfulActivity().setPreferredFont(save.findViewById(androidx.appcompat.R.id.alertTitle)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.message)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button1)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button2)); + getAwfulActivity().setPreferredFont(save.findViewById(android.R.id.button3)); + } + + + /////////////////////////////////////////////////////////////////////////// + // Saving draft data + /////////////////////////////////////////////////////////////////////////// + + + /** + * Trigger a draft save, if required. + */ + private void autoSave() { + if (saveRequired && messageComposer != null) { + if (isOPEmpty()) { + Log.i(TAG, "Message unchanged, discarding."); + // TODO: 12/02/2017 does this actually need to check if it's unchanged? + deleteSavedThread();//if the thread is unchanged, throw it out. + messageComposer.setText(null, false); + } else { + Log.i(TAG, "Message Unsent, saving."); + saveThread(); + } + } + } + + + /** + * Delete any saved thread for the current thread + */ + private void deleteSavedThread() { + mContentResolver.delete(AwfulMessage.CONTENT_URI_THREAD, AwfulMessage.ID + "=?", AwfulProvider.int2StrArray(mForumId)); + } + + + /** + * Save a draft thread for the current thread. + */ + private void saveThread() { + if (getActivity() != null && mForumId > 0 && messageComposer != null) { + String content = messageComposer.getText(); + // don't save if the message is empty/whitespace + // not trimming the actual content, so we retain any whitespace e.g. blank lines after quotes + if (!content.trim().isEmpty()) { + Log.i(TAG, "Saving thread! " + content); + ContentValues post = (threadData == null) ? new ContentValues() : new ContentValues(threadData); + post.put(AwfulMessage.ID, mForumId); + post.put(AwfulMessage.POST_CONTENT, content); + post.put(AwfulMessage.EPOC_TIMESTAMP, System.currentTimeMillis()); + post.put(AwfulMessage.POST_SUBJECT, subject.getText().toString()); + post.put(AwfulMessage.POST_ICON_ID, threadIconPicker.getIcon().iconId); + post.put(AwfulMessage.POST_ICON_URL, threadIconPicker.getIcon().iconUrl); + if (mFileAttachment != null) { + post.put(AwfulMessage.REPLY_ATTACHMENT, mFileAttachment); + } + if (mContentResolver.update(ContentUris.withAppendedId(AwfulMessage.CONTENT_URI_THREAD, mForumId), post, null, null) < 1) { + mContentResolver.insert(AwfulMessage.CONTENT_URI_THREAD, post); + } + } + } + } + + + /////////////////////////////////////////////////////////////////////////// + // Menus + /////////////////////////////////////////////////////////////////////////// + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + Timber.v("onCreateOptionsMenu"); + inflater.inflate(R.menu.post_thread, menu); + + MenuItem attach = menu.findItem(R.id.add_attachment); + if (attach != null && getPrefs() != null) { + attach.setEnabled(getPrefs().hasPlatinum); + attach.setVisible(getPrefs().hasPlatinum); + } + MenuItem remove = menu.findItem(R.id.remove_attachment); + if (remove != null && getPrefs() != null) { + remove.setEnabled((getPrefs().hasPlatinum && this.mFileAttachment != null)); + remove.setVisible(getPrefs().hasPlatinum && this.mFileAttachment != null); + } + MenuItem disableEmoticons = menu.findItem(R.id.disableEmots); + if (disableEmoticons != null) { + disableEmoticons.setChecked(disableEmotes); + } + MenuItem sig = menu.findItem(R.id.signature); + if (sig != null) { + sig.setChecked(postSignature); + } + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Timber.v("onOptionsItemSelected"); + switch (item.getItemId()) { + case R.id.submit_button: + showSubmitDialog(); + break; + case R.id.add_attachment: + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + startActivityForResult(Intent.createChooser(intent, + "Select Picture"), ADD_ATTACHMENT); + break; + case R.id.remove_attachment: + this.mFileAttachment = null; + Toast removeToast = Toast.makeText(getAwfulActivity(), getAwfulActivity().getResources().getText(R.string.file_removed), Toast.LENGTH_SHORT); + removeToast.show(); + invalidateOptionsMenu(); + break; + case R.id.signature: + item.setChecked(!item.isChecked()); + postSignature = item.isChecked(); + break; + case R.id.disableEmots: + item.setChecked(!item.isChecked()); + disableEmotes = item.isChecked(); + break; + default: + return super.onOptionsItemSelected(item); + } + + return true; + } + + + @Override + public void onPreferenceChange(AwfulPreferences prefs, String key) { + super.onPreferenceChange(prefs, key); + //refresh the menu to show/hide attach option (plat only) + invalidateOptionsMenu(); + } + + + /////////////////////////////////////////////////////////////////////////// + // Attachment handling + /////////////////////////////////////////////////////////////////////////// + + // TODO: 13/04/2017 make a separate attachment component and stick all this in there + + private void addAttachment() { + addAttachment(attachmentData); + attachmentData = null; + } + + private void addAttachment(Intent data) { + Uri selectedImageUri = data.getData(); + String path = getFilePath(selectedImageUri); + if (path == null) { + setAttachment(null, getString(R.string.file_error)); + return; + } + + File attachment = new File(path); + String filename = attachment.getName(); + if (!attachment.isFile() || !attachment.canRead()) { + setAttachment(null, String.format(getString(R.string.file_unreadable), filename)); + return; + } else if (!StringUtils.endsWithAny(filename.toLowerCase(), ".jpg", ".jpeg", ".png", ".gif")) { + setAttachment(null, String.format(getString(R.string.file_wrong_filetype), filename)); + return; + } else if (attachment.length() > ATTACHMENT_MAX_BYTES) { + setAttachment(null, String.format(getString(R.string.file_too_big), filename)); + return; + } + + // check the image size without creating a bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); + int height = options.outHeight; + int width = options.outWidth; + if (width > ATTACHMENT_MAX_WIDTH || height > ATTACHMENT_MAX_HEIGHT) { + setAttachment(null, String.format(getString(R.string.file_resolution_too_big), filename, width, height)); + return; + } + + setAttachment(path, String.format(getString(R.string.file_attached), filename)); + } + + + private void setAttachment(@Nullable String attachment, @NonNull String toastMessage) { + mFileAttachment = attachment; + Toast.makeText(getActivity(), toastMessage, Toast.LENGTH_LONG).show(); + invalidateOptionsMenu(); + } + + + private String getFilePath(final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(this.getActivity(), uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + private String getDataColumn(Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = this.getActivity().getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + private boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + + /////////////////////////////////////////////////////////////////////////// + // Misc utility stuff + /////////////////////////////////////////////////////////////////////////// + + + /** + * Utility method to check if the composer contains an empty post + */ + private boolean isOPEmpty() { + return messageComposer.getText().trim().isEmpty(); + } + + /** + * Convert an epoch timestamp to a duration relative to now. + *

    + * Returns the duration in a "1d 4h 22m 30s" format, omitting units with zero values. + */ + private String epochToSimpleDuration(long epoch) { + Duration diff = Duration.between(Instant.ofEpochSecond((epoch / 1000)), Instant.now()).abs(); + String time = ""; + if (diff.toDays() > 0) { + time += " " + diff.toDays() + "d"; + diff = diff.minusDays(diff.toDays()); + } + if (diff.toHours() > 0) { + time += " " + diff.toHours() + "h"; + diff = diff.minusHours(diff.toHours()); + } + if (diff.toMinutes() > 0) { + time += " " + diff.toMinutes() + "m"; + diff = diff.minusMinutes(diff.toMinutes()); + } + + time += " " + diff.getSeconds() + "s"; + return time; + } + + + /////////////////////////////////////////////////////////////////////////// + // UI things + /////////////////////////////////////////////////////////////////////////// + + + /** + * Dismiss the progress dialog and set it to null, if it isn't already. + */ + private void dismissProgressDialog() { + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + progressDialog = null; + } + } + + @Override + public String getTitle() { + return "Post Thread"; + } + + + /////////////////////////////////////////////////////////////////////////// + // Async classes etc + /////////////////////////////////////////////////////////////////////////// + + + /** + * Provides a Loader that pulls draft data for the current thread from the DB. + */ + private class DraftThreadLoaderCallback implements LoaderManager.LoaderCallbacks { + + public Loader onCreateLoader(int aId, Bundle aArgs) { + Log.i(TAG, "Create Thread Cursor: " + mForumId); + return new CursorLoader(getActivity(), + ContentUris.withAppendedId(AwfulMessage.CONTENT_URI_THREAD, mForumId), + AwfulProvider.DraftThreadProjection, + null, + null, + null); + } + + public void onLoadFinished(Loader aLoader, Cursor aData) { + if (aData.isClosed() || !aData.moveToFirst()) { + // no draft saved for this thread + return; + } + // if there's some quote data, deserialise it into a SavedDraft + String quoteData = aData.getString(aData.getColumnIndex(AwfulMessage.POST_CONTENT)); + if (TextUtils.isEmpty(quoteData)) { + return; + } + String subject = aData.getString(aData.getColumnIndex(AwfulMessage.POST_SUBJECT)); + long draftTimestamp = aData.getLong(aData.getColumnIndex(AwfulMessage.EPOC_TIMESTAMP)); + String draftThread = NetworkUtils.unencodeHtml(quoteData); + + String draftIconId = aData.getString(aData.getColumnIndex(AwfulMessage.POST_ICON_ID)); + String draftIconUrl = aData.getString(aData.getColumnIndex(AwfulMessage.POST_ICON_URL)); + + savedDraft = new SavedDraft(draftThread, subject,draftIconId,draftIconUrl, draftTimestamp); + if (Constants.DEBUG) { + Log.i(TAG, "Saved thread message: " + draftThread); + } + } + + @Override + public void onLoaderReset(Loader aLoader) { + + } + } + + + /** + * Provides a Loader that gets metadata for the current thread, and dsiplays its title + */ + private class ForumInfoCallback implements LoaderManager.LoaderCallbacks { + + public Loader onCreateLoader(int aId, Bundle aArgs) { + return new CursorLoader(getActivity(), ContentUris.withAppendedId(AwfulForum.CONTENT_URI, mForumId), + AwfulProvider.ForumProjection, null, null, null); + } + + public void onLoadFinished(Loader aLoader, Cursor aData) { + Log.v(TAG, "Thread title finished, populating."); + } + + @Override + public void onLoaderReset(Loader aLoader) { + } + } + + + private static class SavedDraft { + @NonNull + private final String content; + private final String iconId; + private final String iconUrl; + private final String subject; + private final long timestamp; + + SavedDraft(@NonNull String content, String subject, String iconId, String iconUrl, long timestamp) { + this.content = content; + this.subject = subject; + this.iconId = iconId; + this.iconUrl = iconUrl; + this.timestamp = timestamp; + } + } +} diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/PreviewFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/PreviewFragment.java index 06b61153e..edd5f855e 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/PreviewFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/PreviewFragment.java @@ -45,7 +45,7 @@ import java.util.HashMap; -public class PreviewFragment extends DialogFragment { +public class PreviewFragment extends AwfulDialogFragment { private AwfulWebView postPreView; private ProgressBar progressBar; @@ -64,6 +64,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, getDialog().setCanceledOnTouchOutside(true); postPreView.setContent(getBlankPage()); + getAwfulActivity().setPreferredFont(dialogView); return dialogView; } @@ -100,4 +101,8 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest wrr) { postPreView.setJavascriptHandler(jsInterface); } + @Override + public String getTitle() { + return "Preview"; + } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/PrivateMessageListFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/PrivateMessageListFragment.java index cc12568a4..7d41a3777 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/PrivateMessageListFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/PrivateMessageListFragment.java @@ -114,6 +114,7 @@ public View onCreateView(LayoutInflater aInflater, ViewGroup aContainer, Bundle mFAB.setOnClickListener(onButtonClick); mFAB.setVisibility((getPrefs().noFAB ? View.GONE : View.VISIBLE)); + getAwfulActivity().setPreferredFont(result); return result; } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/ThreadDisplayFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/ThreadDisplayFragment.java index e92b76d0c..ca6a6bbff 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/ThreadDisplayFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/ThreadDisplayFragment.java @@ -189,6 +189,8 @@ Potentially null views, if layout inflation failed (i.e. the WebView package is private final LinkedList backStack = new LinkedList<>(); private boolean bypassBackStack = false; + private boolean zoomEnabled = false; + private String mTitle = null; private String postJump = ""; private int savedScrollPosition = 0; @@ -538,6 +540,10 @@ public void onPrepareOptionsMenu(Menu menu) { if(yospos != null){ yospos.setVisible(mParentForumId == Constants.FORUM_ID_YOSPOS); } + FontManager fm = FontManager.getInstance(); + for (int i = 0; i < menu.size(); i++) { + fm.setMenuItemFont(menu.getItem(i)); + } } @Override @@ -1212,6 +1218,18 @@ public void resumeSwipe() { ((ForumsIndexActivity)mSelf.getAwfulActivity()).allowSwipe(); } + @JavascriptInterface + public void setZoomEnabled(boolean zoomOn) { + zoomEnabled = zoomOn; + if (zoomOn) { + haltSwipe(); + getSwipyLayout().setEnabled(false); + } else { + resumeSwipe(); + getSwipyLayout().setEnabled(!getPrefs().disablePullNext); + } + } + @JavascriptInterface public void popupText(String text) { Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show(); @@ -1327,13 +1345,10 @@ public void startUrlIntent(String url){ .setSubtitle("None of your apps want to open this " + intentUri.getScheme() + ":\\\\ link. Try installing an app that is less picky") .show(); } - } public void displayImage(String url){ - Intent intent = BasicActivity.Companion.intentFor(ImageViewFragment.class, getActivity(), ""); - intent.putExtra(ImageViewFragment.EXTRA_IMAGE_URL, url); - startActivity(intent); + mThreadView.runJavascript(String.format("showImageZoom('%s')", url)); } @Override @@ -1758,6 +1773,10 @@ private int backStackCount(){ @Override public boolean onBackPressed() { + if(zoomEnabled) { + mThreadView.runJavascript("exitImageZoom()"); + return true; + } if(backStackCount() > 0){ popThread(); return true; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/announcements/AnnouncementsFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/announcements/AnnouncementsFragment.java index f98505411..6ecffc769 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/announcements/AnnouncementsFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/announcements/AnnouncementsFragment.java @@ -54,6 +54,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding = AnnouncementsFragmentBinding.inflate(inflater, container, false); View view = binding.getRoot(); initialiseWebView(); + getAwfulActivity().setPreferredFont(view); return view; } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/constants/Constants.java b/Awful.apk/src/main/java/com/ferg/awfulapp/constants/Constants.java index 47169eef1..3a8b2e2c4 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/constants/Constants.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/constants/Constants.java @@ -43,6 +43,7 @@ public class Constants { public static final String FUNCTION_USERCP = BASE_URL + "/usercp.php"; public static final String FUNCTION_FORUM = BASE_URL + "/forumdisplay.php"; public static final String FUNCTION_THREAD = BASE_URL + "/showthread.php"; + public static final String FUNCTION_POST_THREAD = BASE_URL + "/newthread.php"; public static final String FUNCTION_POST_REPLY = BASE_URL + "/newreply.php"; public static final String FUNCTION_EDIT_POST = BASE_URL + "/editpost.php"; public static final String FUNCTION_MEMBER = BASE_URL + "/member.php"; @@ -177,16 +178,19 @@ public class Constants { public static final int THREAD_INFO_LOADER_ID = 891; public static final int POST_LOADER_ID = 892; public static final int FORUM_INDEX_LOADER_ID = 893; + public static final int THREAD_DRAFT_LOADER_ID = 894; public static final String ACTION_DOSEND = "dosend"; public static final String DESTINATION_TOUSER = "touser"; public static final String PARAM_TITLE = "title"; - public static final String PARAM_MESSAGE = "message"; + public static final String PARAM_MESSAGE = "message"; + public static final String PARAM_SUBJECT = "subject"; public static final String EXTRA_BUNDLE = "extras"; public static final String SUBMIT_REPLY = "Submit Reply"; public static final String PREVIEW_REPLY = "Preview Reply"; + public static final String PREVIEW_POST = "Preview Post"; public static final String YES = "yes";//heh @@ -206,6 +210,8 @@ public class Constants { public static final String REPLY_POST_ID = "reply_post_id"; public static final String REPLY_THREAD_ID = "reply_thread_id"; + public static final String POST_FORUM_ID = "post_forum_id"; + public static final int AWFUL_THREAD_ID = 3571717; public static final int FORUM_ID_SHSC = 22; public static final int FORUM_ID_YOSPOS = 219; @@ -221,6 +227,7 @@ public class Constants { public static final int AWFUL_PERMISSION_READ_EXTERNAL_STORAGE = 123; public static final int AWFUL_PERMISSION_WRITE_EXTERNAL_STORAGE = 124; + public static final int AWFUL_PERMISSION_READ_MEDIA_IMAGES = 125; public enum POST_ICON_REQUEST_TYPES { FORUM_POST, PM diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/dialog/Changelog.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/dialog/Changelog.kt index c9e986147..9a0df2703 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/dialog/Changelog.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/dialog/Changelog.kt @@ -1,8 +1,9 @@ package com.ferg.awfulapp.dialog import android.content.Context -import androidx.appcompat.app.AlertDialog import android.text.Html +import androidx.appcompat.app.AlertDialog +import com.ferg.awfulapp.AwfulActivity import com.ferg.awfulapp.R import org.jsoup.Jsoup import org.jsoup.nodes.Document @@ -32,10 +33,16 @@ object Changelog { } // Build a basic dialog with the changelog html - tag handler required pre-Nougat - AlertDialog.Builder(context) + val changelogAlert = AlertDialog.Builder(context) .setTitle(context.getString(R.string.changelog_dialog_title)) .setMessage(Html.fromHtml(changelogText, null, listTagHandler)) - .setPositiveButton(context.getString(R.string.alert_ok)) { dialog, _ -> dialog.dismiss() }.show() + .setPositiveButton(context.getString(R.string.alert_ok)) { dialog, _ -> dialog.dismiss() }.create() + + changelogAlert.show(); + val activity = context as AwfulActivity; + activity.setPreferredFont(changelogAlert.findViewById(androidx.appcompat.R.id.alertTitle)) + activity.setPreferredFont(changelogAlert.findViewById(android.R.id.message)) + activity.setPreferredFont(changelogAlert.findViewById(android.R.id.button1)) } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/forums/ForumListAdapter.java b/Awful.apk/src/main/java/com/ferg/awfulapp/forums/ForumListAdapter.java index 58bedfaf1..94ffb7de3 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/forums/ForumListAdapter.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/forums/ForumListAdapter.java @@ -16,6 +16,7 @@ import com.bignerdranch.expandablerecyclerview.Model.ParentListItem; import com.bignerdranch.expandablerecyclerview.ViewHolder.ChildViewHolder; import com.bignerdranch.expandablerecyclerview.ViewHolder.ParentViewHolder; +import com.ferg.awfulapp.AwfulActivity; import com.ferg.awfulapp.R; import com.ferg.awfulapp.databinding.ForumIndexItemBinding; import com.ferg.awfulapp.databinding.ForumIndexSubforumItemBinding; @@ -37,6 +38,7 @@ */ public class ForumListAdapter extends ExpandableRecyclerAdapter { + private final AwfulActivity parent; private final AwfulPreferences awfulPrefs; @NonNull private final EventListener eventListener; @@ -54,6 +56,7 @@ private ForumListAdapter(@NonNull Context context, @NonNull EventListener listener, @Nullable AwfulPreferences awfulPreferences) { super(topLevelForums); + parent = (AwfulActivity) context; eventListener = listener; awfulPrefs = awfulPreferences; inflater = LayoutInflater.from(context); @@ -273,6 +276,8 @@ void bind(final TopLevelForum forumItem) { setThemeColours(itemView, binding.forumTitle, binding.forumSubtitle); handleSubtitles(forum, binding.forumSubtitle); + parent.setPreferredFont(itemView); + binding.forumFavouriteMarker.setVisibility(forum.isFavourite() ? VISIBLE : GONE); /* the left section (potentially) has a tag and a dropdown button, anything missing diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/popupmenu/BasePopupMenu.java b/Awful.apk/src/main/java/com/ferg/awfulapp/popupmenu/BasePopupMenu.java index ad8699aa0..9097ef28d 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/popupmenu/BasePopupMenu.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/popupmenu/BasePopupMenu.java @@ -14,6 +14,7 @@ import android.widget.ImageView; import android.widget.TextView; +import com.ferg.awfulapp.AwfulDialogFragment; import com.ferg.awfulapp.R; import com.ferg.awfulapp.databinding.ActionItemBinding; import com.ferg.awfulapp.provider.ColorProvider; @@ -31,7 +32,7 @@ * the menu items as an enum, add whichever items you need, and switch on the enum cases to handle * the user's selection. Just pass the enum as the type parameter for the class. */ -public abstract class BasePopupMenu extends DialogFragment { +public abstract class BasePopupMenu extends AwfulDialogFragment { /** * Can be used to set a callback that is called when an action is clicked. @@ -81,6 +82,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, actionsView.setLayoutManager(new LinearLayoutManager(getContext())); getDialog().setCanceledOnTouchOutside(true); + getAwfulActivity().setPreferredFont(result); return result; } @@ -104,12 +106,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, @NonNull abstract List generateMenuItems(); - /** - * The title to display on the dialog. - */ - @NonNull - abstract String getTitle(); - /** * Called when the user selects one of your menu items. * @@ -147,6 +143,7 @@ private class ActionHolderAdapter extends RecyclerView.Adapter { @Override public ActionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.action_item, parent, false); + getAwfulActivity().setPreferredFont(view); return new ActionHolder(view); } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/AwfulPreferences.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/AwfulPreferences.java index bfc13a7a6..3ab7bac1c 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/AwfulPreferences.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/AwfulPreferences.java @@ -162,6 +162,8 @@ public class AwfulPreferences implements OnSharedPreferenceChangeListener { public boolean immersionMode; public String transformer; + public boolean postWarningAccepted; + // APP VERSION STUFF public int alertIDShown; public int lastVersionSeen; @@ -312,6 +314,7 @@ private void updateValues() { forumIndexShowSections = getPreference(Keys.FORUM_INDEX_SHOW_SECTIONS, true); forumIndexShowSubtitles = getPreference(Keys.FORUM_INDEX_SHOW_SUBTITLES, true); forumIndexHideSubforums = getPreference(Keys.FORUM_INDEX_HIDE_SUBFORUMS, true); + postWarningAccepted = getPreference(Keys.POST_WARNING_ACCEPTED, false); //I have never seen this before oh god } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/Keys.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/Keys.java index e3f32e6d0..ecb8b2048 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/Keys.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/Keys.java @@ -136,6 +136,7 @@ public abstract class Keys { FORUM_INDEX_SHOW_SECTIONS, FORUM_INDEX_SHOW_SUBTITLES, FORUM_INDEX_HIDE_SUBFORUMS, + POST_WARNING_ACCEPTED }) @Retention(RetentionPolicy.SOURCE) public @interface BooleanPreference { @@ -224,4 +225,6 @@ public abstract class Keys { public static final int MARKED_USERS = R.string.pref_key_marked_users; public static final int BLOCKED_AVATAR_URLS = R.string.pref_key_blocked_avatar_urls; + + public static final int POST_WARNING_ACCEPTED = R.string.pref_key_post_warning_accepted; } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/SettingsActivity.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/SettingsActivity.java index bf4b4b117..0df815cd3 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/SettingsActivity.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/SettingsActivity.java @@ -1,18 +1,15 @@ package com.ferg.awfulapp.preferences; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; +import androidx.preference.PreferenceManager; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; @@ -103,6 +100,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.settings); View leftPane = findViewById(R.id.root_fragment_container); + View mainPane = findViewById(R.id.main_fragment_container); if (leftPane != null && leftPane.getVisibility() == View.VISIBLE) { isDualPane = true; } @@ -116,7 +114,7 @@ protected void onCreate(Bundle savedInstanceState) { startFragment = new RootSettings(); } - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); // if there's no previous fragment history being restored, initialise! // we need to start with the root fragment, so it's always under the backstack if (savedInstanceState == null) { @@ -141,6 +139,8 @@ protected void onCreate(Bundle savedInstanceState) { setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); updateTitleBar(); + setPreferredFont(leftPane); + setPreferredFont(mainPane); } @@ -151,7 +151,7 @@ protected void onCreate(Bundle savedInstanceState) { */ @Override public void onBackPressed() { - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); int backStackCount = fm.getBackStackEntryCount(); // don't pop off the first entry in dual-pane mode, it will leave the second pane blank - just exit if (backStackCount == 0 || isDualPane && backStackCount == 1) { @@ -207,7 +207,7 @@ private void displayFragment(SettingsFragment fragment, boolean addedFromRoot) { */ // if we're opening a submenu and there's already one open, wipe it from the back stack - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); if (addedFromRoot) { // when a root submenu is clicked, we need a new submenu backstack clearBackStack(fm); @@ -239,7 +239,7 @@ private void updateTitleBar() { if (actionBar == null) { return; } - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); // make sure fragment transactions are finished before we poke around in there fm.executePendingTransactions(); // if there's a submenu fragment present, get the title from that @@ -257,7 +257,7 @@ private void updateTitleBar() { public void onPreferenceChange(AwfulPreferences preferences, String key) { // update the summaries on any loaded fragments for (String tag : new String[]{ROOT_FRAGMENT_TAG, SUBMENU_FRAGMENT_TAG}) { - SettingsFragment fragment = (SettingsFragment) getFragmentManager().findFragmentByTag(tag); + SettingsFragment fragment = (SettingsFragment) getSupportFragmentManager().findFragmentByTag(tag); if (fragment != null) { fragment.setSummaries(); } @@ -300,34 +300,4 @@ private void exportSettings(Intent data) { boolean success = (settingsUri != null && AwfulPreferences.getInstance(this).exportSettings(settingsUri)); Toast.makeText(this, (success ? "Settings exported!" : "Failed to export"), Toast.LENGTH_SHORT).show(); } - - @Override - protected Dialog onCreateDialog(int dialogId) { - switch (dialogId) { - case DIALOG_ABOUT: - CharSequence app_version = getText(R.string.app_name); - try { - app_version = app_version + " " + - getPackageManager().getPackageInfo(getPackageName(), 0) - .versionName; - } catch (PackageManager.NameNotFoundException e) { - // rather unlikely, just show app_name without version - } - // Build the text for the About dialog - Resources res = getResources(); - String aboutText = getString(R.string.about_contributors_title) + "\n\n"; - aboutText += StringUtils.join(res.getStringArray(R.array.about_contributors_array), '\n'); - aboutText += "\n\n" + getString(R.string.about_libraries_title) + "\n\n"; - aboutText += StringUtils.join(res.getStringArray(R.array.about_libraries_array), '\n'); - - return new AlertDialog.Builder(this) - .setTitle(app_version) - .setMessage(aboutText) - .setNeutralButton(android.R.string.ok, (dialog, which) -> { - }) - .create(); - default: - return super.onCreateDialog(dialogId); - } - } } \ No newline at end of file diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/AccountSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/AccountSettings.java index 4519082e7..46decfbef 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/AccountSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/AccountSettings.java @@ -4,13 +4,14 @@ import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; -import android.preference.Preference; +import androidx.preference.Preference; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import android.text.TextUtils; import android.widget.Toast; import com.android.volley.VolleyError; +import com.ferg.awfulapp.AwfulActivity; import com.ferg.awfulapp.R; import com.ferg.awfulapp.network.NetworkUtils; import com.ferg.awfulapp.preferences.Keys; @@ -66,6 +67,7 @@ private class FeaturesListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { final Dialog dialog = ProgressDialog.show(getActivity(), "Loading", "Fetching Account Features", true); + ((AwfulActivity)getActivity()).setPreferredFont(dialog.findViewById(android.R.id.title)); NetworkUtils.queueRequest(new FeatureRequest(getActivity()) .build(null, new AwfulRequest.AwfulResultCallback() { @Override diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ForumIndexSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ForumIndexSettings.java index 11778faae..927ccd72c 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ForumIndexSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ForumIndexSettings.java @@ -1,6 +1,6 @@ package com.ferg.awfulapp.preferences.fragments; -import android.preference.Preference; +import androidx.preference.Preference; import androidx.annotation.NonNull; import androidx.annotation.UiThread; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/MiscSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/MiscSettings.java index 76500d9a4..4a5e95176 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/MiscSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/MiscSettings.java @@ -1,8 +1,8 @@ package com.ferg.awfulapp.preferences.fragments; import android.app.Dialog; -import android.preference.ListPreference; -import android.preference.Preference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.annotation.NonNull; import android.view.View; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/PostSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/PostSettings.java index 4042d0022..251b34838 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/PostSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/PostSettings.java @@ -3,7 +3,7 @@ import android.app.Dialog; import android.content.DialogInterface; import android.graphics.Typeface; -import android.preference.Preference; +import androidx.preference.Preference; import androidx.annotation.NonNull; import android.util.Log; import android.util.TypedValue; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/RootSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/RootSettings.java index ebb09426e..870e3f5e0 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/RootSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/RootSettings.java @@ -1,18 +1,24 @@ package com.ferg.awfulapp.preferences.fragments; import android.app.Activity; +import androidx.appcompat.app.AlertDialog; +import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.preference.Preference; +import android.content.res.Resources; +import androidx.preference.Preference; import androidx.annotation.NonNull; +import com.ferg.awfulapp.AwfulActivity; import com.ferg.awfulapp.NavigationEvent; import com.ferg.awfulapp.R; import com.ferg.awfulapp.constants.Constants; import com.ferg.awfulapp.dialog.Changelog; import com.ferg.awfulapp.preferences.SettingsActivity; +import org.apache.commons.lang3.StringUtils; + import java.util.Calendar; import java.util.Locale; @@ -66,7 +72,32 @@ public String getTitle() { private class AboutListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { - getActivity().showDialog(SettingsActivity.DIALOG_ABOUT); + CharSequence app_version = getText(R.string.app_name); + try { + app_version = app_version + " " + + getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0) + .versionName; + } catch (PackageManager.NameNotFoundException e) { + // rather unlikely, just show app_name without version + } + // Build the text for the About dialog + Resources res = getResources(); + String aboutText = getString(R.string.about_contributors_title) + "\n\n"; + aboutText += StringUtils.join(res.getStringArray(R.array.about_contributors_array), '\n'); + aboutText += "\n\n" + getString(R.string.about_libraries_title) + "\n\n"; + aboutText += StringUtils.join(res.getStringArray(R.array.about_libraries_array), '\n'); + Dialog about = new AlertDialog.Builder(getActivity()) + .setTitle(app_version) + .setMessage(aboutText) + .setNeutralButton(android.R.string.ok, (dialog, which) -> { + }) + .show(); + + AwfulActivity activity = (AwfulActivity) getActivity(); + activity.setPreferredFont(about.findViewById(androidx.appcompat.R.id.alertTitle)); + activity.setPreferredFont(about.findViewById(android.R.id.message)); + activity.setPreferredFont(about.findViewById(android.R.id.button3)); + return true; } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/SettingsFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/SettingsFragment.java index cc204e820..e984ac074 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/SettingsFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/SettingsFragment.java @@ -1,21 +1,32 @@ package com.ferg.awfulapp.preferences.fragments; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.Resources; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceFragment; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.collection.ArrayMap; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceGroupAdapter; +import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; +import androidx.recyclerview.widget.RecyclerView; + import android.util.Log; import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.ListView; +import com.ferg.awfulapp.AwfulActivity; import com.ferg.awfulapp.NavigationEvent; import com.ferg.awfulapp.NavigationEventHandler; import com.ferg.awfulapp.R; @@ -41,7 +52,7 @@ * {@link SettingsActivity#PREFERENCE_XML_FILES} so it can be * automatically checked for defaults!

    */ -public abstract class SettingsFragment extends PreferenceFragment implements NavigationEventHandler { +public abstract class SettingsFragment extends PreferenceFragmentCompat implements NavigationEventHandler { public static final String TAG = "SettingsFragment"; private volatile boolean isInflated; @@ -113,6 +124,7 @@ public abstract class SettingsFragment extends PreferenceFragment implements Nav @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mPrefs = ((SettingsActivity) getActivity()).prefs; try { @@ -132,14 +144,43 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // for some reason, if you theme android:listDivider it won't show up in the preference list // so doing this directly seems to be the only way to theme it? Can't just get() it either - ListView listview = (ListView) getView().findViewById(android.R.id.list); Drawable divider = getResources().getDrawable(R.drawable.list_divider); TypedValue colour = new TypedValue(); getActivity().getTheme().resolveAttribute(android.R.attr.listDivider, colour, true); divider.setColorFilter(colour.data, PorterDuff.Mode.SRC_IN); - listview.setDivider(divider); + setDivider(divider); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View result = super.onCreateView(inflater, container, savedInstanceState); + + ((AwfulActivity) getActivity()).setPreferredFont(result); + + return result; + } + + @SuppressLint("RestrictedApi") + class AwfulPreferenceAdapter extends PreferenceGroupAdapter { + String TAG = "MyAdapter"; + public AwfulPreferenceAdapter(PreferenceGroup preferenceGroup) { + super(preferenceGroup); + } + + @NonNull + @Override + public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + PreferenceViewHolder holder = super.onCreateViewHolder(parent, viewType); + ((AwfulActivity)getActivity()).setPreferredFont(holder.itemView); + return holder; + } } + @NonNull + @Override + protected RecyclerView.Adapter onCreateAdapter(@NonNull PreferenceScreen preferenceScreen) { + return new AwfulPreferenceAdapter(preferenceScreen); + } // // Navigation @@ -305,4 +346,8 @@ public boolean onPreferenceClick(Preference preference) { } } + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + return; + } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ThemeSettings.java b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ThemeSettings.java index 4df60014e..bfcc03794 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ThemeSettings.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/preferences/fragments/ThemeSettings.java @@ -5,8 +5,8 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.os.Build; -import android.preference.ListPreference; -import android.preference.Preference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/provider/AwfulProvider.java b/Awful.apk/src/main/java/com/ferg/awfulapp/provider/AwfulProvider.java index 2d9908a73..7879b50ec 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/provider/AwfulProvider.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/provider/AwfulProvider.java @@ -63,6 +63,7 @@ import static com.ferg.awfulapp.provider.DatabaseHelper.TABLE_PM; import static com.ferg.awfulapp.provider.DatabaseHelper.TABLE_POSTS; import static com.ferg.awfulapp.provider.DatabaseHelper.TABLE_THREADS; +import static com.ferg.awfulapp.provider.DatabaseHelper.TABLE_THREAD_DRAFTS; import static com.ferg.awfulapp.provider.DatabaseHelper.TABLE_UCP_THREADS; public class AwfulProvider extends ContentProvider { @@ -91,8 +92,10 @@ public class AwfulProvider extends ContentProvider { private static final int URI_DRAFT_ID = 11; private static final int URI_EMOTE = 12; private static final int URI_EMOTE_ID = 13; + private static final int URI_THREAD_DRAFT = 14; + private static final int URI_THREAD_DRAFT_ID = 15; /** This just holds the Uri types that directly refer to tables, not IDs */ - private static final Set TABLE_URIS = new HashSet<>(Arrays.asList(URI_FORUM, URI_POST, URI_THREAD, URI_UCP_THREAD, URI_PM, URI_DRAFT, URI_EMOTE)); + private static final Set TABLE_URIS = new HashSet<>(Arrays.asList(URI_FORUM, URI_POST, URI_THREAD, URI_UCP_THREAD, URI_PM, URI_DRAFT, URI_EMOTE, URI_THREAD_DRAFT)); private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { @@ -110,6 +113,8 @@ public class AwfulProvider extends ContentProvider { sUriMatcher.addURI(Constants.AUTHORITY, "draftreplies/#", URI_DRAFT_ID); sUriMatcher.addURI(Constants.AUTHORITY, "emote", URI_EMOTE); sUriMatcher.addURI(Constants.AUTHORITY, "emote/#", URI_EMOTE_ID); + sUriMatcher.addURI(Constants.AUTHORITY, "draftthreads", URI_THREAD_DRAFT); + sUriMatcher.addURI(Constants.AUTHORITY, "draftthreads/#", URI_THREAD_DRAFT_ID); } @@ -262,6 +267,22 @@ private static String[] arrayOfKeys(@NonNull Map map) { DatabaseHelper.UPDATED_TIMESTAMP }; + private static final HashMap sDraftThreadProjectionMap = new HashMap<>(); + static { + sDraftThreadProjectionMap.put(AwfulMessage.ID, AwfulMessage.ID); + sDraftThreadProjectionMap.put(AwfulPost.FORM_COOKIE, AwfulPost.FORM_COOKIE); + sDraftThreadProjectionMap.put(AwfulPost.FORM_KEY, AwfulPost.FORM_KEY); + sDraftThreadProjectionMap.put(AwfulMessage.POST_CONTENT, AwfulMessage.POST_CONTENT); + sDraftThreadProjectionMap.put(AwfulMessage.POST_SUBJECT, AwfulMessage.POST_SUBJECT); + sDraftThreadProjectionMap.put(AwfulMessage.POST_ICON_ID, AwfulMessage.POST_ICON_ID); + sDraftThreadProjectionMap.put(AwfulMessage.POST_ICON_URL, AwfulMessage.POST_ICON_URL); + sDraftThreadProjectionMap.put(AwfulMessage.REPLY_ATTACHMENT, AwfulMessage.REPLY_ATTACHMENT); + sDraftThreadProjectionMap.put(AwfulPost.FORM_BOOKMARK, AwfulPost.FORM_BOOKMARK); + sDraftThreadProjectionMap.put(AwfulMessage.EPOC_TIMESTAMP, AwfulMessage.EPOC_TIMESTAMP); + sDraftThreadProjectionMap.put(DatabaseHelper.UPDATED_TIMESTAMP, DatabaseHelper.UPDATED_TIMESTAMP); + } + public static final String[] DraftThreadProjection = arrayOfKeys(sDraftThreadProjectionMap); + // Private messages private static final HashMap sPMReplyProjectionMap = new HashMap<>(); static { @@ -502,6 +523,12 @@ public Cursor query(@NonNull Uri aUri, String[] aProjection, String aSelection, builder.setProjectionMap(sDraftProjectionMap); break; + case URI_THREAD_DRAFT_ID: + whereClause = AwfulMessage.ID; + case URI_THREAD_DRAFT: + builder.setProjectionMap(sDraftThreadProjectionMap); + break; + case URI_EMOTE_ID: whereClause = AwfulEmote.ID; case URI_EMOTE: @@ -562,9 +589,12 @@ private String getTableForUriType(int uriType) { case URI_PM_ID: case URI_PM: return TABLE_PM; - case URI_DRAFT_ID: - case URI_DRAFT: - return TABLE_DRAFTS; + case URI_DRAFT_ID: + case URI_DRAFT: + return TABLE_DRAFTS; + case URI_THREAD_DRAFT_ID: + case URI_THREAD_DRAFT: + return TABLE_THREAD_DRAFTS; case URI_EMOTE_ID: case URI_EMOTE: return TABLE_EMOTES; diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/provider/DatabaseHelper.java b/Awful.apk/src/main/java/com/ferg/awfulapp/provider/DatabaseHelper.java index c24ed78d7..295d733b6 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/provider/DatabaseHelper.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/provider/DatabaseHelper.java @@ -20,7 +20,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "awful.db"; - private static final int DATABASE_VERSION = 36; + private static final int DATABASE_VERSION = 37; static final String TABLE_FORUM = "forum"; static final String TABLE_THREADS = "threads"; @@ -30,6 +30,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { static final String TABLE_EMOTES = "emotes"; static final String TABLE_PM = "private_messages"; static final String TABLE_DRAFTS = "draft_messages"; + static final String TABLE_THREAD_DRAFTS = "draft_threads"; public static final String UPDATED_TIMESTAMP = "timestamp_row_update"; @@ -46,6 +47,7 @@ public void onCreate(SQLiteDatabase aDb) { createEmoteTable(aDb); createPMTable(aDb); createDraftTable(aDb); + createThreadDraftTable(aDb); } @@ -158,6 +160,21 @@ private void createDraftTable(SQLiteDatabase aDb) { UPDATED_TIMESTAMP + " DATETIME);"); } + private void createThreadDraftTable(SQLiteDatabase aDb) { + aDb.execSQL("CREATE TABLE " + TABLE_THREAD_DRAFTS + " (" + + AwfulMessage.ID + " INTEGER UNIQUE," + + AwfulPost.FORM_KEY + " VARCHAR," + + AwfulPost.FORM_COOKIE + " VARCHAR," + + AwfulMessage.POST_CONTENT + " VARCHAR," + + AwfulMessage.POST_SUBJECT + " VARCHAR," + + AwfulMessage.POST_ICON_ID + " VARCHAR," + + AwfulMessage.POST_ICON_URL + " VARCHAR," + + AwfulPost.FORM_BOOKMARK + " VARCHAR," + + AwfulMessage.REPLY_ATTACHMENT + " VARCHAR," + + AwfulMessage.EPOC_TIMESTAMP + " INTEGER, " + + UPDATED_TIMESTAMP + " DATETIME);"); + } + @Override public void onUpgrade(SQLiteDatabase aDb, int aOldVersion, int aNewVersion) { @@ -188,6 +205,9 @@ public void onUpgrade(SQLiteDatabase aDb, int aOldVersion, int aNewVersion) { case 35: dropTables(aDb, TABLE_POSTS); createPostTable(aDb); + case 36: + dropTables(aDb, TABLE_THREAD_DRAFTS); + createThreadDraftTable(aDb); break;//make sure to keep this break statement on the last case of this switch default: wipeRecreateTables(aDb); @@ -209,7 +229,7 @@ private void dropTables(@NonNull SQLiteDatabase db, @NonNull String... tableName } private void wipeRecreateTables(SQLiteDatabase aDb) { - String[] allTables = {TABLE_FORUM, TABLE_THREADS, TABLE_POSTS, TABLE_EMOTES, TABLE_UCP_THREADS, TABLE_PM, TABLE_DRAFTS}; + String[] allTables = {TABLE_FORUM, TABLE_THREADS, TABLE_POSTS, TABLE_EMOTES, TABLE_UCP_THREADS, TABLE_PM, TABLE_DRAFTS, TABLE_THREAD_DRAFTS}; dropTables(aDb, allTables); onCreate(aDb); } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/reply/MessageComposer.java b/Awful.apk/src/main/java/com/ferg/awfulapp/reply/MessageComposer.java index ccac3db05..8f2c4ab5b 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/reply/MessageComposer.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/reply/MessageComposer.java @@ -9,6 +9,7 @@ import androidx.annotation.Nullable; import com.ferg.awfulapp.AwfulActivity; +import com.ferg.awfulapp.FontManager; import com.ferg.awfulapp.NavigationEvent; import com.ferg.awfulapp.preferences.AwfulPreferences; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -67,6 +68,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View result = inflater.inflate(R.layout.message_composer, container, true); messageBox = result.findViewById(R.id.message_edit_text); addBbCodeToSelectionMenu(messageBox); + ((AwfulActivity) getActivity()).setPreferredFont(result); return result; } @@ -88,6 +90,10 @@ public void onDestroy() { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.message_composer, menu); + FontManager fm = FontManager.getInstance(); + for (int i = 0; i < menu.size(); i++) { + fm.setMenuItemFont(menu.getItem(i)); + } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFilter.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFilter.kt index d47b1b897..a9057ee50 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFilter.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFilter.kt @@ -2,9 +2,9 @@ package com.ferg.awfulapp.search import android.os.Parcel import android.os.Parcelable -import androidx.appcompat.app.AlertDialog import android.view.View import android.widget.EditText +import androidx.appcompat.app.AlertDialog import com.ferg.awfulapp.R /** @@ -45,13 +45,16 @@ class SearchFilter(val type: FilterType, val param: String) : Parcelable { val layout = layoutInflater.inflate(R.layout.insert_text_dialog, null) val textField = layout.findViewById(R.id.text_field) as EditText textField.hint = description ?: label - AlertDialog.Builder(this) + val add = AlertDialog.Builder(this) .setTitle("Add search filter") .setView(layout) .setPositiveButton("Add filter", { _, _ -> searchFragment.addFilter(SearchFilter(this@FilterType, textField.text.toString())) }) .show() + searchFragment.awfulActivity?.setPreferredFont(add.findViewById(androidx.appcompat.R.id.alertTitle)) + searchFragment.awfulActivity?.setPreferredFont(add.findViewById(android.R.id.button1)) + searchFragment.awfulActivity?.setPreferredFont(layout) } } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchForumsFragment.java b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchForumsFragment.java index 1c99dc73a..1cd58040a 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchForumsFragment.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchForumsFragment.java @@ -83,6 +83,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, public SearchHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.search_forum_item, parent, false); + + getAwfulActivity().setPreferredFont(view); return new SearchHolder(view); } @@ -120,6 +122,7 @@ public void onBindViewHolder(SearchHolder holder, final int position) { self.notifyDataSetChanged(); } }); + getAwfulActivity().setPreferredFont(holder.itemView); } @Override diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFragment.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFragment.kt index 1c59b9b4e..08fd4ccf6 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFragment.kt +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/search/SearchFragment.kt @@ -33,16 +33,22 @@ package com.ferg.awfulapp.search import android.app.ProgressDialog import android.os.Bundle -import com.google.android.material.snackbar.Snackbar -import androidx.fragment.app.DialogFragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import android.text.Html -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.EditText import android.widget.TextView +import androidx.core.view.forEach +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.android.volley.VolleyError import com.ferg.awfulapp.AwfulFragment +import com.ferg.awfulapp.FontManager import com.ferg.awfulapp.NavigationEvent import com.ferg.awfulapp.NavigationEvent.Companion.parse import com.ferg.awfulapp.R @@ -57,10 +63,10 @@ import com.ferg.awfulapp.thread.AwfulSearch import com.ferg.awfulapp.thread.AwfulSearchResult import com.ferg.awfulapp.thread.AwfulURL import com.ferg.awfulapp.widget.SwipyRefreshLayout +import com.google.android.material.snackbar.Snackbar import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection import org.apache.commons.lang3.ArrayUtils import timber.log.Timber -import java.util.* class SearchFragment : AwfulFragment(), com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout.OnRefreshListener { @@ -103,7 +109,10 @@ class SearchFragment : AwfulFragment(), com.orangegangsters.github.swipyrefreshl override fun onCreateView(aInflater: LayoutInflater, aContainer: ViewGroup?, aSavedState: Bundle?): View? { super.onCreateView(aInflater, aContainer, aSavedState) Timber.v("onCreateView") - return inflateView(R.layout.search, aContainer, aInflater) + val result = inflateView(R.layout.search, aContainer, aInflater) + + awfulActivity?.setPreferredFont(result) + return result } @@ -171,8 +180,10 @@ class SearchFragment : AwfulFragment(), com.orangegangsters.github.swipyrefreshl override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater!!.inflate(R.menu.search, menu) + val fm = FontManager.getInstance() val filterMenu = menu?.findItem(R.id.search_terms)!!.subMenu SearchFilter.FilterType.values().forEach { filterMenu?.add(it.label) } + filterMenu?.forEach { fm.setMenuItemFont(it) } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -195,6 +206,15 @@ class SearchFragment : AwfulFragment(), com.orangegangsters.github.swipyrefreshl return true } + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + + val fm = FontManager.getInstance() + for (i in 0 until menu.size()) { + fm.setMenuItemFont(menu.getItem(i)) + } + } + /** * Add a filter to the current set. @@ -266,6 +286,7 @@ class SearchFragment : AwfulFragment(), com.orangegangsters.github.swipyrefreshl self.setOnClickListener { AwfulURL.parse(Constants.BASE_URL + threadLink).let(NavigationEvent::Url).let(::navigate) } + awfulActivity?.setPreferredFont(holder.itemView) } } diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/task/PreviewThreadRequest.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/task/PreviewThreadRequest.kt new file mode 100644 index 000000000..25d30b0f2 --- /dev/null +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/task/PreviewThreadRequest.kt @@ -0,0 +1,50 @@ +package com.ferg.awfulapp.task + +import android.content.ContentValues +import android.content.Context +import com.ferg.awfulapp.constants.Constants.* +import com.ferg.awfulapp.network.NetworkUtils +import com.ferg.awfulapp.thread.AwfulMessage +import com.ferg.awfulapp.thread.AwfulPost +import com.ferg.awfulapp.thread.PostPreviewParseTask +import org.jsoup.nodes.Document + +/** + * A request that fetches a preview of a thread, providing the parsed HTML. + * + * This functions like the Preview button when composing a thread on the site, which + * takes the current content in the post window and renders it as HTML. You supply + * the data on the edit page via the ContentValues parameter, which is sent to + * the site to retrieve the preview. + */ +class PreviewThreadRequest (context: Context, reply: ContentValues) + : AwfulRequest(context, FUNCTION_POST_THREAD, isPostRequest = true) { +// TODO: 23/05/2024 this, PreviewPostRequest and PreviewEditRequest are almost identical, oh no it's getting worse + init { + with(parameters) { + val threadId = reply.getAsInteger(AwfulMessage.ID)?.toString() + ?: throw IllegalArgumentException("No forum ID included") + add(PARAM_ACTION, "postthread") + add(PARAM_FORUM_ID, Integer.toString(reply.getAsInteger(AwfulMessage.ID)!!)) + add(PARAM_FORMKEY, reply.getAsString(AwfulPost.FORM_KEY)) + add(PARAM_FORM_COOKIE, reply.getAsString(AwfulPost.FORM_COOKIE)) + add(PARAM_SUBJECT, NetworkUtils.encodeHtml(reply.getAsString(AwfulMessage.POST_SUBJECT))) + add(PARAM_MESSAGE, NetworkUtils.encodeHtml(reply.getAsString(AwfulMessage.POST_CONTENT))) + + add(PARAM_PARSEURL, YES) + // TODO: this bookmarks every thread you post in, unless you turn it off in a browser - seems bad? + if (reply.getAsString(AwfulPost.FORM_BOOKMARK).equals("checked", ignoreCase = true)) { + add(PARAM_BOOKMARK, YES) + } + listOf(AwfulMessage.REPLY_SIGNATURE, AwfulMessage.REPLY_DISABLE_SMILIES) + .forEach { if (reply.containsKey(it)) add(it, YES) } + + reply.getAsString(AwfulMessage.REPLY_ATTACHMENT)?.let { filePath -> attachFile(PARAM_ATTACHMENT, filePath) } + add(PARAM_PREVIEW, PREVIEW_REPLY) + } + } + + + override fun handleResponse(doc: Document): String = PostPreviewParseTask(doc).call() + +} diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/task/SendThreadRequest.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/task/SendThreadRequest.kt new file mode 100644 index 000000000..7439fcb95 --- /dev/null +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/task/SendThreadRequest.kt @@ -0,0 +1,40 @@ +package com.ferg.awfulapp.task + +import android.content.ContentValues +import android.content.Context +import com.ferg.awfulapp.constants.Constants.* +import com.ferg.awfulapp.network.NetworkUtils +import com.ferg.awfulapp.thread.AwfulMessage +import com.ferg.awfulapp.thread.AwfulPost +import org.jsoup.nodes.Document + +/** + * Submit a thread described by a set of ContentValues + */ +class SendThreadRequest(context: Context, reply: ContentValues) + : AwfulRequest(context, FUNCTION_POST_THREAD, isPostRequest = true) { + + init { + with(parameters) { + add(PARAM_ACTION, "postthread") + add(PARAM_FORUM_ID, Integer.toString(reply.getAsInteger(AwfulMessage.ID)!!)) + add(PARAM_FORMKEY, reply.getAsString(AwfulPost.FORM_KEY)) + add(PARAM_FORM_COOKIE, reply.getAsString(AwfulPost.FORM_COOKIE)) + add(PARAM_SUBJECT, NetworkUtils.encodeHtml(reply.getAsString(AwfulMessage.POST_SUBJECT))) + add(PARAM_MESSAGE, NetworkUtils.encodeHtml(reply.getAsString(AwfulMessage.POST_CONTENT))) + add(PARAM_PARSEURL, YES) + add("iconid", reply.getAsString(AwfulMessage.POST_ICON_ID)) + if (reply.getAsString(AwfulPost.FORM_BOOKMARK).equals("checked", ignoreCase = true)) { + add(PARAM_BOOKMARK, YES) + } + listOf(AwfulMessage.REPLY_SIGNATURE, AwfulMessage.REPLY_DISABLE_SMILIES) + .forEach { key -> if (reply.containsKey(key)) add(key, YES) } + reply.getAsString(AwfulMessage.REPLY_ATTACHMENT)?.let { filePath -> + attachFile(PARAM_ATTACHMENT, filePath) + } + } + } + + override fun handleResponse(doc: Document): Void? = null + +} diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/task/ThreadRequest.kt b/Awful.apk/src/main/java/com/ferg/awfulapp/task/ThreadRequest.kt new file mode 100644 index 000000000..a4160e691 --- /dev/null +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/task/ThreadRequest.kt @@ -0,0 +1,36 @@ +package com.ferg.awfulapp.task + +import android.content.ContentValues +import android.content.Context +import com.ferg.awfulapp.constants.Constants.* +import com.ferg.awfulapp.provider.DatabaseHelper +import com.ferg.awfulapp.thread.Thread +import com.ferg.awfulapp.util.AwfulError +import org.jsoup.nodes.Document +import java.sql.Timestamp + +/** + * Request the data you get when starting a new thread on the site. + * + * This provides you with any initial op contents, the form key + * and cookie (for authentication?) as well as any selected + * options (see [Thread.processReply]) and a current timestamp. + */ +class ThreadRequest(context: Context, private val forumId: Int) + : AwfulRequest(context, FUNCTION_POST_THREAD) { + + init { + with(parameters) { + add(PARAM_ACTION, "newthread") + add(PARAM_FORUM_ID, forumId.toString()) + } + } + + @Throws(AwfulError::class) + override fun handleResponse(doc: Document): ContentValues { + return Thread.processThread(doc, forumId).apply { + put(DatabaseHelper.UPDATED_TIMESTAMP, Timestamp(System.currentTimeMillis()).toString()) + } + } + +} diff --git a/Awful.apk/src/main/java/com/ferg/awfulapp/thread/AwfulHtmlPage.java b/Awful.apk/src/main/java/com/ferg/awfulapp/thread/AwfulHtmlPage.java index d69ca5ead..68493f352 100644 --- a/Awful.apk/src/main/java/com/ferg/awfulapp/thread/AwfulHtmlPage.java +++ b/Awful.apk/src/main/java/com/ferg/awfulapp/thread/AwfulHtmlPage.java @@ -60,7 +60,8 @@ public abstract class AwfulHtmlPage { "longtap.js", "jsonp.js", "embedding.js", - "thread.js" + "thread.js", + "hammer.js" }; /** @@ -117,7 +118,7 @@ public static String getContainerHtml(AwfulPreferences aPrefs, @Nullable Integer if (!aPrefs.preferredFont.contains("default")) { - buffer.append("\n"); + buffer.append("\n"); } for (String scriptName : JS_FILES) { buffer.append("