diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bfc06e0d6c267607f464634c2cd14a05348a5066..ce7a9f6e16e4a0bc3f877bb573f64d583a2b8c12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ build: stage: build script: - | - ./gradlew build + ./gradlew assemble retval=$? if [$retval -ne 0]; then echo "error on building, exit code: "$retval diff --git a/.travis.yml b/.travis.yml index 22ce005512d445f078888a17977f4db67c62d8af..ae7f6a9424f9887d043ed4c79afc24d282dfeab9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,12 @@ notifications: env: global: - - COMPILE_API_LEVEL=27 + - COMPILE_API_LEVEL=29 - EMULATOR_API_LEVEL=24 # It seems emulator levels 25,26,27 from main repos have google_apis by default, and do not support armeabi-v7a. Check commit comment. - EMULATOR_TAG=default # Possible values are default, google_apis, android-tv, android-wear, android-wear-cn - EMULATOR_ABI=armeabi-v7a # Default is armeabi-v7a, possible options are: x86, x86_64, mips, arm64-v8a, armeabi-v7a. Note: check commit comment - EMULATOR_NAME=qksms3 - - ANDROID_BUILD_TOOLS_VERSION=27.0.2 # Match build-tools version used in build.gradle + - ANDROID_BUILD_TOOLS_VERSION=29.0.2 # Match build-tools version used in build.gradle - EMULATOR="system-images;android-${EMULATOR_API_LEVEL};${EMULATOR_TAG};${EMULATOR_ABI}" # Used to install/create emulator android: @@ -36,8 +36,9 @@ android: # Set up Android-sdk and the emulator before_install: - # Decrypt keystore - - openssl aes-256-cbc -K $encrypted_4d1d940e2c65_key -iv $encrypted_4d1d940e2c65_iv -in keystore.enc -out keystore -d + # Decrypt keystore and google-services.json + - openssl aes-256-cbc -K $encrypted_4d1d940e2c65_key -iv $encrypted_4d1d940e2c65_iv -in secrets.tar.enc -out secrets.tar -d + - tar xvf secrets.tar - echo 'count=0' > /home/travis/.android/repositories.cfg # Avoid warning - ls -lar $HOME/**/* @@ -49,7 +50,7 @@ before_install: - yes | sdkmanager "platforms;android-${EMULATOR_API_LEVEL}" # Android platform required by emulator - yes | sdkmanager "platforms;android-${COMPILE_API_LEVEL}" # Android platform required by compiler - yes | sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" # Android build tools - - yes | sdkmanager "${EMULATOR}" # Install emulator system image + - yes | sdkmanager "${EMULATOR}" # Install emulator system image - sdkmanager --list || true # Print out package list for debug purposes # Run the emulator @@ -75,7 +76,7 @@ before_script: # Build APK script: - | - ./gradlew :presentation:assembleWithAnalyticsRelease assembleAndroidTest -PtestCoverageEnabled='true' + ./gradlew :presentation:assembleWithAnalyticsRelease :presentation:bundleWithAnalyticsRelease assembleAndroidTest -PtestCoverageEnabled='true' retval=$? if [$retval -ne 0]; then echo "error on assembling, exit code: "$retval @@ -90,9 +91,8 @@ deploy: api_key: secure: XF7V/I02gpyOzCAFXEFyrThXVUUnKjSaWQ8lppO50mVtdugimjWIPtHrcYASaJQf9INhqo0lamk+khPxtKxc1BSCp8o+c22UKcpczyjD4kK27a3zKfuNQWteBRjCH34vIGnrRFSHSWYLIgeuoIK3q5Lq4IBK/Od3mfpRaDt1ER+IqMzR3L205x1H8dW3MVuxXgdnq3jHlRpq86oOe293+dnblVCtWUvAzwhZPnnbBc4JUaNomMI7dLJ/pAigByCoHHmG9pc2Cky1yyWVAnTZFAlf2PbzPDLRRnXmHuYKfHxiZgd/l8JTiZdhky9cXgFoSxvJyDABRqqLxVNfXt2ZwgdtiulZml8RB1FB0L37qL72mxWgi6y9IbQgt/FG20K2QpSBglk0bCGLS+h5Yz3kV4fhsBY7llpWGw14BvlAx9sUfl3Ej+IUsWoJgA00TFNGDG8sMyOFoCQVz/sB4Dv4h+JfynJZcmm8okcfYrWBHOoHY7cH3chBWp/2A736f2A/aqnBd6z8a03toe2ILC9eSOiIhrVxPyqLmEKBD1rCduVFNteqGwm9G9YwKpvFibTqu0gqEtfF7cmuMH6M5PYExI5EzoewZTYmgp02+lBuFAEMvycVvXcu8VfeeT6cgeLlmz2hsbo93UfoSQyP+gSojMOOkVUsl6mIp1STLiJ5IRY= file: - - /home/travis/build/moezbhatti/qksms/presentation/build/outputs/apk/withAnalytics/release/presentation-withAnalytics-arm64-v8a-release.apk - - /home/travis/build/moezbhatti/qksms/presentation/build/outputs/apk/withAnalytics/release/presentation-withAnalytics-armeabi-v7a-release.apk - - /home/travis/build/moezbhatti/qksms/presentation/build/outputs/apk/withAnalytics/release/presentation-withAnalytics-universal-release.apk + - /home/travis/build/moezbhatti/qksms/presentation/build/outputs/bundle/withAnalyticsRelease/presentation.aab + - /home/travis/build/moezbhatti/qksms/presentation/build/outputs/apk/withAnalytics/release/presentation-withAnalytics-release.apk on: repo: moezbhatti/qksms tags: true \ No newline at end of file diff --git a/android-smsmms/build.gradle b/android-smsmms/build.gradle index 43ebe5fe8f18606df8681e985ba17f266efc5dc7..81dbda501f2e756dd575f87b483030e370f6d5e8 100644 --- a/android-smsmms/build.gradle +++ b/android-smsmms/build.gradle @@ -38,7 +38,6 @@ android { dependencies { implementation "com.jakewharton.timber:timber:$timber_version" - implementation 'com.klinkerapps:logger:1.0.3' implementation 'com.squareup.okhttp:okhttp:2.5.0' implementation 'com.squareup.okhttp:okhttp-urlconnection:2.5.0' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" diff --git a/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java b/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java index 508ea26cf6f4277e560cff62d3c8bfb7840c0c27..ebf980d33e9d22a5c509e617ef149f1f374842b9 100755 --- a/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java +++ b/android-smsmms/src/main/java/android/database/sqlite/SqliteWrapper.java @@ -23,15 +23,14 @@ import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.widget.Toast; -import com.klinker.android.logger.Log; import org.jetbrains.annotations.Nullable; +import timber.log.Timber; /** * @hide */ public final class SqliteWrapper { - private static final String TAG = "SqliteWrapper"; private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE = "unable to open database file"; @@ -59,7 +58,7 @@ public final class SqliteWrapper { try { return resolver.query(uri, projection, selection, selectionArgs, sortOrder); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when query: ", e); + Timber.e(e, "Catch a SQLiteException when query: "); checkSQLiteException(context, e); return null; } @@ -70,7 +69,7 @@ public final class SqliteWrapper { try { return resolver.update(uri, values, where, selectionArgs); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when update: ", e); + Timber.e(e, "Catch a SQLiteException when update: "); checkSQLiteException(context, e); return -1; } @@ -81,7 +80,7 @@ public final class SqliteWrapper { try { return resolver.delete(uri, where, selectionArgs); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when delete: ", e); + Timber.e(e, "Catch a SQLiteException when delete: "); checkSQLiteException(context, e); return -1; } @@ -92,7 +91,7 @@ public final class SqliteWrapper { try { return resolver.insert(uri, values); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when insert: ", e); + Timber.e(e, "Catch a SQLiteException when insert: "); checkSQLiteException(context, e); return null; } diff --git a/android-smsmms/src/main/java/android/net/DhcpInfoInternal.java b/android-smsmms/src/main/java/android/net/DhcpInfoInternal.java index b9ff371f5a8d8eb6432a993c2648fb3b1c5922d2..1e0c7f63a26aae1a6734735cc3cfc52b31e793ec 100755 --- a/android-smsmms/src/main/java/android/net/DhcpInfoInternal.java +++ b/android-smsmms/src/main/java/android/net/DhcpInfoInternal.java @@ -17,7 +17,7 @@ package android.net; import android.text.TextUtils; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.net.Inet4Address; import java.net.InetAddress; @@ -32,7 +32,6 @@ import java.util.Collections; * @hide */ public class DhcpInfoInternal { - private final static String TAG = "DhcpInfoInternal"; public String ipAddress; public int prefixLength; @@ -96,7 +95,7 @@ public class DhcpInfoInternal { public LinkAddress makeLinkAddress() { if (TextUtils.isEmpty(ipAddress)) { - Log.e(TAG, "makeLinkAddress with empty ipAddress"); + Timber.e("makeLinkAddress with empty ipAddress"); return null; } return new LinkAddress(NetworkUtilsHelper.numericToInetAddress(ipAddress), prefixLength); @@ -112,12 +111,12 @@ public class DhcpInfoInternal { if (TextUtils.isEmpty(dns1) == false) { p.addDns(NetworkUtilsHelper.numericToInetAddress(dns1)); } else { - Log.d(TAG, "makeLinkProperties with empty dns1!"); + Timber.d("makeLinkProperties with empty dns1!"); } if (TextUtils.isEmpty(dns2) == false) { p.addDns(NetworkUtilsHelper.numericToInetAddress(dns2)); } else { - Log.d(TAG, "makeLinkProperties with empty dns2!"); + Timber.d("makeLinkProperties with empty dns2!"); } return p; } diff --git a/android-smsmms/src/main/java/android/net/LinkCapabilities.java b/android-smsmms/src/main/java/android/net/LinkCapabilities.java index 6b824b85832679da4778b409c5e4cc2edca60a99..1c8be5c6488afdbb5cf10c46e1dbc9f9cdbd1464 100755 --- a/android-smsmms/src/main/java/android/net/LinkCapabilities.java +++ b/android-smsmms/src/main/java/android/net/LinkCapabilities.java @@ -18,7 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.Collection; import java.util.HashMap; @@ -32,7 +32,6 @@ import java.util.Set; * @hide */ public class LinkCapabilities implements Parcelable { - private static final String TAG = "LinkCapabilities"; private static final boolean DBG = false; /** @@ -388,6 +387,6 @@ public class LinkCapabilities implements Parcelable { * Debug logging */ protected static void log(String s) { - Log.d(TAG, s); + Timber.d(s); } } diff --git a/android-smsmms/src/main/java/android/net/NetworkUtilsHelper.java b/android-smsmms/src/main/java/android/net/NetworkUtilsHelper.java index e4f7b6a49ffb9d1b4cfd528d1deca1c7adfb3e30..601f735717205b255b1ddddeb789b9570c13ff27 100755 --- a/android-smsmms/src/main/java/android/net/NetworkUtilsHelper.java +++ b/android-smsmms/src/main/java/android/net/NetworkUtilsHelper.java @@ -16,7 +16,7 @@ package android.net; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.net.Inet4Address; import java.net.Inet6Address; @@ -31,8 +31,6 @@ import java.util.Collection; */ public class NetworkUtilsHelper { - private static final String TAG = "NetworkUtils"; - /** * Bring the named network interface up. */ @@ -255,7 +253,7 @@ public class NetworkUtilsHelper { addrHexString.substring(16, 20), addrHexString.substring(20, 24), addrHexString.substring(24, 28), addrHexString.substring(28, 32))); } catch (Exception e) { - Log.e("NetworkUtils", "error in hexToInet6Address(" + addrHexString + "): " + e); + Timber.e("error in hexToInet6Address(" + addrHexString + "): " + e); throw new IllegalArgumentException(e); } } diff --git a/android-smsmms/src/main/java/android/net/RouteInfo.java b/android-smsmms/src/main/java/android/net/RouteInfo.java index ffd074b6f2733f4cc0e4e5b80bf6eaa5ddd33f2d..1aa3ea8cbf62e5494cfea28baadf3664d478111f 100755 --- a/android-smsmms/src/main/java/android/net/RouteInfo.java +++ b/android-smsmms/src/main/java/android/net/RouteInfo.java @@ -18,7 +18,7 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.net.Inet4Address; import java.net.Inet6Address; @@ -32,7 +32,6 @@ import java.util.Collection; * @hide */ public class RouteInfo implements Parcelable { - private static final String TAG = "RouteInfo"; /** * The IP destination address for this route. */ @@ -54,14 +53,14 @@ public class RouteInfo implements Parcelable { destination = new LinkAddress(Inet4Address.getLocalHost(), 0); } catch (UnknownHostException e) { // TODO Auto-generated catch block - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } } else { try { destination = new LinkAddress(Inet6Address.getLocalHost(), 0); } catch (UnknownHostException e) { // TODO Auto-generated catch block - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } } } else { @@ -75,14 +74,14 @@ public class RouteInfo implements Parcelable { gateway = Inet4Address.getLocalHost(); } catch (UnknownHostException e) { // TODO Auto-generated catch block - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } } else { try { gateway = Inet6Address.getLocalHost(); } catch (UnknownHostException e) { // TODO Auto-generated catch block - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } } } diff --git a/android-smsmms/src/main/java/com/android/mms/MmsConfig.java b/android-smsmms/src/main/java/com/android/mms/MmsConfig.java index 2cab73f912953e52fbf3099a576cf3367c97095d..7e7ab62a5335f1e24d50181966812f4ad9d295f4 100755 --- a/android-smsmms/src/main/java/com/android/mms/MmsConfig.java +++ b/android-smsmms/src/main/java/com/android/mms/MmsConfig.java @@ -18,15 +18,14 @@ package com.android.mms; import android.content.Context; import android.content.res.XmlResourceParser; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import timber.log.Timber; import java.io.IOException; public class MmsConfig { - private static final String TAG = "MmsConfig"; private static final boolean DEBUG = true; private static final boolean LOCAL_LOGV = false; @@ -107,7 +106,7 @@ public class MmsConfig { public static void init(Context context) { if (LOCAL_LOGV) { - Log.v(TAG, "MmsConfig.init()"); + Timber.v("MmsConfig.init()"); } // Always put the mnc/mcc in the log so we can tell which mms_config.xml was loaded. @@ -120,7 +119,7 @@ public class MmsConfig { public static int getMaxMessageSize() { if (LOCAL_LOGV) { - Log.v(TAG, "MmsConfig.getMaxMessageSize(): " + mMaxMessageSize); + Timber.v("MmsConfig.getMaxMessageSize(): " + mMaxMessageSize); } return mMaxMessageSize; } @@ -209,7 +208,7 @@ public class MmsConfig { } if (DEBUG) { - Log.v(TAG, "tag: " + tag + " value: " + value + " - " + + Timber.v("tag: " + tag + " value: " + value + " - " + text); } if ("name".equalsIgnoreCase(name)) { @@ -297,11 +296,11 @@ public class MmsConfig { } } } catch (XmlPullParserException e) { - Log.e(TAG, "loadMmsSettings caught ", e); + Timber.e(e, "loadMmsSettings caught "); } catch (NumberFormatException e) { - Log.e(TAG, "loadMmsSettings caught ", e); + Timber.e(e, "loadMmsSettings caught "); } catch (IOException e) { - Log.e(TAG, "loadMmsSettings caught ", e); + Timber.e(e, "loadMmsSettings caught "); } finally { parser.close(); } @@ -316,7 +315,7 @@ public class MmsConfig { String err = String.format("MmsConfig.loadMmsSettings mms_config.xml missing %s setting", errorStr); - Log.e(TAG, err); + Timber.e(err); } } diff --git a/android-smsmms/src/main/java/com/android/mms/dom/events/EventTargetImpl.java b/android-smsmms/src/main/java/com/android/mms/dom/events/EventTargetImpl.java index dbaa304e157ccf3266d6e7ccf7560f85afea8531..9317d1f402b43728139df07d27af1eeb0ed9d1b7 100755 --- a/android-smsmms/src/main/java/com/android/mms/dom/events/EventTargetImpl.java +++ b/android-smsmms/src/main/java/com/android/mms/dom/events/EventTargetImpl.java @@ -16,17 +16,15 @@ package com.android.mms.dom.events; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import org.w3c.dom.events.Event; import org.w3c.dom.events.EventException; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; +import timber.log.Timber; import java.util.ArrayList; public class EventTargetImpl implements EventTarget { - private static final String TAG = LogTag.TAG; private ArrayList mListenerEntries; private EventTarget mNodeTarget; @@ -98,7 +96,7 @@ public class EventTargetImpl implements EventTarget { catch (Exception e) { // Any exceptions thrown inside an EventListener will // not stop propagation of the event - Log.w(TAG, "Catched EventListener exception", e); + Timber.w(e, "Catched EventListener exception"); } } } diff --git a/android-smsmms/src/main/java/com/android/mms/dom/smil/ElementTimeImpl.java b/android-smsmms/src/main/java/com/android/mms/dom/smil/ElementTimeImpl.java index 40bad2b2ab7f488cb10327ba9f3afb47bf497998..7bee39013e5133bf69d0146dfc67118cb31fe9e7 100755 --- a/android-smsmms/src/main/java/com/android/mms/dom/smil/ElementTimeImpl.java +++ b/android-smsmms/src/main/java/com/android/mms/dom/smil/ElementTimeImpl.java @@ -16,18 +16,16 @@ package com.android.mms.dom.smil; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import org.w3c.dom.DOMException; import org.w3c.dom.smil.ElementTime; import org.w3c.dom.smil.SMILElement; import org.w3c.dom.smil.Time; import org.w3c.dom.smil.TimeList; +import timber.log.Timber; import java.util.ArrayList; public abstract class ElementTimeImpl implements ElementTime { - private static final String TAG = LogTag.TAG; private static final String FILL_REMOVE_ATTRIBUTE = "remove"; private static final String FILL_FREEZE_ATTRIBUTE = "freeze"; @@ -131,7 +129,7 @@ public abstract class ElementTimeImpl implements ElementTime { getEndConstraints())); } catch (IllegalArgumentException e) { // Ignore badly formatted times - Log.e(TAG, "Malformed time value.", e); + Timber.e(e, "Malformed time value."); } } } diff --git a/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilMediaElementImpl.java b/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilMediaElementImpl.java index 08a43e8b4bc4fab51c1361c39e2805c1b439e732..3d03aaba57ac4acd06de7b89e61d71eb483503c6 100755 --- a/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilMediaElementImpl.java +++ b/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilMediaElementImpl.java @@ -17,23 +17,19 @@ package com.android.mms.dom.smil; import com.android.mms.dom.events.EventImpl; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import org.w3c.dom.DOMException; import org.w3c.dom.events.DocumentEvent; import org.w3c.dom.events.Event; import org.w3c.dom.smil.ElementTime; import org.w3c.dom.smil.SMILMediaElement; import org.w3c.dom.smil.TimeList; +import timber.log.Timber; -public class SmilMediaElementImpl extends SmilElementImpl implements - SMILMediaElement { +public class SmilMediaElementImpl extends SmilElementImpl implements SMILMediaElement { public final static String SMIL_MEDIA_START_EVENT = "SmilMediaStart"; public final static String SMIL_MEDIA_END_EVENT = "SmilMediaEnd"; public final static String SMIL_MEDIA_PAUSE_EVENT = "SmilMediaPause"; public final static String SMIL_MEDIA_SEEK_EVENT = "SmilMediaSeek"; - private final static String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; ElementTime mElementTime = new ElementTimeImpl(this) { @@ -43,7 +39,7 @@ public class SmilMediaElementImpl extends SmilElementImpl implements Event event = doc.createEvent("Event"); event.initEvent(eventType, false, false); if (LOCAL_LOGV) { - Log.v(TAG, "Dispatching 'begin' event to " + Timber.v("Dispatching 'begin' event to " + SmilMediaElementImpl.this.getTagName() + " " + SmilMediaElementImpl.this.getSrc() + " at " + System.currentTimeMillis()); @@ -57,7 +53,7 @@ public class SmilMediaElementImpl extends SmilElementImpl implements EventImpl event = (EventImpl) doc.createEvent("Event"); event.initEvent(eventType, false, false, seekTo); if (LOCAL_LOGV) { - Log.v(TAG, "Dispatching 'begin' event to " + Timber.v("Dispatching 'begin' event to " + SmilMediaElementImpl.this.getTagName() + " " + SmilMediaElementImpl.this.getSrc() + " at " + System.currentTimeMillis()); @@ -106,7 +102,7 @@ public class SmilMediaElementImpl extends SmilElementImpl implements // Discrete media dur = 0; } else { - Log.w(TAG, "Unknown media type"); + Timber.w("Unknown media type"); } } return dur; diff --git a/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilRegionElementImpl.java b/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilRegionElementImpl.java index efd62f0c3a15dc07f5f161703aa2fadd01d14907..6f136ef7530cf2e819124c5c8ceca067faec174e 100755 --- a/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilRegionElementImpl.java +++ b/android-smsmms/src/main/java/com/android/mms/dom/smil/SmilRegionElementImpl.java @@ -16,11 +16,10 @@ package com.android.mms.dom.smil; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import org.w3c.dom.DOMException; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILRegionElement; +import timber.log.Timber; public class SmilRegionElementImpl extends SmilElementImpl implements SMILRegionElement { @@ -45,8 +44,6 @@ public class SmilRegionElementImpl extends SmilElementImpl implements private static final String RIGHT_ATTRIBUTE_NAME = "right"; private static final String BOTTOM_ATTRIBUTE_NAME = "bottom"; private static final String FIT_ATTRIBUTE_NAME = "fit"; - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; SmilRegionElementImpl(SmilDocumentImpl owner, String tagName) { @@ -77,7 +74,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements return parseRegionLength(getAttribute(LEFT_ATTRIBUTE_NAME), true); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Left attribute is not set or incorrect."); + Timber.v("Left attribute is not set or incorrect."); } } try { @@ -87,7 +84,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements return bbw - right - width; } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Right or width attribute is not set or incorrect."); + Timber.v("Right or width attribute is not set or incorrect."); } } return 0; @@ -98,7 +95,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements return parseRegionLength(getAttribute(TOP_ATTRIBUTE_NAME), false); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Top attribute is not set or incorrect."); + Timber.v("Top attribute is not set or incorrect."); } } try { @@ -108,7 +105,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements return bbh - bottom - height; } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Bottom or height attribute is not set or incorrect."); + Timber.v("Bottom or height attribute is not set or incorrect."); } } return 0; @@ -161,7 +158,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements height; } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Height attribute is not set or incorrect."); + Timber.v("Height attribute is not set or incorrect."); } } int bbh = ((SMILDocument) getOwnerDocument()).getLayout().getRootLayout().getHeight(); @@ -169,14 +166,14 @@ public class SmilRegionElementImpl extends SmilElementImpl implements bbh -= parseRegionLength(getAttribute(TOP_ATTRIBUTE_NAME), false); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Top attribute is not set or incorrect."); + Timber.v("Top attribute is not set or incorrect."); } } try { bbh -= parseRegionLength(getAttribute(BOTTOM_ATTRIBUTE_NAME), false); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Bottom attribute is not set or incorrect."); + Timber.v("Bottom attribute is not set or incorrect."); } } return bbh; @@ -194,7 +191,7 @@ public class SmilRegionElementImpl extends SmilElementImpl implements width; } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Width attribute is not set or incorrect."); + Timber.v("Width attribute is not set or incorrect."); } } int bbw = ((SMILDocument) getOwnerDocument()).getLayout().getRootLayout().getWidth(); @@ -202,14 +199,14 @@ public class SmilRegionElementImpl extends SmilElementImpl implements bbw -= parseRegionLength(getAttribute(LEFT_ATTRIBUTE_NAME), true); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Left attribute is not set or incorrect."); + Timber.v("Left attribute is not set or incorrect."); } } try { bbw -= parseRegionLength(getAttribute(RIGHT_ATTRIBUTE_NAME), true); } catch (NumberFormatException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Right attribute is not set or incorrect."); + Timber.v("Right attribute is not set or incorrect."); } } return bbw; diff --git a/android-smsmms/src/main/java/com/android/mms/dom/smil/parser/SmilXmlSerializer.java b/android-smsmms/src/main/java/com/android/mms/dom/smil/parser/SmilXmlSerializer.java index 8605ca69165c946d02214933a84e6c02b005afd3..7ab3f90f06e2d587b9e62a8208f17538e5e0e6d9 100755 --- a/android-smsmms/src/main/java/com/android/mms/dom/smil/parser/SmilXmlSerializer.java +++ b/android-smsmms/src/main/java/com/android/mms/dom/smil/parser/SmilXmlSerializer.java @@ -16,12 +16,12 @@ package com.android.mms.dom.smil.parser; -import com.klinker.android.logger.Log; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILElement; +import timber.log.Timber; import java.io.BufferedWriter; import java.io.IOException; @@ -31,7 +31,6 @@ import java.io.UnsupportedEncodingException; import java.io.Writer; public class SmilXmlSerializer { - private static final String TAG = "SmilXmlSerializer"; public static void serialize(SMILDocument smilDoc, OutputStream out) { try { @@ -40,9 +39,9 @@ public class SmilXmlSerializer { writeElement(writer, smilDoc.getDocumentElement()); writer.flush(); } catch (UnsupportedEncodingException e) { - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } catch (IOException e) { - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); } } diff --git a/android-smsmms/src/main/java/com/android/mms/layout/HVGALayoutParameters.java b/android-smsmms/src/main/java/com/android/mms/layout/HVGALayoutParameters.java index ddb27dacb57674c7be7486d26bfcdff8fd88cb10..61cf1da5a3fe72aea4201a9ff8fea0b41585dccc 100755 --- a/android-smsmms/src/main/java/com/android/mms/layout/HVGALayoutParameters.java +++ b/android-smsmms/src/main/java/com/android/mms/layout/HVGALayoutParameters.java @@ -17,12 +17,9 @@ package com.android.mms.layout; import android.content.Context; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; public class HVGALayoutParameters implements LayoutParameters { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private int mType = -1; @@ -41,7 +38,7 @@ public class HVGALayoutParameters implements LayoutParameters { } if (LOCAL_LOGV) { - Log.v(TAG, "HVGALayoutParameters.(" + type + ")."); + Timber.v("HVGALayoutParameters.(" + type + ")."); } mType = type; @@ -56,7 +53,7 @@ public class HVGALayoutParameters implements LayoutParameters { mTextHeightPortrait = (int) (mMaxWidth * .10f); if (LOCAL_LOGV) { - Log.v(TAG, "HVGALayoutParameters mMaxWidth: " + mMaxWidth + + Timber.v("HVGALayoutParameters mMaxWidth: " + mMaxWidth + " mMaxHeight: " + mMaxHeight + " mImageHeightLandscape: " + mImageHeightLandscape + " mTextHeightLandscape: " + mTextHeightLandscape + diff --git a/android-smsmms/src/main/java/com/android/mms/layout/LayoutManager.java b/android-smsmms/src/main/java/com/android/mms/layout/LayoutManager.java index 6b2884ad79ee5b2017bc37f2098f39992d9cff38..5113b4a72973d9a0523d6ea2a794e0726fd26e7f 100755 --- a/android-smsmms/src/main/java/com/android/mms/layout/LayoutManager.java +++ b/android-smsmms/src/main/java/com/android/mms/layout/LayoutManager.java @@ -18,14 +18,12 @@ package com.android.mms.layout; import android.content.Context; import android.content.res.Configuration; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; /** * MMS presentation layout management. */ public class LayoutManager { - private static final String TAG = LogTag.TAG; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; @@ -46,7 +44,7 @@ public class LayoutManager { : LayoutParameters.HVGA_LANDSCAPE); if (LOCAL_LOGV) { - Log.v(TAG, "LayoutParameters: " + mLayoutParams.getTypeDescription() + Timber.v("LayoutParameters: " + mLayoutParams.getTypeDescription() + ": " + mLayoutParams.getWidth() + "x" + mLayoutParams.getHeight()); } } @@ -65,11 +63,11 @@ public class LayoutManager { public static void init(Context context) { if (LOCAL_LOGV) { - Log.v(TAG, "DefaultLayoutManager.init()"); + Timber.v("DefaultLayoutManager.init()"); } if (sInstance != null) { - Log.w(TAG, "Already initialized."); + Timber.w("Already initialized."); } sInstance = new LayoutManager(context); } @@ -83,7 +81,7 @@ public class LayoutManager { public void onConfigurationChanged(Configuration newConfig) { if (LOCAL_LOGV) { - Log.v(TAG, "-> LayoutManager.onConfigurationChanged()."); + Timber.v("-> LayoutManager.onConfigurationChanged()."); } initLayoutParameters(newConfig); } diff --git a/android-smsmms/src/main/java/com/android/mms/logs/LogTag.java b/android-smsmms/src/main/java/com/android/mms/logs/LogTag.java deleted file mode 100755 index a93c8ec01b2ca575c345c9b84925b69864fd5f9b..0000000000000000000000000000000000000000 --- a/android-smsmms/src/main/java/com/android/mms/logs/LogTag.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2014 Jacob Klinker - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.mms.logs; - -public class LogTag { - public static final String TAG = "Mms"; - - public static final String TRANSACTION = TAG; -} diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/ApnSettings.java b/android-smsmms/src/main/java/com/android/mms/service_alt/ApnSettings.java index fd5b1326ab256204ec5d71cb42d4eb13f111b7f5..cf36a4043cbe77b6f315c5191e5bacc23e4702b3 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/ApnSettings.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/ApnSettings.java @@ -26,7 +26,7 @@ import android.preference.PreferenceManager; import android.provider.Telephony; import android.text.TextUtils; import com.android.mms.service_alt.exception.ApnException; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.net.URI; import java.net.URISyntaxException; @@ -35,7 +35,6 @@ import java.net.URISyntaxException; * APN settings used for MMS transactions */ public class ApnSettings { - private static final String TAG = "ApnSettings"; // MMSC URL private final String mServiceCenter; @@ -100,7 +99,7 @@ public class ApnSettings { return new ApnSettings(mmsc, mmsProxy, parsePort(mmsPort), "Default from settings"); } - Log.v(TAG, "ApnSettings: apnName " + apnName); + Timber.v("ApnSettings: apnName " + apnName); // TODO: CURRENT semantics is currently broken in telephony. Revive this when it is fixed. //String selection = Telephony.Carriers.CURRENT + " IS NOT NULL"; String selection = null; @@ -148,7 +147,7 @@ public class ApnSettings { try { proxyPort = Integer.parseInt(portString); } catch (NumberFormatException e) { - Log.e(TAG, "Invalid port " + portString); + Timber.e("Invalid port " + portString); throw new ApnException("Invalid port " + portString); } } diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java b/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java index cb48e2eecd9fc2c3ec65aabda3f95230efdff23b..8a556aee2941a3ad192ad9ad6daf59c63e55ef61 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/DownloadRequest.java @@ -27,7 +27,6 @@ import android.os.Binder; import android.os.Bundle; import android.provider.Telephony; import android.text.TextUtils; -import android.util.Log; import com.android.mms.service_alt.exception.MmsHttpException; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.GenericPdu; @@ -38,30 +37,30 @@ import com.google.android.mms.pdu_alt.RetrieveConf; import com.google.android.mms.util_alt.SqliteWrapper; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.Transaction; +import timber.log.Timber; /** * Request to download an MMS */ public class DownloadRequest extends MmsRequest { - private static final String TAG = "DownloadRequest"; private static final String LOCATION_SELECTION = Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?"; - static final String[] PROJECTION = new String[] { + static final String[] PROJECTION = new String[]{ Telephony.Mms.CONTENT_LOCATION }; // The indexes of the columns which must be consistent with above PROJECTION. - static final int COLUMN_CONTENT_LOCATION = 0; + static final int COLUMN_CONTENT_LOCATION = 0; private final String mLocationUrl; private final PendingIntent mDownloadedIntent; private final Uri mContentUri; public DownloadRequest(RequestManager manager, int subId, String locationUrl, - Uri contentUri, PendingIntent downloadedIntent, String creator, - Bundle configOverrides, Context context) throws MmsException { + Uri contentUri, PendingIntent downloadedIntent, String creator, + Bundle configOverrides, Context context) throws MmsException { super(manager, subId, creator, configOverrides); if (locationUrl == null) { @@ -79,7 +78,7 @@ public class DownloadRequest extends MmsRequest { throws MmsHttpException { final MmsHttpClient mmsHttpClient = netMgr.getOrCreateHttpClient(); if (mmsHttpClient == null) { - Log.e(TAG, "MMS network is not ready!"); + Timber.e("MMS network is not ready!"); throw new MmsHttpException(0/*statusCode*/, "MMS network is not ready"); } return mmsHttpClient.execute( @@ -117,9 +116,9 @@ public class DownloadRequest extends MmsRequest { // Let any mms apps running as secondary user know that a new mms has been downloaded. notifyOfDownload(context); - Log.d(TAG, "DownloadRequest.persistIfRequired"); + Timber.d("DownloadRequest.persistIfRequired"); if (response == null || response.length < 1) { - Log.e(TAG, "DownloadRequest.persistIfRequired: empty response"); + Timber.e("DownloadRequest.persistIfRequired: empty response"); // Update the retrieve status of the NotificationInd final ContentValues values = new ContentValues(1); values.put(Telephony.Mms.RETRIEVE_STATUS, PduHeaders.RETRIEVE_STATUS_ERROR_END); @@ -140,7 +139,7 @@ public class DownloadRequest extends MmsRequest { final GenericPdu pdu = (new PduParser(response, mmsConfig.getSupportMmsContentDisposition())).parse(); if (pdu == null || !(pdu instanceof RetrieveConf)) { - Log.e(TAG, "DownloadRequest.persistIfRequired: invalid parsed PDU"); + Timber.e("DownloadRequest.persistIfRequired: invalid parsed PDU"); // Update the error type of the NotificationInd setErrorType(context, locationUrl, Telephony.MmsSms.ERR_TYPE_MMS_PROTO_PERMANENT); @@ -149,7 +148,7 @@ public class DownloadRequest extends MmsRequest { final RetrieveConf retrieveConf = (RetrieveConf) pdu; final int status = retrieveConf.getRetrieveStatus(); // if (status != PduHeaders.RETRIEVE_STATUS_OK) { -// Log.e(TAG, "DownloadRequest.persistIfRequired: retrieve failed " +// Timber.e("DownloadRequest.persistIfRequired: retrieve failed " // + status); // // Update the retrieve status of the NotificationInd // final ContentValues values = new ContentValues(1); @@ -168,16 +167,12 @@ public class DownloadRequest extends MmsRequest { // } // Store the downloaded message final PduPersister persister = PduPersister.getPduPersister(context); - final Uri messageUri = persister.persist( - pdu, - Telephony.Mms.Inbox.CONTENT_URI, - true/*createThreadId*/, - true/*groupMmsEnabled*/, - null/*preOpenedFiles*/); + final Uri messageUri = persister.persist(pdu, Telephony.Mms.Inbox.CONTENT_URI, true, true, null); if (messageUri == null) { - Log.e(TAG, "DownloadRequest.persistIfRequired: can not persist message"); + Timber.e("DownloadRequest.persistIfRequired: can not persist message"); return null; } + // Update some of the properties of the message final ContentValues values = new ContentValues(); values.put(Telephony.Mms.DATE, System.currentTimeMillis() / 1000L); @@ -191,32 +186,31 @@ public class DownloadRequest extends MmsRequest { values.put(Telephony.Mms.SUBSCRIPTION_ID, subId); } - if (SqliteWrapper.update( - context, - context.getContentResolver(), - messageUri, - values, - null/*where*/, - null/*selectionArg*/) != 1) { - Log.e(TAG, "DownloadRequest.persistIfRequired: can not update message"); + try { + context.getContentResolver().update(messageUri, values, null, null); + } catch (SQLiteException e) { + // On MIUI and a couple other devices, the above call will fail and say `no such column: sub_id` + // If before making that call, we check to see if the sub_id column is available for messageUri, we will + // find that it is available, and yet the update call will still fail. So - there's no way we can know + // in advance that it will fail, and we have to just try again + if (values.containsKey(Telephony.Mms.SUBSCRIPTION_ID)) { + values.remove(Telephony.Mms.SUBSCRIPTION_ID); + context.getContentResolver().update(messageUri, values, null, null); + } else { + throw e; + } } // Delete the corresponding NotificationInd - SqliteWrapper.delete(context, - context.getContentResolver(), - Telephony.Mms.CONTENT_URI, - LOCATION_SELECTION, - new String[]{ - Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), - locationUrl - }); + SqliteWrapper.delete(context, context.getContentResolver(), Telephony.Mms.CONTENT_URI, LOCATION_SELECTION, + new String[]{Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), locationUrl}); return messageUri; } catch (MmsException e) { - Log.e(TAG, "DownloadRequest.persistIfRequired: can not persist message", e); + Timber.e(e, "DownloadRequest.persistIfRequired: can not persist message"); } catch (SQLiteException e) { - Log.e(TAG, "DownloadRequest.persistIfRequired: can not update message", e); + Timber.e(e, "DownloadRequest.persistIfRequired: can not update message"); } catch (RuntimeException e) { - Log.e(TAG, "DownloadRequest.persistIfRequired: can not parse response", e); + Timber.e(e, "DownloadRequest.persistIfRequired: can not parse response"); } finally { Binder.restoreCallingIdentity(identity); } @@ -269,7 +263,7 @@ public class DownloadRequest extends MmsRequest { /** * Transfer the received response to the caller (for download requests write to content uri) * - * @param fillIn the intent that will be returned to the caller + * @param fillIn the intent that will be returned to the caller * @param response the pdu to transfer */ @Override @@ -285,7 +279,7 @@ public class DownloadRequest extends MmsRequest { /** * Try downloading via the carrier app. * - * @param context The context + * @param context The context * @param carrierMessagingServicePackage The carrier messaging service handling the download */ public void tryDownloadingByCarrierApp(Context context, String carrierMessagingServicePackage) { @@ -323,10 +317,10 @@ public class DownloadRequest extends MmsRequest { private static Long getId(Context context, String location) { String selection = Telephony.Mms.CONTENT_LOCATION + " = ?"; - String[] selectionArgs = new String[] { location }; + String[] selectionArgs = new String[]{location}; Cursor c = android.database.sqlite.SqliteWrapper.query( context, context.getContentResolver(), - Telephony.Mms.CONTENT_URI, new String[] { Telephony.Mms._ID }, + Telephony.Mms.CONTENT_URI, new String[]{Telephony.Mms._ID}, selection, selectionArgs, null); if (c != null) { try { diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfig.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfig.java index bb8fa825de0286b86bdb93356bf2ea13329a7a70..00eb2357a3710662cdcfde3c8019b3daf0499646 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfig.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfig.java @@ -24,8 +24,8 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Base64; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.R; +import timber.log.Timber; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; @@ -44,7 +44,6 @@ import java.util.concurrent.ConcurrentHashMap; * 4. Add key/value for relevant mms_config.xml of a specific carrier (mcc/mnc) */ public class MmsConfig { - private static final String TAG = "MmsConfig"; private static final String DEFAULT_HTTP_KEY_X_WAP_PROFILE = "x-wap-profile"; @@ -198,10 +197,10 @@ public class MmsConfig { mKeyValues.putAll(DEFAULTS); // Load User-Agent and UA profile URL settings loadDeviceUaSettings(context); - Log.v(TAG, "MmsConfig: mUserAgent=" + mUserAgent + ", mUaProfUrl=" + mUaProfUrl); + Timber.v("MmsConfig: mUserAgent=" + mUserAgent + ", mUaProfUrl=" + mUaProfUrl); // Load mms_config.xml resource overlays loadFromResources(context); - Log.v(TAG, "MmsConfig: all settings -- " + mKeyValues); + Timber.v("MmsConfig: all settings -- " + mKeyValues); } /** @@ -218,10 +217,10 @@ public class MmsConfig { mKeyValues.putAll(DEFAULTS); // Load User-Agent and UA profile URL settings loadDeviceUaSettings(context); - Log.v(TAG, "MmsConfig: mUserAgent=" + mUserAgent + ", mUaProfUrl=" + mUaProfUrl); + Timber.v("MmsConfig: mUserAgent=" + mUserAgent + ", mUaProfUrl=" + mUaProfUrl); // Load mms_config.xml resource overlays loadFromResources(context); - Log.v(TAG, "MmsConfig: all settings -- " + mKeyValues); + Timber.v("MmsConfig: all settings -- " + mKeyValues); } /** @@ -328,7 +327,7 @@ public class MmsConfig { mKeyValues.put(key, value); } } catch (NumberFormatException e) { - Log.e(TAG, "MmsConfig.update: invalid " + key + "," + value + "," + type); + Timber.e("MmsConfig.update: invalid " + key + "," + value + "," + type); } } @@ -345,7 +344,7 @@ public class MmsConfig { } private void loadFromResources(Context context) { - Log.d(TAG, "MmsConfig.loadFromResources"); + Timber.d("MmsConfig.loadFromResources"); final XmlResourceParser parser = context.getResources().getXml(R.xml.mms_config); final MmsConfigXmlProcessor processor = MmsConfigXmlProcessor.get(parser); processor.setMmsConfigHandler(new MmsConfigXmlProcessor.MmsConfigHandler() { @@ -594,9 +593,7 @@ public class MmsConfig { } } - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "MmsConfig.getNai: nai=" + nai); - } + Timber.v("MmsConfig.getNai: nai=" + nai); if (!TextUtils.isEmpty(nai)) { String naiSuffix = getNaiSuffix(); diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigManager.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigManager.java index ad47a62af26e52d2e10d90035affe3c22191b536..1f1aca0abae4a44c55ef7152b36f9d97f3fa46e9 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigManager.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigManager.java @@ -25,7 +25,7 @@ import android.os.Build; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.ArrayMap; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.List; import java.util.Map; @@ -37,7 +37,6 @@ import java.util.Map; * */ public class MmsConfigManager { - private static final String TAG = "MmsConfigManager"; private static volatile MmsConfigManager sInstance = new MmsConfigManager(); @@ -63,7 +62,7 @@ public class MmsConfigManager { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - Log.i(TAG, "mReceiver action: " + action); + Timber.i("mReceiver action: " + action); if (action.equals("LOADED")) { loadInBackground(); } @@ -116,7 +115,7 @@ public class MmsConfigManager { Configuration configuration = mContext.getResources().getConfiguration(); // Always put the mnc/mcc in the log so we can tell which mms_config.xml // was loaded. - Log.i(TAG, "MmsConfigManager.loadInBackground(): mcc/mnc: " + + Timber.i("MmsConfigManager.loadInBackground(): mcc/mnc: " + configuration.mcc + "/" + configuration.mnc); load(mContext); } @@ -137,7 +136,7 @@ public class MmsConfigManager { synchronized(mSubIdConfigMap) { mmsConfig = mSubIdConfigMap.get(subId); } - Log.i(TAG, "getMmsConfigBySubId -- for sub: " + subId + " mmsConfig: " + mmsConfig); + Timber.i("getMmsConfigBySubId -- for sub: " + subId + " mmsConfig: " + mmsConfig); return mmsConfig; } @@ -155,7 +154,7 @@ public class MmsConfigManager { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { List subs = mSubscriptionManager.getActiveSubscriptionInfoList(); if (subs == null || subs.size() < 1) { - Log.e(TAG, "MmsConfigManager.load -- empty getActiveSubInfoList"); + Timber.e("MmsConfigManager.load -- empty getActiveSubInfoList"); return; } // Load all the mms_config.xml files in a separate map and then swap with the @@ -167,11 +166,11 @@ public class MmsConfigManager { Configuration config = mContext.getResources().getConfiguration(); configuration.mcc = config.mcc; configuration.mnc = config.mnc; - Log.i(TAG, "MmsConfigManager.load -- no mcc/mnc for sub: " + sub + + Timber.i("MmsConfigManager.load -- no mcc/mnc for sub: " + sub + " using mcc/mnc from main context: " + configuration.mcc + "/" + configuration.mnc); } else { - Log.i(TAG, "MmsConfigManager.load -- mcc/mnc for sub: " + sub); + Timber.i("MmsConfigManager.load -- mcc/mnc for sub: " + sub); configuration.mcc = sub.getMcc(); configuration.mnc = sub.getMnc(); diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigXmlProcessor.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigXmlProcessor.java index e087efba0f83a47e4c856162c3027929b609a4c8..b95cd80c694bc85108b8f739ca4f317ae071e363 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigXmlProcessor.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsConfigXmlProcessor.java @@ -17,9 +17,9 @@ package com.android.mms.service_alt; import android.content.ContentValues; -import com.klinker.android.logger.Log; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import timber.log.Timber; import java.io.IOException; @@ -27,7 +27,6 @@ import java.io.IOException; * XML processor for mms_config.xml */ public class MmsConfigXmlProcessor { - private static final String TAG = "MmsConfigXmlProcessor"; public interface MmsConfigHandler { public void process(String key, String value, String type); @@ -92,9 +91,9 @@ public class MmsConfigXmlProcessor { processMmsConfig(); } } catch (IOException e) { - Log.e(TAG, "MmsConfigXmlProcessor: I/O failure " + e, e); + Timber.e(e, "MmsConfigXmlProcessor: I/O failure " + e); } catch (XmlPullParserException e) { - Log.e(TAG, "MmsConfigXmlProcessor: parsing failure " + e, e); + Timber.e(e, "MmsConfigXmlProcessor: parsing failure " + e); } } @@ -132,7 +131,7 @@ public class MmsConfigXmlProcessor { } return mLogStringBuilder.toString(); } catch (XmlPullParserException e) { - Log.e(TAG, "xmlParserDebugContext: " + e, e); + Timber.e(e, "xmlParserDebugContext: " + e); } } return "Unknown"; @@ -190,7 +189,7 @@ public class MmsConfigXmlProcessor { mMmsConfigHandler.process(key, value, type); } } else { - Log.w(TAG, "MmsConfig: invalid key=" + key + " or type=" + type); + Timber.w("MmsConfig: invalid key=" + key + " or type=" + type); } } } diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsHttpClient.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsHttpClient.java index f03699b0a096fd69558c2b06ab5375f4ec3597b6..aa31f9d8b4b13ad4567ec28b570665fabcee08f2 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsHttpClient.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsHttpClient.java @@ -18,7 +18,6 @@ package com.android.mms.service_alt; import android.content.Context; import android.text.TextUtils; -import android.util.Log; import com.android.mms.service_alt.exception.MmsHttpException; import com.squareup.okhttp.ConnectionPool; import com.squareup.okhttp.ConnectionSpec; @@ -29,6 +28,7 @@ import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.Internal; import com.squareup.okhttp.internal.huc.HttpURLConnectionImpl; import com.squareup.okhttp.internal.huc.HttpsURLConnectionImpl; +import timber.log.Timber; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; @@ -60,7 +60,6 @@ import java.util.regex.Pattern; * MMS HTTP client for sending and downloading MMS messages */ public class MmsHttpClient { - private static final String TAG = "MmsHttpClient"; public static final String METHOD_POST = "POST"; public static final String METHOD_GET = "GET"; @@ -117,7 +116,7 @@ public class MmsHttpClient { public byte[] execute(String urlString, byte[] pdu, String method, boolean isProxySet, String proxyHost, int proxyPort, MmsConfig.Overridden mmsConfig) throws MmsHttpException { - Log.d(TAG, "HTTP: " + method + " " + redactUrlForNonVerbose(urlString) + Timber.d("HTTP: " + method + " " + urlString + (isProxySet ? (", proxy=" + proxyHost + ":" + proxyPort) : "") + ", PDU size=" + (pdu != null ? pdu.length : 0)); checkMethod(method); @@ -140,13 +139,13 @@ public class MmsHttpClient { HEADER_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault())); // Header: User-Agent final String userAgent = mmsConfig.getUserAgent(); - Log.i(TAG, "HTTP: User-Agent=" + userAgent); + Timber.i("HTTP: User-Agent=" + userAgent); connection.setRequestProperty(HEADER_USER_AGENT, userAgent); // Header: x-wap-profile final String uaProfUrlTagName = mmsConfig.getUaProfTagName(); final String uaProfUrl = mmsConfig.getUaProfUrl(); if (uaProfUrl != null) { - Log.i(TAG, "HTTP: UaProfUrl=" + uaProfUrl); + Timber.i("HTTP: UaProfUrl=" + uaProfUrl); connection.setRequestProperty(uaProfUrlTagName, uaProfUrl); } // Add extra headers specified by mms_config.xml's httpparams @@ -154,7 +153,7 @@ public class MmsHttpClient { // Different stuff for GET and POST if (METHOD_POST.equals(method)) { if (pdu == null || pdu.length < 1) { - Log.e(TAG, "HTTP: empty pdu"); + Timber.e("HTTP: empty pdu"); throw new MmsHttpException(0/*statusCode*/, "Sending empty PDU"); } connection.setDoOutput(true); @@ -166,29 +165,22 @@ public class MmsHttpClient { connection.setRequestProperty(HEADER_CONTENT_TYPE, HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET); } - if (Log.isLoggable(TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getRequestProperties()); - } + logHttpHeaders(connection.getRequestProperties()); connection.setFixedLengthStreamingMode(pdu.length); // Sending request body - final OutputStream out = - new BufferedOutputStream(connection.getOutputStream()); + final OutputStream out = new BufferedOutputStream(connection.getOutputStream()); out.write(pdu); out.flush(); out.close(); } else if (METHOD_GET.equals(method)) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getRequestProperties()); - } + logHttpHeaders(connection.getRequestProperties()); connection.setRequestMethod(METHOD_GET); } // Get response final int responseCode = connection.getResponseCode(); final String responseMessage = connection.getResponseMessage(); - Log.d(TAG, "HTTP: " + responseCode + " " + responseMessage); - if (Log.isLoggable(TAG, Log.VERBOSE)) { - logHttpHeaders(connection.getHeaderFields()); - } + Timber.d("HTTP: " + responseCode + " " + responseMessage); + logHttpHeaders(connection.getHeaderFields()); if (responseCode / 100 != 2) { throw new MmsHttpException(responseCode, responseMessage); } @@ -201,19 +193,17 @@ public class MmsHttpClient { } in.close(); final byte[] responseBody = byteOut.toByteArray(); - Log.d(TAG, "HTTP: response size=" + Timber.d("HTTP: response size=" + (responseBody != null ? responseBody.length : 0)); return responseBody; } catch (MalformedURLException e) { - final String redactedUrl = redactUrlForNonVerbose(urlString); - Log.e(TAG, "HTTP: invalid URL " + redactedUrl, e); - throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + redactedUrl, e); + Timber.e(e, "HTTP: invalid URL " + urlString); + throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + urlString, e); } catch (ProtocolException e) { - final String redactedUrl = redactUrlForNonVerbose(urlString); - Log.e(TAG, "HTTP: invalid URL protocol " + redactedUrl, e); - throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + redactedUrl, e); + Timber.e(e, "HTTP: invalid URL protocol " + urlString); + throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + urlString, e); } catch (IOException e) { - Log.e(TAG, "HTTP: IO failure", e); + Timber.e(e, "HTTP: IO failure"); throw new MmsHttpException(0/*statusCode*/, e); } finally { if (connection != null) { @@ -328,7 +318,7 @@ public class MmsHttpClient { } } } - Log.v(TAG, "HTTP: headers\n" + sb.toString()); + Timber.v("HTTP: headers\n" + sb.toString()); } } @@ -423,7 +413,7 @@ public class MmsHttpClient { if (macroValue != null) { replaced.append(macroValue); } else { - Log.w(TAG, "HTTP: invalid macro " + macro); + Timber.w("HTTP: invalid macro " + macro); } nextStart = matcher.end(); } @@ -459,35 +449,4 @@ public class MmsHttpClient { } } } - - /** - * Redact the URL for non-VERBOSE logging. Replace url with only the host part and the length - * of the input URL string. - * - * @param urlString - * @return - */ - public static String redactUrlForNonVerbose(String urlString) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - // Don't redact for VERBOSE level logging - return urlString; - } - if (TextUtils.isEmpty(urlString)) { - return urlString; - } - String protocol = "http"; - String host = ""; - try { - final URL url = new URL(urlString); - protocol = url.getProtocol(); - host = url.getHost(); - } catch (MalformedURLException e) { - // Ignore - } - // Print "http://host[length]" - final StringBuilder sb = new StringBuilder(); - sb.append(protocol).append("://").append(host) - .append("[").append(urlString.length()).append("]"); - return sb.toString(); - } } diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsNetworkManager.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsNetworkManager.java index 509b8a865779de76bb393f58b832e719f9cbc67a..ce3fe460e3b92c948bd9c04ad352da5e0c6b912c 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsNetworkManager.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsNetworkManager.java @@ -26,14 +26,14 @@ import android.net.SSLCertificateSocketFactory; import android.os.Build; import android.os.SystemClock; import com.android.mms.service_alt.exception.MmsNetworkException; -import com.klinker.android.logger.Log; import com.squareup.okhttp.ConnectionPool; +import timber.log.Timber; import java.net.InetAddress; import java.net.UnknownHostException; public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { - private static final String TAG = "MmsNetworkManager"; + // Timeout used to call ConnectivityManager.requestNetwork private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000; // Wait timeout for this class, a little bit longer than the above timeout @@ -118,10 +118,10 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { mMmsRequestCount += 1; if (mNetwork != null) { // Already available - Log.d(TAG, "MmsNetworkManager: already available"); + Timber.d("MmsNetworkManager: already available"); return mNetwork; } - Log.d(TAG, "MmsNetworkManager: start new network request"); + Timber.d("MmsNetworkManager: start new network request"); // Not available, so start a new request newRequest(); final long shouldEnd = SystemClock.elapsedRealtime() + NETWORK_ACQUIRE_TIMEOUT_MILLIS; @@ -130,7 +130,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { try { this.wait(waitTime); } catch (InterruptedException e) { - Log.w(TAG, "MmsNetworkManager: acquire network wait interrupted"); + Timber.w("MmsNetworkManager: acquire network wait interrupted"); } if (mNetwork != null || permissionError) { // Success @@ -140,7 +140,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { waitTime = shouldEnd - SystemClock.elapsedRealtime(); } // Timed out, so release the request and fail - Log.d(TAG, "MmsNetworkManager: timed out"); + Timber.d("MmsNetworkManager: timed out"); releaseRequestLocked(mNetworkCallback); throw new MmsNetworkException("Acquiring network timed out"); } @@ -153,7 +153,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { synchronized (this) { if (mMmsRequestCount > 0) { mMmsRequestCount -= 1; - Log.d(TAG, "MmsNetworkManager: release, count=" + mMmsRequestCount); + Timber.d("MmsNetworkManager: release, count=" + mMmsRequestCount); if (mMmsRequestCount < 1) { releaseRequestLocked(mNetworkCallback); } @@ -171,7 +171,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { @Override public void onAvailable(Network network) { super.onAvailable(network); - Log.d(TAG, "NetworkCallbackListener.onAvailable: network=" + network); + Timber.d("NetworkCallbackListener.onAvailable: network=" + network); synchronized (MmsNetworkManager.this) { mNetwork = network; MmsNetworkManager.this.notifyAll(); @@ -181,7 +181,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { @Override public void onLost(Network network) { super.onLost(network); - Log.d(TAG, "NetworkCallbackListener.onLost: network=" + network); + Timber.d("NetworkCallbackListener.onLost: network=" + network); synchronized (MmsNetworkManager.this) { releaseRequestLocked(this); MmsNetworkManager.this.notifyAll(); @@ -191,7 +191,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { // @Override // public void onUnavailable() { // super.onUnavailable(); -// Log.d(TAG, "NetworkCallbackListener.onUnavailable"); +// Timber.d("NetworkCallbackListener.onUnavailable"); // synchronized (MmsNetworkManager.this) { // releaseRequestLocked(this); // MmsNetworkManager.this.notifyAll(); @@ -203,7 +203,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { connectivityManager.requestNetwork( mNetworkRequest, mNetworkCallback); } catch (SecurityException e) { - Log.e(TAG, "permission exception... skipping it for testing purposes", e); + Timber.e(e, "permission exception... skipping it for testing purposes"); permissionError = true; } } @@ -220,7 +220,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { try { connectivityManager.unregisterNetworkCallback(callback); } catch (Exception e) { - Log.e(TAG, "couldn't unregister", e); + Timber.e(e, "couldn't unregister"); } } resetLocked(); @@ -306,7 +306,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { Network network = null; synchronized (this) { if (mNetwork == null) { - Log.d(TAG, "MmsNetworkManager: getApnName: network not available"); + Timber.d("MmsNetworkManager: getApnName: network not available"); mNetworkRequest = new NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build(); @@ -320,7 +320,7 @@ public class MmsNetworkManager implements com.squareup.okhttp.internal.Network { if (mmsNetworkInfo != null) { apnName = mmsNetworkInfo.getExtraInfo(); } - Log.d(TAG, "MmsNetworkManager: getApnName: " + apnName); + Timber.d("MmsNetworkManager: getApnName: " + apnName); return apnName; } diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsRequest.java b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsRequest.java index dac4f9931f6c1f083a5a84a7eb1a311f2fd54659..7ba80c0728c25934c59eebfc94da1e76bcb79e18 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/MmsRequest.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/MmsRequest.java @@ -32,14 +32,13 @@ import android.telephony.SmsManager; import android.telephony.TelephonyManager; import com.android.mms.service_alt.exception.ApnException; import com.android.mms.service_alt.exception.MmsHttpException; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.Utils; +import timber.log.Timber; /** * Base class for MMS requests. This has the common logic of sending/downloading MMS. */ public abstract class MmsRequest { - private static final String TAG = "MmsRequest"; private static final int RETRY_TIMES = 3; @@ -153,21 +152,21 @@ public abstract class MmsRequest { } mobileDataEnabled = Utils.isMobileDataEnabled(context); - Log.v(TAG, "mobile data enabled: " + mobileDataEnabled); + Timber.v("mobile data enabled: " + mobileDataEnabled); if (!mobileDataEnabled && !useWifi(context)) { - Log.v(TAG, "mobile data not enabled, so forcing it to enable"); + Timber.v("mobile data not enabled, so forcing it to enable"); Utils.setMobileDataEnabled(context, true); } if (!ensureMmsConfigLoaded()) { // Check mms config - Log.e(TAG, "MmsRequest: mms config is not loaded yet"); + Timber.e("MmsRequest: mms config is not loaded yet"); result = SmsManager.MMS_ERROR_CONFIGURATION_ERROR; } else if (!prepareForHttpRequest()) { // Prepare request, like reading pdu data from user - Log.e(TAG, "MmsRequest: failed to prepare for request"); + Timber.e("MmsRequest: failed to prepare for request"); result = SmsManager.MMS_ERROR_IO_ERROR; } else if (!isDataNetworkAvailable(context, mSubId)) { - Log.e(TAG, "MmsRequest: in airplane mode or mobile data disabled"); + Timber.e("MmsRequest: in airplane mode or mobile data disabled"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { result = SmsManager.MMS_ERROR_NO_DATA_NETWORK; } else { @@ -181,7 +180,7 @@ public abstract class MmsRequest { try { networkManager.acquireNetwork(); } catch (Exception e) { - Log.e(TAG, "error acquiring network", e); + Timber.e(e, "error acquiring network"); } final String apnName = networkManager.getApnName(); @@ -195,11 +194,11 @@ public abstract class MmsRequest { // If the APN name was already null then don't need to retry throw (e); } - Log.i(TAG, "MmsRequest: No match with APN name:" + Timber.i("MmsRequest: No match with APN name:" + apnName + ", try with no name"); apn = ApnSettings.load(context, null, mSubId); } - Log.i(TAG, "MmsRequest: using " + apn.toString()); + Timber.i("MmsRequest: using " + apn.toString()); response = doHttp(context, networkManager, apn); result = Activity.RESULT_OK; // Success @@ -208,20 +207,20 @@ public abstract class MmsRequest { networkManager.releaseNetwork(); } } catch (ApnException e) { - Log.e(TAG, "MmsRequest: APN failure", e); + Timber.e(e, "MmsRequest: APN failure"); result = SmsManager.MMS_ERROR_INVALID_APN; break; // } catch (MmsNetworkException e) { -// Log.e(TAG, "MmsRequest: MMS network acquiring failure", e); +// Timber.e(e, "MmsRequest: MMS network acquiring failure"); // result = SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS; // // Retry } catch (MmsHttpException e) { - Log.e(TAG, "MmsRequest: HTTP or network I/O failure", e); + Timber.e(e, "MmsRequest: HTTP or network I/O failure"); result = SmsManager.MMS_ERROR_HTTP_FAILURE; httpStatusCode = e.getStatusCode(); // Retry } catch (Exception e) { - Log.e(TAG, "MmsRequest: unexpected failure", e); + Timber.e(e, "MmsRequest: unexpected failure"); result = SmsManager.MMS_ERROR_UNSPECIFIED; break; } @@ -234,7 +233,7 @@ public abstract class MmsRequest { } if (!mobileDataEnabled) { - Log.v(TAG, "setting mobile data back to disabled"); + Timber.v("setting mobile data back to disabled"); Utils.setMobileDataEnabled(context, false); } @@ -281,7 +280,7 @@ public abstract class MmsRequest { } pendingIntent.send(context, result, fillIn); } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "MmsRequest: sending pending intent canceled", e); + Timber.e(e, "MmsRequest: sending pending intent canceled"); } } @@ -313,7 +312,7 @@ public abstract class MmsRequest { == CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK || carrierMessagingAppResult == CarrierMessagingService.DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK) { - Log.d(TAG, "Sending/downloading MMS by IP failed."); + Timber.d("Sending/downloading MMS by IP failed."); mRequestManager.addSimRequest(MmsRequest.this); return true; } else { diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/PhoneUtils.java b/android-smsmms/src/main/java/com/android/mms/service_alt/PhoneUtils.java index 1954130f50420706f492fb5d206ec35d1e1f4f88..afdee112531967e6d6cbb9f82bd86e9757db74ff 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/PhoneUtils.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/PhoneUtils.java @@ -18,10 +18,10 @@ package com.android.mms.service_alt; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.Log; import com.android.i18n.phonenumbers.NumberParseException; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.Phonenumber; +import timber.log.Timber; import java.lang.reflect.Method; import java.util.Locale; @@ -31,8 +31,6 @@ import java.util.Locale; */ public class PhoneUtils { - private static final String TAG = "PhoneUtils"; - /** * Get a canonical national format phone number. If parsing fails, just return the * original number. @@ -63,12 +61,12 @@ public class PhoneUtils { if (phoneNumberUtil.isValidNumber(phoneNumber)) { return phoneNumber; } else { - Log.e(TAG, "getParsedNumber: not a valid phone number" + Timber.e("getParsedNumber: not a valid phone number" + " for country " + country); return null; } } catch (final NumberParseException e) { - Log.e(TAG, "getParsedNumber: Not able to parse phone number"); + Timber.e("getParsedNumber: Not able to parse phone number"); return null; } } diff --git a/android-smsmms/src/main/java/com/android/mms/service_alt/SubscriptionIdChecker.java b/android-smsmms/src/main/java/com/android/mms/service_alt/SubscriptionIdChecker.java index 351569e2f4d307023f1a1414b2cf642865481d76..afff0de0babbc632aa6d25fbcdde60bbc457ab6d 100755 --- a/android-smsmms/src/main/java/com/android/mms/service_alt/SubscriptionIdChecker.java +++ b/android-smsmms/src/main/java/com/android/mms/service_alt/SubscriptionIdChecker.java @@ -6,11 +6,10 @@ import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.os.Build; import android.provider.Telephony; -import android.util.Log; import com.google.android.mms.util_alt.SqliteWrapper; +import timber.log.Timber; class SubscriptionIdChecker { - private static final String TAG = "SubscriptionIdChecker"; private static SubscriptionIdChecker sInstance; private boolean mCanUseSubscriptionId = false; @@ -27,7 +26,7 @@ class SubscriptionIdChecker { mCanUseSubscriptionId = true; } } catch (SQLiteException e) { - Log.e(TAG, "SubscriptionIdChecker.check() fail"); + Timber.e("SubscriptionIdChecker.check() fail"); } finally { if (c != null) { c.close(); diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/DefaultRetryScheme.java b/android-smsmms/src/main/java/com/android/mms/transaction/DefaultRetryScheme.java index 1a8cf616978794015325a3d4e2abb97b072e4cb9..0879277423ce3a73ebf16c36baa147c971cbcd0a 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/DefaultRetryScheme.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/DefaultRetryScheme.java @@ -18,14 +18,12 @@ package com.android.mms.transaction; import android.content.Context; import android.util.Config; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; /** * Default retry scheme, based on specs. */ public class DefaultRetryScheme extends AbstractRetryScheme { - private static final String TAG = LogTag.TAG; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; @@ -50,7 +48,7 @@ public class DefaultRetryScheme extends AbstractRetryScheme { @Override public long getWaitingInterval() { if (LOCAL_LOGV) { - Log.v(TAG, "Next int: " + sDefaultRetryScheme[mRetriedTimes]); + Timber.v("Next int: " + sDefaultRetryScheme[mRetriedTimes]); } return sDefaultRetryScheme[mRetriedTimes]; } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java b/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java index 9c7c229b67413636c55b0686775c789d351ac05d..349b6e74494846efc8791905db0486ec9d9eb805 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/DownloadManager.java @@ -16,9 +16,9 @@ import android.provider.Telephony; import android.telephony.SmsManager; import android.text.TextUtils; import com.android.mms.MmsConfig; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.MmsReceivedReceiver; +import timber.log.Timber; import java.io.File; import java.util.Random; @@ -30,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap; * We should manage to call SMSManager.downloadMultimediaMessage(). */ public class DownloadManager { - private static final String TAG = "DownloadManager"; private static DownloadManager ourInstance = new DownloadManager(); private static final ConcurrentHashMap mMap = new ConcurrentHashMap<>(); @@ -58,7 +57,7 @@ public class DownloadManager { // Use unique action in order to avoid cancellation of notifying download result. context.getApplicationContext().registerReceiver(receiver, new IntentFilter(receiver.mAction)); - Log.v(TAG, "receiving with system method"); + Timber.v("receiving with system method"); final String fileName = "download." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat"; File mDownloadFile = new File(context.getCacheDir(), fileName); Uri contentUri = (new Uri.Builder()) diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/HttpUtils.java b/android-smsmms/src/main/java/com/android/mms/transaction/HttpUtils.java index 7e8743910eb7912496e831f7186c8c70e7d3714a..44457c30845247402ae21bfa154443cedf361bb7 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/HttpUtils.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/HttpUtils.java @@ -22,8 +22,6 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Config; import com.android.mms.MmsConfig; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; @@ -35,6 +33,7 @@ import org.apache.http.conn.params.ConnRouteParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; +import timber.log.Timber; import java.io.DataInputStream; import java.io.IOException; @@ -44,7 +43,6 @@ import java.net.URISyntaxException; import java.util.Locale; public class HttpUtils { - private static final String TAG = LogTag.TRANSACTION; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; @@ -95,19 +93,16 @@ public class HttpUtils { throw new IllegalArgumentException("URL must not be null."); } - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "httpConnection: params list"); - Log.v(TAG, "\ttoken\t\t= " + token); - Log.v(TAG, "\turl\t\t= " + url); - Log.v(TAG, "\tmethod\t\t= " - + ((method == HTTP_POST_METHOD) ? "POST" - : ((method == HTTP_GET_METHOD) ? "GET" : "UNKNOWN"))); - Log.v(TAG, "\tisProxySet\t= " + isProxySet); - Log.v(TAG, "\tproxyHost\t= " + proxyHost); - Log.v(TAG, "\tproxyPort\t= " + proxyPort); - // TODO Print out binary data more readable. - //Log.v(TAG, "\tpdu\t\t= " + Arrays.toString(pdu)); - } + Timber.v("httpConnection: params list"); + Timber.v("\ttoken\t\t= " + token); + Timber.v("\turl\t\t= " + url); + Timber.v("\tmethod\t\t= " + ((method == HTTP_POST_METHOD) ? "POST" + : ((method == HTTP_GET_METHOD) ? "GET" : "UNKNOWN"))); + Timber.v("\tisProxySet\t= " + isProxySet); + Timber.v("\tproxyHost\t= " + proxyHost); + Timber.v("\tproxyPort\t= " + proxyPort); + // TODO Print out binary data more readable. + //Timber.v("\tpdu\t\t= " + Arrays.toString(pdu)); AndroidHttpClient client = null; @@ -135,7 +130,7 @@ public class HttpUtils { req = new HttpGet(url); break; default: - Log.e(TAG, "Unknown HTTP method: " + method + Timber.e("Unknown HTTP method: " + method + ". Must be one of POST[" + HTTP_POST_METHOD + "] or GET[" + HTTP_GET_METHOD + "]."); return null; @@ -155,10 +150,7 @@ public class HttpUtils { String xWapProfileTagName = MmsConfig.getUaProfTagName(); String xWapProfileUrl = MmsConfig.getUaProfUrl(); if (xWapProfileUrl != null) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.d(LogTag.TRANSACTION, - "[HttpUtils] httpConn: xWapProfUrl=" + xWapProfileUrl); - } + Timber.d("[HttpUtils] httpConn: xWapProfUrl=" + xWapProfileUrl); req.addHeader(xWapProfileTagName, xWapProfileUrl); } } @@ -214,12 +206,12 @@ public class HttpUtils { try { dis.close(); } catch (IOException e) { - Log.e(TAG, "Error closing input stream: " + e.getMessage()); + Timber.e("Error closing input stream: " + e.getMessage()); } } } if (entity.isChunked()) { - Log.v(TAG, "httpConnection: transfer encoding is chunked"); + Timber.v("httpConnection: transfer encoding is chunked"); int bytesTobeRead = MmsConfig.getMaxMessageSize(); byte[] tempBody = new byte[bytesTobeRead]; DataInputStream dis = new DataInputStream(entity.getContent()); @@ -232,8 +224,7 @@ public class HttpUtils { bytesRead = dis.read(tempBody, offset, bytesTobeRead); } catch (IOException e) { readError = true; - Log.e(TAG, "httpConnection: error reading input stream" - + e.getMessage()); + Timber.e("httpConnection: error reading input stream" + e.getMessage()); break; } if (bytesRead > 0) { @@ -246,16 +237,15 @@ public class HttpUtils { // bytesRead will be -1 if the data was read till the eof body = new byte[offset]; System.arraycopy(tempBody, 0, body, 0, offset); - Log.v(TAG, "httpConnection: Chunked response length [" - + Integer.toString(offset) + "]"); + Timber.v("httpConnection: Chunked response length [" + Integer.toString(offset) + "]"); } else { - Log.e(TAG, "httpConnection: Response entity too large or empty"); + Timber.e("httpConnection: Response entity too large or empty"); } } finally { try { dis.close(); } catch (IOException e) { - Log.e(TAG, "Error closing input stream: " + e.getMessage()); + Timber.e("Error closing input stream: " + e.getMessage()); } } } @@ -288,7 +278,7 @@ public class HttpUtils { private static void handleHttpConnectionException(Exception exception, String url) throws IOException { // Inner exception should be logged to make life easier. - Log.e(TAG, "Url: " + url + "\n" + exception.getMessage()); + Timber.e("Url: " + url + "\n" + exception.getMessage()); IOException e = new IOException(exception.getMessage()); e.initCause(exception); throw e; @@ -303,10 +293,7 @@ public class HttpUtils { // set the socket timeout int soTimeout = MmsConfig.getHttpSocketTimeout(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) { - Log.d(TAG, "[HttpUtils] createHttpClient w/ socket timeout " + soTimeout + " ms, " - + ", UA=" + userAgent); - } + Timber.d("[HttpUtils] createHttpClient w/ socket timeout " + soTimeout + " ms, " + ", UA=" + userAgent); HttpConnectionParams.setSoTimeout(params, soTimeout); return client; } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/MessageStatusService.java b/android-smsmms/src/main/java/com/android/mms/transaction/MessageStatusService.java index de54d773361bd6aa10bfb3cb30a6c81cc543ce11..d4150616563e31a9c1a4a7f06f2389803307fb9d 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/MessageStatusService.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/MessageStatusService.java @@ -27,8 +27,7 @@ import android.net.Uri; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Inbox; import android.telephony.SmsMessage; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; /** * Service that gets started by the MessageStatusReceiver when a message status report is @@ -36,7 +35,6 @@ import com.klinker.android.logger.Log; */ public class MessageStatusService extends IntentService { private static final String[] ID_PROJECTION = new String[] { Sms._ID }; - private static final String LOG_TAG = LogTag.TAG; private static final Uri STATUS_URI = Uri.parse("content://sms/status"); public MessageStatusService() { @@ -88,10 +86,8 @@ public class MessageStatusService extends IntentService { boolean isStatusReport = message.isStatusReportMessage(); ContentValues contentValues = new ContentValues(2); - if (Log.isLoggable(LogTag.TAG, Log.DEBUG)) { - log("updateMessageStatus: msgUrl=" + messageUri + ", status=" + status + - ", isStatusReport=" + isStatusReport); - } + log("updateMessageStatus: msgUrl=" + messageUri + ", status=" + status + + ", isStatusReport=" + isStatusReport); contentValues.put(Sms.STATUS, status); contentValues.put(Inbox.DATE_SENT, System.currentTimeMillis()); @@ -107,10 +103,10 @@ public class MessageStatusService extends IntentService { } private void error(String message) { - Log.e(LOG_TAG, "[MessageStatusReceiver] " + message); + Timber.e("[MessageStatusReceiver] " + message); } private void log(String message) { - Log.d(LOG_TAG, "[MessageStatusReceiver] " + message); + Timber.d("[MessageStatusReceiver] " + message); } } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/MmsPushOutboxMessages.java b/android-smsmms/src/main/java/com/android/mms/transaction/MmsPushOutboxMessages.java index 993c87429a0f50c6aa5f5288796eb171e83d16b3..bee69171d921616689dfd70c953e71ae46aaa4c8 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/MmsPushOutboxMessages.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/MmsPushOutboxMessages.java @@ -18,8 +18,7 @@ package com.android.mms.transaction; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; /** * MmsPushOutboxMessages listens for MMS_SEND_OUTBOX_MSG intent . @@ -30,16 +29,13 @@ import com.klinker.android.logger.Log; */ public class MmsPushOutboxMessages extends BroadcastReceiver { private static final String INTENT_MMS_SEND_OUTBOX_MSG = "android.intent.action.MMS_SEND_OUTBOX_MSG"; - private static final String TAG = LogTag.TAG; @Override public void onReceive(Context context, Intent intent) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Received the MMS_SEND_OUTBOX_MSG intent: " + intent); - } + Timber.v("Received the MMS_SEND_OUTBOX_MSG intent: " + intent); String action = intent.getAction(); if(action.equalsIgnoreCase(INTENT_MMS_SEND_OUTBOX_MSG)){ - Log.d(TAG,"Now waking up the MMS service"); + Timber.d("Now waking up the MMS service"); context.startService(new Intent(context, TransactionService.class)); } } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/MmsSystemEventReceiver.java b/android-smsmms/src/main/java/com/android/mms/transaction/MmsSystemEventReceiver.java index 73d8035d64353b5e98db620b1c54d5e1a62b9c9d..b0e125fd728b1e76e66540fec53c5364dd6dcd37 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/MmsSystemEventReceiver.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/MmsSystemEventReceiver.java @@ -23,9 +23,8 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.provider.Telephony.Mms; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.Utils; +import timber.log.Timber; /** * MmsSystemEventReceiver receives the @@ -37,7 +36,6 @@ import com.klinker.android.send_message.Utils; * */ public class MmsSystemEventReceiver extends BroadcastReceiver { - private static final String TAG = LogTag.TAG; private static ConnectivityManager mConnMgr = null; public static void wakeUpService(Context context) { @@ -45,12 +43,10 @@ public class MmsSystemEventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Intent received: " + intent); - } + Timber.v("Intent received: " + intent); if (!Utils.isDefaultSmsApp(context)) { - Log.v(TAG, "not default sms app, cancelling"); + Timber.v("not default sms app, cancelling"); return; } @@ -66,28 +62,24 @@ public class MmsSystemEventReceiver extends BroadcastReceiver { if (Utils.isMmsOverWifiEnabled(context)) { NetworkInfo niWF = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if ((niWF != null) && (niWF.isConnected())) { - Log.v(TAG, "TYPE_WIFI connected"); + Timber.v("TYPE_WIFI connected"); wakeUpService(context); } } else { boolean mobileDataEnabled = Utils.isMobileDataEnabled(context); if (!mobileDataEnabled) { - Log.v(TAG, "mobile data turned off, bailing"); + Timber.v("mobile data turned off, bailing"); //Utils.setMobileDataEnabled(context, true); return; } - NetworkInfo mmsNetworkInfo = mConnMgr - .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); + NetworkInfo mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); if (mmsNetworkInfo == null) { return; } boolean available = mmsNetworkInfo.isAvailable(); boolean isConnected = mmsNetworkInfo.isConnected(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "TYPE_MOBILE_MMS available = " + available + - ", isConnected = " + isConnected); - } + Timber.v("TYPE_MOBILE_MMS available = " + available + ", isConnected = " + isConnected); // Wake up transact service when MMS data is available and isn't connected. if (available && !isConnected) { diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java index 2e89cab371e43a75bed3a6a7e3c2e5a0cb645b25..4d9c2686bf275d342835cb78f7fc1a91a0438ec0 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/NotificationTransaction.java @@ -29,7 +29,6 @@ import android.provider.Telephony.Mms.Inbox; import android.provider.Telephony.Threads; import android.telephony.TelephonyManager; import com.android.mms.MmsConfig; -import com.android.mms.logs.LogTag; import com.android.mms.util.DownloadManager; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.GenericPdu; @@ -39,18 +38,13 @@ import com.google.android.mms.pdu_alt.PduComposer; import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.BroadcastUtils; +import timber.log.Timber; import java.io.IOException; -import static com.android.mms.transaction.TransactionState.FAILED; -import static com.android.mms.transaction.TransactionState.INITIALIZED; -import static com.android.mms.transaction.TransactionState.SUCCESS; -import static com.google.android.mms.pdu_alt.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF; -import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_DEFERRED; -import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_RETRIEVED; -import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_UNRECOGNIZED; +import static com.android.mms.transaction.TransactionState.*; +import static com.google.android.mms.pdu_alt.PduHeaders.*; /** * The NotificationTransaction is responsible for handling multimedia @@ -69,8 +63,6 @@ import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_UNRECOGNIZED; * in case the client is in immediate retrieve mode. */ public class NotificationTransaction extends Transaction implements Runnable { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private Uri mUri; @@ -88,7 +80,7 @@ public class NotificationTransaction extends Transaction implements Runnable { mNotificationInd = (NotificationInd) PduPersister.getPduPersister(context).load(mUri); } catch (MmsException e) { - Log.e(TAG, "Failed to load NotificationInd from: " + uriString, e); + Timber.e(e, "Failed to load NotificationInd from: " + uriString); throw new IllegalArgumentException(); } @@ -114,7 +106,7 @@ public class NotificationTransaction extends Transaction implements Runnable { ind, Inbox.CONTENT_URI, !allowAutoDownload(mContext), true, null); } catch (MmsException e) { - Log.e(TAG, "Failed to save NotificationInd in constructor.", e); + Timber.e(e, "Failed to save NotificationInd in constructor."); throw new IllegalArgumentException(); } @@ -146,7 +138,7 @@ public class NotificationTransaction extends Transaction implements Runnable { boolean autoDownload = allowAutoDownload(mContext); try { if (LOCAL_LOGV) { - Log.v(TAG, "Notification transaction launched: " + this); + Timber.v("Notification transaction launched: " + this); } // By default, we set status to STATUS_DEFERRED because we @@ -163,7 +155,7 @@ public class NotificationTransaction extends Transaction implements Runnable { downloadManager.markState(mUri, DownloadManager.STATE_DOWNLOADING); if (LOCAL_LOGV) { - Log.v(TAG, "Content-Location: " + mContentLocation); + Timber.v("Content-Location: " + mContentLocation); } byte[] retrieveConfData = null; @@ -178,7 +170,7 @@ public class NotificationTransaction extends Transaction implements Runnable { if (retrieveConfData != null) { GenericPdu pdu = new PduParser(retrieveConfData).parse(); if ((pdu == null) || (pdu.getMessageType() != MESSAGE_TYPE_RETRIEVE_CONF)) { - Log.e(TAG, "Invalid M-RETRIEVE.CONF PDU. " + + Timber.e("Invalid M-RETRIEVE.CONF PDU. " + (pdu != null ? "message type: " + pdu.getMessageType() : "null pdu")); mTransactionState.setState(FAILED); status = STATUS_UNRECOGNIZED; @@ -198,7 +190,7 @@ public class NotificationTransaction extends Transaction implements Runnable { // M-NotifyResp.ind from Inbox. SqliteWrapper.delete(mContext, mContext.getContentResolver(), mUri, null, null); - Log.v(TAG, "NotificationTransaction received new mms message: " + uri); + Timber.v("NotificationTransaction received new mms message: " + uri); // Delete obsolete threads SqliteWrapper.delete(mContext, mContext.getContentResolver(), Threads.OBSOLETE_THREADS_URI, null, null); @@ -214,7 +206,7 @@ public class NotificationTransaction extends Transaction implements Runnable { } if (LOCAL_LOGV) { - Log.v(TAG, "status=0x" + Integer.toHexString(status)); + Timber.v("status=0x" + Integer.toHexString(status)); } // Check the status and update the result state of this Transaction. @@ -232,7 +224,7 @@ public class NotificationTransaction extends Transaction implements Runnable { sendNotifyRespInd(status); } catch (Throwable t) { - Log.e(TAG, "error", t); + Timber.e(t, "error"); } finally { mTransactionState.setContentUri(mUri); if (!autoDownload) { @@ -242,7 +234,7 @@ public class NotificationTransaction extends Transaction implements Runnable { } if (mTransactionState.getState() != SUCCESS) { mTransactionState.setState(FAILED); - Log.e(TAG, "NotificationTransaction failed."); + Timber.e("NotificationTransaction failed."); } notifyObservers(); } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java b/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java index 335ad195c88e68d96d0bc2a48a25083543846f9c..f8edc5aa781d22f54c22e5901824ab23b9036f96 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/PushReceiver.java @@ -29,7 +29,6 @@ import android.os.AsyncTask; import android.provider.Telephony.Mms; import android.provider.Telephony.Mms.Inbox; import com.android.mms.MmsConfig; -import com.android.mms.logs.LogTag; import com.google.android.mms.ContentType; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.DeliveryInd; @@ -39,7 +38,7 @@ import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.ReadOrigInd; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.HashSet; import java.util.Set; @@ -48,16 +47,13 @@ import java.util.concurrent.Executors; import static android.provider.Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION; import static android.provider.Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION; -import static com.google.android.mms.pdu_alt.PduHeaders.MESSAGE_TYPE_DELIVERY_IND; -import static com.google.android.mms.pdu_alt.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; -import static com.google.android.mms.pdu_alt.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND; +import static com.google.android.mms.pdu_alt.PduHeaders.*; /** * Receives Intent.WAP_PUSH_RECEIVED_ACTION intents and starts the * TransactionService by passing the push-data to it. */ public class PushReceiver extends BroadcastReceiver { - private static final String TAG = LogTag.TAG; static final String[] PROJECTION = new String[]{Mms.CONTENT_LOCATION, Mms.LOCKED}; @@ -77,7 +73,7 @@ public class PushReceiver extends BroadcastReceiver { @Override protected Void doInBackground(Intent... intents) { - Log.v(TAG, "receiving a new mms message"); + Timber.v("receiving a new mms message"); Intent intent = intents[0]; // Get raw PDU push-data from the message and parse it @@ -86,7 +82,7 @@ public class PushReceiver extends BroadcastReceiver { GenericPdu pdu = parser.parse(); if (pdu == null) { - Log.e(TAG, "Invalid PUSH data"); + Timber.e("Invalid PUSH data"); return null; } @@ -135,37 +131,32 @@ public class PushReceiver extends BroadcastReceiver { // Save the pdu. If we can start downloading the real pdu immediately, // don't allow persist() to create a thread for the notificationInd // because it causes UI jank. - Uri uri = p.persist(pdu, Inbox.CONTENT_URI, - !NotificationTransaction.allowAutoDownload(mContext), - true, - null); + Uri uri = p.persist(pdu, Inbox.CONTENT_URI, true, true, null); String location = getContentLocation(mContext, uri); if (downloadedUrls.contains(location)) { - Log.v(TAG, "already added this download, don't download again"); + Timber.v("already added this download, don't download again"); return null; } else { downloadedUrls.add(location); } - Log.v(TAG, "receiving on a lollipop+ device"); - DownloadManager.getInstance().downloadMultimediaMessage(mContext, location, uri, true); } else { - Log.v(TAG, "Skip downloading duplicate message: " + new String(nInd.getContentLocation())); + Timber.v("Skip downloading duplicate message: " + new String(nInd.getContentLocation())); } break; } default: - Log.e(TAG, "Received unrecognized PDU."); + Timber.e("Received unrecognized PDU."); } } catch (MmsException e) { - Log.e(TAG, "Failed to save the data from PUSH: type=" + type, e); + Timber.e(e, "Failed to save the data from PUSH: type=" + type); } catch (RuntimeException e) { - Log.e(TAG, "Unexpected RuntimeException.", e); + Timber.e(e, "Unexpected RuntimeException."); } - Log.v(TAG, "PUSH Intent processed."); + Timber.v("PUSH Intent processed."); return null; } @@ -178,15 +169,15 @@ public class PushReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.v(TAG, intent.getAction() + " " + intent.getType()); + Timber.v(intent.getAction() + " " + intent.getType()); if ((intent.getAction().equals(WAP_PUSH_DELIVER_ACTION) || intent.getAction().equals(WAP_PUSH_RECEIVED_ACTION)) && ContentType.MMS_MESSAGE.equals(intent.getType())) { - Log.v(TAG, "Received PUSH Intent: " + intent); + Timber.v("Received PUSH Intent: " + intent); MmsConfig.init(context); new ReceivePushTask(context, goAsync()).executeOnExecutor(PUSH_RECEIVER_EXECUTOR, intent); - Log.v("mms_receiver", context.getPackageName() + " received and aborted"); + Timber.v(context.getPackageName() + " received and aborted"); } } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/ReadRecTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/ReadRecTransaction.java index b47136b2a5b94281fae1e3bda340f9e40d0d7295..6226c9f91a6dd5abfbb2196cc204b53d7b7593b0 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/ReadRecTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/ReadRecTransaction.java @@ -19,14 +19,13 @@ package com.android.mms.transaction; import android.content.Context; import android.net.Uri; import android.provider.Telephony.Mms.Sent; -import com.android.mms.logs.LogTag; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.EncodedStringValue; import com.google.android.mms.pdu_alt.PduComposer; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.ReadRecInd; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.Utils; +import timber.log.Timber; import java.io.IOException; @@ -42,8 +41,6 @@ import java.io.IOException; * */ public class ReadRecTransaction extends Transaction implements Runnable{ - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private Thread mThread; @@ -91,15 +88,15 @@ public class ReadRecTransaction extends Transaction implements Runnable{ mTransactionState.setContentUri(uri); } catch (IOException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Failed to send M-Read-Rec.Ind.", e); + Timber.v(e, "Failed to send M-Read-Rec.Ind."); } } catch (MmsException e) { if (LOCAL_LOGV) { - Log.v(TAG, "Failed to load message from Outbox.", e); + Timber.v(e, "Failed to load message from Outbox."); } } catch (RuntimeException e) { if (LOCAL_LOGV) { - Log.e(TAG, "Unexpected RuntimeException.", e); + Timber.e(e, "Unexpected RuntimeException."); } } finally { if (mTransactionState.getState() != TransactionState.SUCCESS) { diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java index f802644168ddfa38d4a49d247a1ac19987a64bf7..8abd542b2d576e1089a62d4f3c521fa670b4a6a7 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/RetrieveTransaction.java @@ -25,7 +25,6 @@ import android.provider.Telephony.Mms; import android.provider.Telephony.Mms.Inbox; import android.text.TextUtils; import com.android.mms.MmsConfig; -import com.android.mms.logs.LogTag; import com.android.mms.util.DownloadManager; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.AcknowledgeInd; @@ -35,8 +34,8 @@ import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.RetrieveConf; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.Utils; +import timber.log.Timber; import java.io.IOException; @@ -54,8 +53,6 @@ import java.io.IOException; * */ public class RetrieveTransaction extends Transaction implements Runnable { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private final Uri mUri; @@ -80,7 +77,7 @@ public class RetrieveTransaction extends Transaction implements Runnable { mUri = Uri.parse(uri); // The Uri of the M-Notification.ind mId = mContentLocation = getContentLocation(context, mUri); if (LOCAL_LOGV) { - Log.v(TAG, "X-Mms-Content-Location: " + mContentLocation); + Timber.v("X-Mms-Content-Location: " + mContentLocation); } } else { throw new IllegalArgumentException( @@ -183,7 +180,7 @@ public class RetrieveTransaction extends Transaction implements Runnable { // Don't mark the transaction as failed if we failed to send it. sendAcknowledgeInd(retrieveConf); } catch (Throwable t) { - Log.e(TAG, "error", t); + Timber.e(t, "error"); if ("HTTP error: Not Found".equals(t.getMessage())) { // Delete the expired M-Notification.ind. SqliteWrapper.delete(mContext, mContext.getContentResolver(), @@ -193,7 +190,7 @@ public class RetrieveTransaction extends Transaction implements Runnable { if (mTransactionState.getState() != TransactionState.SUCCESS) { mTransactionState.setState(TransactionState.FAILED); mTransactionState.setContentUri(mUri); - Log.e(TAG, "Retrieval failed."); + Timber.e("Retrieval failed."); } notifyObservers(); } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/RetryScheduler.java b/android-smsmms/src/main/java/com/android/mms/transaction/RetryScheduler.java index 27cd9716e500bcf4fb7b05b91b45e7fec3252d0b..455581810a80308a96fe164a5670448692d53dfb 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/RetryScheduler.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/RetryScheduler.java @@ -31,17 +31,14 @@ import android.net.Uri; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.MmsSms.PendingMessages; -import com.android.mms.logs.LogTag; import com.android.mms.util.DownloadManager; import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduPersister; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.R; +import timber.log.Timber; public class RetryScheduler implements Observer { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private final Context mContext; @@ -71,9 +68,7 @@ public class RetryScheduler implements Observer { try { Transaction t = (Transaction) observable; - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "[RetryScheduler] update " + observable); - } + Timber.v("[RetryScheduler] update " + observable); // We are only supposed to handle M-Notification.ind, M-Send.req // and M-ReadRec.ind. @@ -172,10 +167,8 @@ public class RetryScheduler implements Observer { if ((retryIndex < scheme.getRetryLimit()) && retry) { long retryAt = current + scheme.getWaitingInterval(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "scheduleRetry: retry for " + uri + " is scheduled at " - + (retryAt - System.currentTimeMillis()) + "ms from now"); - } + Timber.v("scheduleRetry: retry for " + uri + " is scheduled at " + + (retryAt - System.currentTimeMillis()) + "ms from now"); values.put(PendingMessages.DUE_TIME, retryAt); @@ -231,7 +224,7 @@ public class RetryScheduler implements Observer { PendingMessages.CONTENT_URI, values, PendingMessages._ID + "=" + id, null); } else if (LOCAL_LOGV) { - Log.v(TAG, "Cannot found correct pending status for: " + msgId); + Timber.v("Cannot found correct pending status for: " + msgId); } } finally { cursor.close(); @@ -279,7 +272,7 @@ public class RetryScheduler implements Observer { cursor.close(); } if (respStatus != 0) { - Log.e(TAG, "Response status is: " + respStatus); + Timber.e("Response status is: " + respStatus); } return respStatus; } @@ -298,9 +291,7 @@ public class RetryScheduler implements Observer { cursor.close(); } if (retrieveStatus != 0) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Retrieve status is: " + retrieveStatus); - } + Timber.v("Retrieve status is: " + retrieveStatus); } return retrieveStatus; } @@ -323,10 +314,7 @@ public class RetryScheduler implements Observer { Context.ALARM_SERVICE); am.set(AlarmManager.RTC, retryAt, operation); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Next retry is scheduled at" - + (retryAt - System.currentTimeMillis()) + "ms from now"); - } + Timber.v("Next retry is scheduled at" + (retryAt - System.currentTimeMillis()) + "ms from now"); } } finally { cursor.close(); diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/SendTransaction.java b/android-smsmms/src/main/java/com/android/mms/transaction/SendTransaction.java index 67cb74c8e45269464a4f9ab3ca670bc1f934f67a..851334a2bc9b601604aa748252d9c9a9d9ae18fd 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/SendTransaction.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/SendTransaction.java @@ -25,7 +25,6 @@ import android.net.Uri; import android.provider.Telephony.Mms; import android.provider.Telephony.Mms.Sent; import android.text.TextUtils; -import com.android.mms.logs.LogTag; import com.android.mms.util.RateController; import com.android.mms.util.SendingProgressTokenManager; import com.google.android.mms.pdu_alt.EncodedStringValue; @@ -35,9 +34,9 @@ import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.SendConf; import com.google.android.mms.pdu_alt.SendReq; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.Utils; +import timber.log.Timber; import java.util.Arrays; @@ -55,8 +54,6 @@ import java.util.Arrays; * */ public class SendTransaction extends Transaction implements Runnable { - private static final String TAG = LogTag.TAG; - private Thread mThread; public final Uri mSendReqURI; @@ -86,7 +83,7 @@ public class SendTransaction extends Transaction implements Runnable { RateController.init(mContext); RateController rateCtlr = RateController.getInstance(); if (rateCtlr.isLimitSurpassed() && !rateCtlr.isAllowedByUser()) { - Log.e(TAG, "Sending rate limit surpassed."); + Timber.e("Sending rate limit surpassed."); return; } @@ -116,15 +113,13 @@ public class SendTransaction extends Transaction implements Runnable { new PduComposer(mContext, sendReq).make()); SendingProgressTokenManager.remove(tokenKey); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - String respStr = new String(response); - builder.append("[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr); - Log.d(TAG, "[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr); - } + String respStr = new String(response); + builder.append("[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr); + Timber.d("[SendTransaction] run: send mms msg (" + mId + "), resp=" + respStr); SendConf conf = (SendConf) new PduParser(response).parse(); if (conf == null) { - Log.e(TAG, "No M-Send.conf received."); + Timber.e("No M-Send.conf received."); builder.append("No M-Send.conf received.\n"); } @@ -133,7 +128,7 @@ public class SendTransaction extends Transaction implements Runnable { byte[] reqId = sendReq.getTransactionId(); byte[] confId = conf.getTransactionId(); if (!Arrays.equals(reqId, confId)) { - Log.e(TAG, "Inconsistent Transaction-ID: req=" + Timber.e("Inconsistent Transaction-ID: req=" + new String(reqId) + ", conf=" + new String(confId)); builder.append("Inconsistent Transaction-ID: req=" + new String(reqId) + ", conf=" + new String(confId) + "\n"); @@ -150,7 +145,7 @@ public class SendTransaction extends Transaction implements Runnable { if (respStatus != PduHeaders.RESPONSE_STATUS_OK) { SqliteWrapper.update(mContext, mContext.getContentResolver(), mSendReqURI, values, null, null); - Log.e(TAG, "Server returned an error code: " + respStatus); + Timber.e("Server returned an error code: " + respStatus); builder.append("Server returned an error code: " + respStatus + "\n"); return; } @@ -166,12 +161,12 @@ public class SendTransaction extends Transaction implements Runnable { mTransactionState.setState(TransactionState.SUCCESS); mTransactionState.setContentUri(uri); } catch (Throwable t) { - Log.e(TAG, "error", t); + Timber.e(t, "error"); } finally { if (mTransactionState.getState() != TransactionState.SUCCESS) { mTransactionState.setState(TransactionState.FAILED); mTransactionState.setContentUri(mSendReqURI); - Log.e(TAG, "Delivery failed."); + Timber.e("Delivery failed."); builder.append("Delivery failed\n"); Intent intent = new Intent(com.klinker.android.send_message.Transaction.MMS_ERROR); diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/TransactionService.java b/android-smsmms/src/main/java/com/android/mms/transaction/TransactionService.java index 8b9f05303b6e187ce6e7408121ce25eee5e090dd..6935d1aeb56745774011bd4a5703f6c30cc41da7 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/TransactionService.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/TransactionService.java @@ -40,7 +40,6 @@ import android.provider.Telephony.MmsSms; import android.provider.Telephony.MmsSms.PendingMessages; import android.text.TextUtils; import android.widget.Toast; -import com.android.mms.logs.LogTag; import com.android.mms.util.RateController; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.GenericPdu; @@ -48,10 +47,10 @@ import com.google.android.mms.pdu_alt.NotificationInd; import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.BroadcastUtils; import com.klinker.android.send_message.R; import com.klinker.android.send_message.Utils; +import timber.log.Timber; import java.io.IOException; import java.util.ArrayList; @@ -84,7 +83,6 @@ import java.util.ArrayList; * */ public class TransactionService extends Service implements Observer { - private static final String TAG = LogTag.TAG; /** * Used to identify notification intents broadcasted by the @@ -169,12 +167,10 @@ public class TransactionService extends Service implements Observer { @Override public void onCreate() { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Creating TransactionService"); - } + Timber.v("Creating TransactionService"); if (!Utils.isDefaultSmsApp(this)) { - Log.v(TAG, "not default app, so exiting"); + Timber.v("not default app, so exiting"); stopSelf(); return; } @@ -206,19 +202,19 @@ public class TransactionService extends Service implements Observer { // new Thread(new Runnable() { // @Override // public void run() { -// Log.v(TAG, "starting receiving with new lollipop method"); +// Timber.v("starting receiving with new lollipop method"); // try { Thread.sleep(60000); } catch (Exception e) { } -// Log.v(TAG, "done sleeping, lets try and grab the message"); +// Timber.v("done sleeping, lets try and grab the message"); // Uri contentUri = Uri.parse(intent.getStringExtra(TransactionBundle.URI)); // String downloadLocation = null; // Cursor locationQuery = getContentResolver().query(contentUri, new String[]{Telephony.Mms.CONTENT_LOCATION, Telephony.Mms._ID}, null, null, "date desc"); // // if (locationQuery != null && locationQuery.moveToFirst()) { -// Log.v(TAG, "grabbing content location url"); +// Timber.v("grabbing content location url"); // downloadLocation = locationQuery.getString(locationQuery.getColumnIndex(Telephony.Mms.CONTENT_LOCATION)); // } // -// Log.v(TAG, "creating request with url: " + downloadLocation); +// Timber.v("creating request with url: " + downloadLocation); // DownloadRequest request = new DownloadRequest(downloadLocation, contentUri, null, null, null); // MmsNetworkManager manager = new MmsNetworkManager(TransactionService.this); // request.execute(TransactionService.this, manager); @@ -270,11 +266,8 @@ public class TransactionService extends Service implements Observer { boolean noNetwork = !isNetworkAvailable(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() + - " intent=" + intent); - Log.v(TAG, " networkAvailable=" + !noNetwork); - } + Timber.v("onNewIntent: serviceId: " + serviceId + ": " + intent.getExtras() + " intent=" + intent); + Timber.v(" networkAvailable=" + !noNetwork); String action = intent.getAction(); if (ACTION_ONALARM.equals(action) || ACTION_ENABLE_AUTO_RETRIEVE.equals(action) || @@ -286,14 +279,10 @@ public class TransactionService extends Service implements Observer { try { int count = cursor.getCount(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNewIntent: cursor.count=" + count + " action=" + action); - } + Timber.v("onNewIntent: cursor.count=" + count + " action=" + action); if (count == 0) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNewIntent: no pending messages. Stopping service."); - } + Timber.v("onNewIntent: no pending messages. Stopping service."); RetryScheduler.setRetryAlarm(this); stopSelfIfIdle(serviceId); return; @@ -323,16 +312,12 @@ public class TransactionService extends Service implements Observer { cursor.close(); } } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNewIntent: no pending messages. Stopping service."); - } + Timber.v("onNewIntent: no pending messages. Stopping service."); RetryScheduler.setRetryAlarm(this); stopSelfIfIdle(serviceId); } } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNewIntent: launch transaction..."); - } + Timber.v("onNewIntent: launch transaction..."); // For launching NotificationTransaction and test purpose. TransactionBundle args = new TransactionBundle(intent.getExtras()); launchTransaction(serviceId, args, noNetwork); @@ -342,9 +327,7 @@ public class TransactionService extends Service implements Observer { private void stopSelfIfIdle(int startId) { synchronized (mProcessing) { if (mProcessing.isEmpty() && mPending.isEmpty()) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "stopSelfIfIdle: STOP!"); - } + Timber.v("stopSelfIfIdle: STOP!"); stopSelf(startId); } @@ -364,14 +347,14 @@ public class TransactionService extends Service implements Observer { case PduHeaders.MESSAGE_TYPE_SEND_REQ: return Transaction.SEND_TRANSACTION; default: - Log.w(TAG, "Unrecognized MESSAGE_TYPE: " + msgType); + Timber.w("Unrecognized MESSAGE_TYPE: " + msgType); return -1; } } private void launchTransaction(int serviceId, TransactionBundle txnBundle, boolean noNetwork) { if (noNetwork) { - Log.w(TAG, "launchTransaction: no network error!"); + Timber.w("launchTransaction: no network error!"); onNetworkUnavailable(serviceId, txnBundle.getTransactionType()); return; } @@ -379,16 +362,12 @@ public class TransactionService extends Service implements Observer { msg.arg1 = serviceId; msg.obj = txnBundle; - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "launchTransaction: sending message " + msg); - } + Timber.v("launchTransaction: sending message " + msg); mServiceHandler.sendMessage(msg); } private void onNetworkUnavailable(int serviceId, int transactionType) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "onNetworkUnavailable: sid=" + serviceId + ", type=" + transactionType); - } + Timber.v("onNetworkUnavailable: sid=" + serviceId + ", type=" + transactionType); int toastType = TOAST_NONE; if (transactionType == Transaction.RETRIEVE_TRANSACTION) { @@ -404,11 +383,9 @@ public class TransactionService extends Service implements Observer { @Override public void onDestroy() { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Destroying TransactionService"); - } + Timber.v("Destroying TransactionService"); if (!mPending.isEmpty()) { - Log.w(TAG, "TransactionService exiting with transaction still pending"); + Timber.w("TransactionService exiting with transaction still pending"); } releaseWakeLock(); @@ -421,7 +398,7 @@ public class TransactionService extends Service implements Observer { mServiceHandler.sendEmptyMessage(EVENT_QUIT); if (!mobileDataEnabled && !lollipopReceiving) { - Log.v(TAG, "disabling mobile data"); + Timber.v("disabling mobile data"); Utils.setMobileDataEnabled(TransactionService.this, false); } } @@ -438,31 +415,23 @@ public class TransactionService extends Service implements Observer { Transaction transaction = (Transaction) observable; int serviceId = transaction.getServiceId(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "update transaction " + serviceId); - } + Timber.v("update transaction " + serviceId); try { synchronized (mProcessing) { mProcessing.remove(transaction); if (mPending.size() > 0) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "update: handle next pending transaction..."); - } + Timber.v("update: handle next pending transaction..."); Message msg = mServiceHandler.obtainMessage( EVENT_HANDLE_NEXT_PENDING_TRANSACTION, transaction.getConnectionSettings()); mServiceHandler.sendMessage(msg); } else if (mProcessing.isEmpty()) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "update: endMmsConnectivity"); - } + Timber.v("update: endMmsConnectivity"); endMmsConnectivity(); } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "update: mProcessing is not empty"); - } + Timber.v("update: mProcessing is not empty"); } } @@ -473,9 +442,7 @@ public class TransactionService extends Service implements Observer { switch (result) { case TransactionState.SUCCESS: - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Transaction complete: " + serviceId); - } + Timber.v("Transaction complete: " + serviceId); intent.putExtra(STATE_URI, state.getContentUri()); @@ -499,21 +466,14 @@ public class TransactionService extends Service implements Observer { } break; case TransactionState.FAILED: - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Transaction failed: " + serviceId); - } + Timber.v("Transaction failed: " + serviceId); break; default: - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Transaction state unknown: " + - serviceId + " " + result); - } + Timber.v("Transaction state unknown: " + serviceId + " " + result); break; } - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "update: broadcast transaction result " + result); - } + Timber.v("update: broadcast transaction result " + result); // Broadcast the result of the transaction. BroadcastUtils.sendExplicitBroadcast(this, intent, TRANSACTION_COMPLETED_ACTION); } finally { @@ -534,39 +494,34 @@ public class TransactionService extends Service implements Observer { private void acquireWakeLock() { // It's okay to double-acquire this because we are not using it // in reference-counted mode. - Log.v(TAG, "mms acquireWakeLock"); + Timber.v("mms acquireWakeLock"); mWakeLock.acquire(); } private void releaseWakeLock() { // Don't release the wake lock if it hasn't been created and acquired. if (mWakeLock != null && mWakeLock.isHeld()) { - Log.v(TAG, "mms releaseWakeLock"); + Timber.v("mms releaseWakeLock"); mWakeLock.release(); } } protected int beginMmsConnectivity() throws IOException { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "beginMmsConnectivity"); - } + Timber.v("beginMmsConnectivity"); // Take a wake lock so we don't fall asleep before the message is downloaded. createWakeLock(); if (Utils.isMmsOverWifiEnabled(this)) { NetworkInfo niWF = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if ((niWF != null) && (niWF.isConnected())) { - Log.v(TAG, "beginMmsConnectivity: Wifi active"); + Timber.v("beginMmsConnectivity: Wifi active"); return 0; } } - int result = mConnMgr.startUsingNetworkFeature( - ConnectivityManager.TYPE_MOBILE, "enableMMS"); + int result = mConnMgr.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableMMS"); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "beginMmsConnectivity: result=" + result); - } + Timber.v("beginMmsConnectivity: result=" + result); switch (result) { case 0: @@ -580,9 +535,7 @@ public class TransactionService extends Service implements Observer { protected void endMmsConnectivity() { try { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "endMmsConnectivity"); - } + Timber.v("endMmsConnectivity"); // cancel timer for renewal of lease mServiceHandler.removeMessages(EVENT_CONTINUE_MMS_CONNECTIVITY); @@ -636,9 +589,7 @@ public class TransactionService extends Service implements Observer { */ @Override public void handleMessage(Message msg) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Handling incoming message: " + msg + " = " + decodeMessage(msg)); - } + Timber.v("Handling incoming message: " + msg + " = " + decodeMessage(msg)); Transaction transaction = null; @@ -658,21 +609,19 @@ public class TransactionService extends Service implements Observer { } } - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "handle EVENT_CONTINUE_MMS_CONNECTIVITY event..."); - } + Timber.v("handle EVENT_CONTINUE_MMS_CONNECTIVITY event..."); try { int result = beginMmsConnectivity(); if (result != 0) { - Log.v(TAG, "Extending MMS connectivity returned " + result + + Timber.v("Extending MMS connectivity returned " + result + " instead of APN_ALREADY_ACTIVE"); // Just wait for connectivity startup without // any new request of APN switch. return; } } catch (IOException e) { - Log.w(TAG, "Attempt to extend use of MMS connectivity failed"); + Timber.w("Attempt to extend use of MMS connectivity failed"); return; } @@ -686,10 +635,8 @@ public class TransactionService extends Service implements Observer { TransactionBundle args = (TransactionBundle) msg.obj; TransactionSettings transactionSettings; - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "EVENT_TRANSACTION_REQUEST MmscUrl=" + - args.getMmscUrl() + " proxy port: " + args.getProxyAddress()); - } + Timber.v("EVENT_TRANSACTION_REQUEST MmscUrl=" + + args.getMmscUrl() + " proxy port: " + args.getProxyAddress()); // Set the connection settings for this transaction. // If these have not been set in args, load the default settings. @@ -704,10 +651,8 @@ public class TransactionService extends Service implements Observer { int transactionType = args.getTransactionType(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "handle EVENT_TRANSACTION_REQUEST: transactionType=" + - transactionType + " " + decodeTransactionType(transactionType)); - } + Timber.v("handle EVENT_TRANSACTION_REQUEST: transactionType=" + + transactionType + " " + decodeTransactionType(transactionType)); // Create appropriate transaction switch (transactionType) { @@ -729,7 +674,7 @@ public class TransactionService extends Service implements Observer { TransactionService.this, serviceId, transactionSettings, (NotificationInd) ind); } else { - Log.e(TAG, "Invalid PUSH data."); + Timber.e("Invalid PUSH data."); transaction = null; return; } @@ -754,11 +699,9 @@ public class TransactionService extends Service implements Observer { return; } - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Started processing of incoming message: " + msg); - } + Timber.v("Started processing of incoming message: " + msg); } catch (Exception ex) { - Log.w(TAG, "Exception occurred while handling message: " + msg, ex); + Timber.w(ex, "Exception occurred while handling message: " + msg); if (transaction != null) { try { @@ -769,7 +712,7 @@ public class TransactionService extends Service implements Observer { } } } catch (Throwable t) { - Log.e(TAG, "Unexpected Throwable.", t); + Timber.e(t, "Unexpected Throwable."); } finally { // Set transaction to null to allow stopping the // transaction service. @@ -778,9 +721,7 @@ public class TransactionService extends Service implements Observer { } } finally { if (transaction == null) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Transaction was null. Stopping self: " + serviceId); - } + Timber.v("Transaction was null. Stopping self: " + serviceId); endMmsConnectivity(); stopSelf(serviceId); } @@ -790,7 +731,7 @@ public class TransactionService extends Service implements Observer { processPendingTransaction(transaction, (TransactionSettings) msg.obj); return; default: - Log.w(TAG, "what=" + msg.what); + Timber.w("what=" + msg.what); return; } } @@ -819,9 +760,7 @@ public class TransactionService extends Service implements Observer { public void processPendingTransaction(Transaction transaction, TransactionSettings settings) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processPendingTxn: transaction=" + transaction); - } + Timber.v("processPendingTxn: transaction=" + transaction); int numProcessTransaction = 0; synchronized (mProcessing) { @@ -842,27 +781,20 @@ public class TransactionService extends Service implements Observer { try { int serviceId = transaction.getServiceId(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processPendingTxn: process " + serviceId); - } + Timber.v("processPendingTxn: process " + serviceId); if (processTransaction(transaction)) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Started deferred processing of transaction " - + transaction); - } + Timber.v("Started deferred processing of transaction " + transaction); } else { transaction = null; stopSelf(serviceId); } } catch (IOException e) { - Log.w(TAG, e.getMessage(), e); + Timber.w(e, e.getMessage()); } } else { if (numProcessTransaction == 0) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processPendingTxn: no more transaction, endMmsConnectivity"); - } + Timber.v("processPendingTxn: no more transaction, endMmsConnectivity"); endMmsConnectivity(); } } @@ -881,18 +813,13 @@ public class TransactionService extends Service implements Observer { synchronized (mProcessing) { for (Transaction t : mPending) { if (t.isEquivalent(transaction)) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Transaction already pending: " + - transaction.getServiceId()); - } + Timber.v("Transaction already pending: " + transaction.getServiceId()); return true; } } for (Transaction t : mProcessing) { if (t.isEquivalent(transaction)) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Duplicated transaction: " + transaction.getServiceId()); - } + Timber.v("Duplicated transaction: " + transaction.getServiceId()); return true; } } @@ -903,16 +830,12 @@ public class TransactionService extends Service implements Observer { * to defer processing the transaction until * connectivity is established. */ - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processTransaction: call beginMmsConnectivity..."); - } + Timber.v("processTransaction: call beginMmsConnectivity..."); int connectivityResult = beginMmsConnectivity(); if (connectivityResult == 1) { mPending.add(transaction); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processTransaction: connResult=APN_REQUEST_STARTED, " + - "defer transaction pending MMS connectivity"); - } + Timber.v("processTransaction: connResult=APN_REQUEST_STARTED, " + + "defer transaction pending MMS connectivity"); return true; } // If there is already a transaction in processing list, because of the previous @@ -921,26 +844,19 @@ public class TransactionService extends Service implements Observer { // to the Processing list. But Processing list is never traversed to // resend, resulting in transaction not completed/sent. if (mProcessing.size() > 0) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Adding transaction to 'mPending' list: " + transaction); - } + Timber.v("Adding transaction to 'mPending' list: " + transaction); mPending.add(transaction); return true; } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Adding transaction to 'mProcessing' list: " + transaction); - } + Timber.v("Adding transaction to 'mProcessing' list: " + transaction); mProcessing.add(transaction); } } // Set a timer to keep renewing our "lease" on the MMS connection - sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), - APN_EXTENSION_WAIT); + sendMessageDelayed(obtainMessage(EVENT_CONTINUE_MMS_CONNECTIVITY), APN_EXTENSION_WAIT); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "processTransaction: starting transaction " + transaction); - } + Timber.v("processTransaction: starting transaction " + transaction); // Attach to transaction and process it transaction.attach(TransactionService.this); @@ -960,9 +876,7 @@ public class TransactionService extends Service implements Observer { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.w(TAG, "ConnectivityBroadcastReceiver.onReceive() action: " + action); - } + Timber.w("ConnectivityBroadcastReceiver.onReceive() action: " + action); if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { return; @@ -973,9 +887,7 @@ public class TransactionService extends Service implements Observer { if (mConnMgr != null && Utils.isMobileDataEnabled(context)) { mmsNetworkInfo = mConnMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_MMS); } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "mConnMgr is null, bail"); - } + Timber.v("mConnMgr is null, bail"); } /* @@ -984,24 +896,17 @@ public class TransactionService extends Service implements Observer { * transaction, if any. */ - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo); - } + Timber.v("Handle ConnectivityBroadcastReceiver.onReceive(): " + mmsNetworkInfo); // Check availability of the mobile network. if (mmsNetworkInfo == null) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "mms type is null or mobile data is turned off, bail"); - } + Timber.v("mms type is null or mobile data is turned off, bail"); } else { // This is a very specific fix to handle the case where the phone receives an // incoming call during the time we're trying to setup the mms connection. // When the call ends, restart the process of mms connectivity. if ("2GVoiceCallEnded".equals(mmsNetworkInfo.getReason())) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, " reason is " + "2GVoiceCallEnded" + - ", retrying mms connectivity"); - } + Timber.v(" reason is " + "2GVoiceCallEnded" + ", retrying mms connectivity"); renewMmsConnectivity(); return; } @@ -1011,7 +916,7 @@ public class TransactionService extends Service implements Observer { TransactionService.this, mmsNetworkInfo.getExtraInfo()); // If this APN doesn't have an MMSC, mark everything as failed and bail. if (TextUtils.isEmpty(settings.getMmscUrl())) { - Log.v(TAG, " empty MMSC url, bail"); + Timber.v(" empty MMSC url, bail"); BroadcastUtils.sendExplicitBroadcast( TransactionService.this, new Intent(), @@ -1023,15 +928,11 @@ public class TransactionService extends Service implements Observer { } mServiceHandler.processPendingTransaction(null, settings); } else { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, " TYPE_MOBILE_MMS not connected, bail"); - } + Timber.v(" TYPE_MOBILE_MMS not connected, bail"); // Retry mms connectivity once it's possible to connect if (mmsNetworkInfo.isAvailable()) { - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, " retrying mms connectivity for it's available"); - } + Timber.v(" retrying mms connectivity for it's available"); renewMmsConnectivity(); } } diff --git a/android-smsmms/src/main/java/com/android/mms/transaction/TransactionSettings.java b/android-smsmms/src/main/java/com/android/mms/transaction/TransactionSettings.java index 7deb73069aa887e84e9660928aa4cbbb045c297a..242fb417d8db54fd9965fccc3311e25ee8e595cc 100755 --- a/android-smsmms/src/main/java/com/android/mms/transaction/TransactionSettings.java +++ b/android-smsmms/src/main/java/com/android/mms/transaction/TransactionSettings.java @@ -21,10 +21,9 @@ import android.net.NetworkUtilsHelper; import android.provider.Telephony; import android.text.TextUtils; import com.android.mms.MmsConfig; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.Transaction; import com.klinker.android.send_message.Utils; +import timber.log.Timber; /** * Container of transaction settings. Instances of this class are contained @@ -32,10 +31,6 @@ import com.klinker.android.send_message.Utils; * settings or of the MMS Client. */ public class TransactionSettings { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = true; - private static final boolean LOCAL_LOGV = false; - private String mServiceCenter; private String mProxyAddress; private int mProxyPort = -1; @@ -57,7 +52,7 @@ public class TransactionSettings { * @param context The context of the MMS Client */ public TransactionSettings(Context context, String apnName) { - Log.v(TAG, "TransactionSettings: apnName: " + apnName); + Timber.v("TransactionSettings: apnName: " + apnName); // String selection = "current" + " IS NOT NULL"; // String[] selectionArgs = null; // if (!TextUtils.isEmpty(apnName)) { @@ -72,15 +67,15 @@ public class TransactionSettings { // Telephony.Carriers.CONTENT_URI, // APN_PROJECTION, selection, selectionArgs, null); // -// Log.v(TAG, "TransactionSettings looking for apn: " + selection + " returned: " + +// Timber.v("TransactionSettings looking for apn: " + selection + " returned: " + // (cursor == null ? "null cursor" : (cursor.getCount() + " hits"))); // } catch (SecurityException e) { -// Log.e(TAG, "exception thrown", e); +// Timber.e(e, "exception thrown"); // cursor = null; // } // // if (cursor == null) { -// Log.e(TAG, "Apn is not found in Database!"); +// Timber.e("Apn is not found in Database!"); if (Transaction.Companion.getSettings() == null) { Transaction.Companion.setSettings(Utils.getDefaultSendSettings(context)); } @@ -92,26 +87,26 @@ public class TransactionSettings { String agent = Transaction.Companion.getSettings().getAgent(); if (agent != null && !agent.trim().equals("")) { MmsConfig.setUserAgent(agent); - Log.v(TAG, "set user agent"); + Timber.v("set user agent"); } String uaProfUrl = Transaction.Companion.getSettings().getUserProfileUrl(); if (uaProfUrl != null && !uaProfUrl.trim().equals("")) { MmsConfig.setUaProfUrl(uaProfUrl); - Log.v(TAG, "set user agent profile url"); + Timber.v("set user agent profile url"); } String uaProfTagName = Transaction.Companion.getSettings().getUaProfTagName(); if (uaProfTagName != null && !uaProfTagName.trim().equals("")) { MmsConfig.setUaProfTagName(uaProfTagName); - Log.v(TAG, "set user agent profile tag name"); + Timber.v("set user agent profile tag name"); } if (isProxySet()) { try { mProxyPort = Integer.parseInt(Transaction.Companion.getSettings().getPort()); } catch (NumberFormatException e) { - Log.e(TAG, "could not get proxy: " + Transaction.Companion.getSettings().getPort(), e); + Timber.e(e, "could not get proxy: " + Transaction.Companion.getSettings().getPort()); } } // } @@ -137,9 +132,9 @@ public class TransactionSettings { // mProxyPort = Integer.parseInt(portString); // } catch (NumberFormatException e) { // if (TextUtils.isEmpty(portString)) { -// Log.w(TAG, "mms port not set!"); +// Timber.w("mms port not set!"); // } else { -// Log.e(TAG, "Bad port number format: " + portString, e); +// Timber.e(e, "Bad port number format: " + portString); // } // } // } @@ -149,10 +144,10 @@ public class TransactionSettings { // cursor.close(); // } // -// Log.v(TAG, "APN setting: MMSC: " + mServiceCenter + " looked for: " + selection); +// Timber.v("APN setting: MMSC: " + mServiceCenter + " looked for: " + selection); // // if (sawValidApn && TextUtils.isEmpty(mServiceCenter)) { -// Log.e(TAG, "Invalid APN setting: MMSC is empty"); +// Timber.e("Invalid APN setting: MMSC is empty"); // } } @@ -170,11 +165,9 @@ public class TransactionSettings { mProxyAddress = proxyAddr; mProxyPort = proxyPort; - if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { - Log.v(TAG, "TransactionSettings: " + mServiceCenter + - " proxyAddress: " + mProxyAddress + - " proxyPort: " + mProxyPort); - } + Timber.v("TransactionSettings: " + mServiceCenter + + " proxyAddress: " + mProxyAddress + + " proxyPort: " + mProxyPort); } public String getMmscUrl() { diff --git a/android-smsmms/src/main/java/com/android/mms/util/DownloadManager.java b/android-smsmms/src/main/java/com/android/mms/util/DownloadManager.java index 7be39292ba6081dd39ff8a6c8decc951c4eefe9d..af793abff7d8fd5624c4912c6f762dab1e056147 100755 --- a/android-smsmms/src/main/java/com/android/mms/util/DownloadManager.java +++ b/android-smsmms/src/main/java/com/android/mms/util/DownloadManager.java @@ -28,18 +28,15 @@ import android.preference.PreferenceManager; import android.provider.Telephony.Mms; import android.widget.Toast; import com.android.internal.telephony.TelephonyProperties; -import com.android.mms.logs.LogTag; import com.android.mms.service_alt.SystemPropertiesProxy; import com.google.android.mms.MmsException; import com.google.android.mms.pdu_alt.EncodedStringValue; import com.google.android.mms.pdu_alt.NotificationInd; import com.google.android.mms.pdu_alt.PduPersister; -import com.klinker.android.logger.Log; import com.klinker.android.send_message.R; +import timber.log.Timber; public class DownloadManager { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; public static final int DEFERRED_MASK = 0x04; @@ -67,7 +64,7 @@ public class DownloadManager { mAutoDownload = getAutoDownloadState(context, mPreferences); if (LOCAL_LOGV) { - Log.v(TAG, "mAutoDownload ------> " + mAutoDownload); + Timber.v("mAutoDownload ------> " + mAutoDownload); } } @@ -77,11 +74,11 @@ public class DownloadManager { public static void init(Context context) { if (LOCAL_LOGV) { - Log.v(TAG, "DownloadManager.init()"); + Timber.v("DownloadManager.init()"); } if (sInstance != null) { - Log.w(TAG, "Already initialized."); + Timber.w("Already initialized."); } sInstance = new DownloadManager(context); } @@ -101,14 +98,14 @@ public class DownloadManager { boolean autoDownload = prefs.getBoolean("auto_download_mms", true); if (LOCAL_LOGV) { - Log.v(TAG, "auto download without roaming -> " + autoDownload); + Timber.v("auto download without roaming -> " + autoDownload); } if (autoDownload) { boolean alwaysAuto = true; if (LOCAL_LOGV) { - Log.v(TAG, "auto download during roaming -> " + alwaysAuto); + Timber.v("auto download during roaming -> " + alwaysAuto); } if (!roaming || alwaysAuto) { @@ -123,7 +120,7 @@ public class DownloadManager { String roaming = SystemPropertiesProxy.get(context, TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, null); if (LOCAL_LOGV) { - Log.v(TAG, "roaming ------> " + roaming); + Timber.v("roaming ------> " + roaming); } return "true".equals(roaming); } @@ -145,7 +142,7 @@ public class DownloadManager { return; } } catch(MmsException e) { - Log.e(TAG, e.getMessage(), e); + Timber.e(e, e.getMessage()); return; } @@ -157,7 +154,7 @@ public class DownloadManager { Toast.makeText(mContext, getMessage(uri), Toast.LENGTH_LONG).show(); } catch (MmsException e) { - Log.e(TAG, e.getMessage(), e); + Timber.e(e, e.getMessage()); } } }); @@ -180,7 +177,7 @@ public class DownloadManager { try { Toast.makeText(mContext, errStr, Toast.LENGTH_LONG).show(); } catch (Exception e) { - Log.e(TAG,"Caught an exception in showErrorCodeToast"); + Timber.e("Caught an exception in showErrorCodeToast"); } } }); diff --git a/android-smsmms/src/main/java/com/android/mms/util/ExternalLogger.java b/android-smsmms/src/main/java/com/android/mms/util/ExternalLogger.java deleted file mode 100755 index e57e41592b5a26c973c0986fcea8d345876e8ed9..0000000000000000000000000000000000000000 --- a/android-smsmms/src/main/java/com/android/mms/util/ExternalLogger.java +++ /dev/null @@ -1,36 +0,0 @@ - -package com.android.mms.util; - - -import java.util.concurrent.CopyOnWriteArrayList; - -public class ExternalLogger { - private static final CopyOnWriteArrayList sListener = new CopyOnWriteArrayList(); - - public interface LoggingListener { - void onLogException(String tag, Throwable e); - void onLogMessage(String tag, String message); - } - - private ExternalLogger(){} - - public static void addListener(LoggingListener listener) { - sListener.add(listener); - } - - public static void removeListener(LoggingListener listener) { - sListener.remove(listener); - } - - public static void logException(String tag, Throwable e) { - for (LoggingListener listener: sListener) { - listener.onLogException(tag, e); - } - } - - public static void logMessage(String tag, String message) { - for (LoggingListener listener: sListener) { - listener.onLogMessage(tag, message); - } - } -} diff --git a/android-smsmms/src/main/java/com/android/mms/util/RateController.java b/android-smsmms/src/main/java/com/android/mms/util/RateController.java index 9c3985c40b8391c91c09d9ac12e75f4ccc62dc81..fa4365dcb34e538027349aaad0f8dfc28775210d 100755 --- a/android-smsmms/src/main/java/com/android/mms/util/RateController.java +++ b/android-smsmms/src/main/java/com/android/mms/util/RateController.java @@ -24,12 +24,9 @@ import android.content.IntentFilter; import android.database.Cursor; import android.database.sqlite.SqliteWrapper; import android.provider.Telephony.Mms.Rate; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; public class RateController { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private static final int RATE_LIMIT = 100; @@ -55,7 +52,7 @@ public class RateController { @Override public void onReceive(Context context, Intent intent) { if (LOCAL_LOGV) { - Log.v(TAG, "Intent received: " + intent); + Timber.v("Intent received: " + intent); } if (RATE_LIMIT_CONFIRMED_ACTION.equals(intent.getAction())) { @@ -74,11 +71,11 @@ public class RateController { public static void init(Context context) { if (LOCAL_LOGV) { - Log.v(TAG, "RateController.init()"); + Timber.v("RateController.init()"); } if (sInstance != null) { - Log.w(TAG, "Already initialized."); + Timber.w("Already initialized."); return; } sInstance = new RateController(context); @@ -149,7 +146,7 @@ public class RateController { for (int t = 0; (mAnswer == NO_ANSWER) && (t < ANSWER_TIMEOUT); t += 1000) { try { if (LOCAL_LOGV) { - Log.v(TAG, "Waiting for answer..." + t / 1000); + Timber.v("Waiting for answer..." + t / 1000); } wait(1000L); } catch (InterruptedException e) { diff --git a/android-smsmms/src/main/java/com/android/mms/util/SendingProgressTokenManager.java b/android-smsmms/src/main/java/com/android/mms/util/SendingProgressTokenManager.java index fdd397f078f96f898ba466dcd437c48dabb19196..a0d4dcc002b3a92fe9eb04cedaba56a637d28580 100755 --- a/android-smsmms/src/main/java/com/android/mms/util/SendingProgressTokenManager.java +++ b/android-smsmms/src/main/java/com/android/mms/util/SendingProgressTokenManager.java @@ -16,14 +16,11 @@ package com.android.mms.util; -import com.android.mms.logs.LogTag; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.HashMap; public class SendingProgressTokenManager { - private static final String TAG = LogTag.TAG; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private static final HashMap TOKEN_POOL; @@ -36,21 +33,21 @@ public class SendingProgressTokenManager { synchronized public static long get(Object key) { Long token = TOKEN_POOL.get(key); if (LOCAL_LOGV) { - Log.v(TAG, "TokenManager.get(" + key + ") -> " + token); + Timber.v("TokenManager.get(" + key + ") -> " + token); } return token != null ? token : NO_TOKEN; } synchronized public static void put(Object key, long token) { if (LOCAL_LOGV) { - Log.v(TAG, "TokenManager.put(" + key + ", " + token + ")"); + Timber.v("TokenManager.put(" + key + ", " + token + ")"); } TOKEN_POOL.put(key, token); } synchronized public static void remove(Object key) { if (LOCAL_LOGV) { - Log.v(TAG, "TokenManager.remove(" + key + ")"); + Timber.v("TokenManager.remove(" + key + ")"); } TOKEN_POOL.remove(key); } diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/EncodedStringValue.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/EncodedStringValue.java index 706be7df935b86d6dbb8d35b7ca980ef127917e2..26de8f6b47fc74c685ce9239b8b5a9b01cac1b64 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/EncodedStringValue.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/EncodedStringValue.java @@ -16,7 +16,7 @@ package com.google.android.mms.pdu_alt; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,8 +27,6 @@ import java.util.ArrayList; * Encoded-string-value = Text-string | Value-length Char-set Text-string */ public class EncodedStringValue implements Cloneable { - private static final String TAG = "EncodedStringValue"; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; /** @@ -74,7 +72,7 @@ public class EncodedStringValue implements Cloneable { mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME); mCharacterSet = CharacterSets.DEFAULT_CHARSET; } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Default encoding must be supported.", e); + Timber.e(e, "Default encoding must be supported."); } } @@ -140,7 +138,7 @@ public class EncodedStringValue implements Cloneable { return new String(mData, name); } catch (UnsupportedEncodingException e) { if (LOCAL_LOGV) { - Log.v(TAG, e.getMessage(), e); + Timber.v(e, e.getMessage()); } try { return new String(mData, CharacterSets.MIMENAME_ISO_8859_1); @@ -172,7 +170,7 @@ public class EncodedStringValue implements Cloneable { newTextString.write(mData); newTextString.write(textString); } catch (IOException e) { - Log.e(TAG, "logging error", e); + Timber.e(e, "logging error"); e.printStackTrace(); throw new NullPointerException( "appendTextString: failed when write a new Text-string"); @@ -196,7 +194,7 @@ public class EncodedStringValue implements Cloneable { try { return new EncodedStringValue(mCharacterSet, dstBytes); } catch (Exception e) { - Log.e(TAG, "logging error", e); + Timber.e(e, "logging error"); e.printStackTrace(); throw new CloneNotSupportedException(e.getMessage()); } diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduComposer.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduComposer.java index fbba0e2b626c3e566f8b37b5edc1636cd5abf64d..8e9f5edb5948f9673ab7c2ecc567815e181ae202 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduComposer.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduComposer.java @@ -19,7 +19,7 @@ package com.google.android.mms.pdu_alt; import android.content.ContentResolver; import android.content.Context; import android.text.TextUtils; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; @@ -80,7 +80,6 @@ public class PduComposer { * Block size when read data from InputStream. */ static private final int PDU_COMPOSER_BLOCK_SIZE = 1024; - private static final String TAG = "PduComposer"; /** * The output message. @@ -886,7 +885,7 @@ public class PduComposer { appendTextString(part.getContentType()); } catch (ArrayIndexOutOfBoundsException e){ - Log.e(TAG, "logging error", e); + Timber.e(e, "logging error"); e.printStackTrace(); } diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduParser.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduParser.java index 75cd1c8601cc296ceebdbae51010ea7cbea72f94..654de146df9f86548f18b36601cf43ca33b66e2b 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduParser.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduParser.java @@ -16,10 +16,9 @@ package com.google.android.mms.pdu_alt; -import com.android.mms.util.ExternalLogger; import com.google.android.mms.ContentType; import com.google.android.mms.InvalidHeaderValueException; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -80,8 +79,6 @@ public class PduParser { /** * The log tag. */ - private static final String LOG_TAG = "PduParser"; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; /** @@ -149,33 +146,33 @@ public class PduParser { switch (messageType) { case PduHeaders.MESSAGE_TYPE_SEND_REQ: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ"); + Timber.v("parse: MESSAGE_TYPE_SEND_REQ"); } SendReq sendReq = new SendReq(mHeaders, mBody); return sendReq; case PduHeaders.MESSAGE_TYPE_SEND_CONF: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF"); + Timber.v("parse: MESSAGE_TYPE_SEND_CONF"); } SendConf sendConf = new SendConf(mHeaders); return sendConf; case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND"); + Timber.v("parse: MESSAGE_TYPE_NOTIFICATION_IND"); } NotificationInd notificationInd = new NotificationInd(mHeaders); return notificationInd; case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND"); + Timber.v("parse: MESSAGE_TYPE_NOTIFYRESP_IND"); } NotifyRespInd notifyRespInd = new NotifyRespInd(mHeaders); return notifyRespInd; case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF"); + Timber.v("parse: MESSAGE_TYPE_RETRIEVE_CONF"); } RetrieveConf retrieveConf = new RetrieveConf(mHeaders, mBody); @@ -203,33 +200,33 @@ public class PduParser { // multipart/signed return retrieveConf; } else { - ExternalLogger.logMessage(LOG_TAG, "Unsupported ContentType: " + ctTypeStr); + Timber.v("Unsupported ContentType: " + ctTypeStr); } return null; case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND"); + Timber.v("parse: MESSAGE_TYPE_DELIVERY_IND"); } DeliveryInd deliveryInd = new DeliveryInd(mHeaders); return deliveryInd; case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND"); + Timber.v("parse: MESSAGE_TYPE_ACKNOWLEDGE_IND"); } AcknowledgeInd acknowledgeInd = new AcknowledgeInd(mHeaders); return acknowledgeInd; case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND"); + Timber.v("parse: MESSAGE_TYPE_READ_ORIG_IND"); } ReadOrigInd readOrigInd = new ReadOrigInd(mHeaders); return readOrigInd; case PduHeaders.MESSAGE_TYPE_READ_REC_IND: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND"); + Timber.v("parse: MESSAGE_TYPE_READ_REC_IND"); } ReadRecInd readRecInd = new ReadRecInd(mHeaders); @@ -261,7 +258,7 @@ public class PduParser { pduDataStream.reset(); byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); + Timber.v("TextHeader: " + new String(bVal)); } /* we should ignore it at the moment */ continue; @@ -271,7 +268,7 @@ public class PduParser { { int messageType = extractByteValue(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType); + Timber.v("parseHeaders: messageType: " + messageType); } switch (messageType) { // We don't support these kind of messages now. @@ -335,8 +332,7 @@ public class PduParser { { int value = extractByteValue(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " + - value); + Timber.v("parseHeaders: byte: " + headerField + " value: " + value); } try { @@ -360,7 +356,7 @@ public class PduParser { try { long value = parseLongInteger(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " + + Timber.v("parseHeaders: longint: " + headerField + " value: " + value); } headers.setLongInteger(value, headerField); @@ -379,7 +375,7 @@ public class PduParser { try { long value = parseIntegerValue(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " + + Timber.v("parseHeaders: int: " + headerField + " value: " + value); } headers.setLongInteger(value, headerField); @@ -415,7 +411,7 @@ public class PduParser { if (null != value) { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " + + Timber.v("parseHeaders: string: " + headerField + " value: " + new String(value)); } headers.setTextString(value, headerField); @@ -444,7 +440,7 @@ public class PduParser { if (null != value) { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField + Timber.v("parseHeaders: encoded string: " + headerField + " value: " + value.getString()); } headers.setEncodedStringValue(value, headerField); @@ -470,7 +466,7 @@ public class PduParser { if (null != address) { String str = new String(address); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField + Timber.v("parseHeaders: (to/cc/bcc) address: " + headerField + " value: " + str); } int endIndex = str.indexOf("/"); @@ -525,7 +521,7 @@ public class PduParser { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: time value: " + headerField + Timber.v("parseHeaders: time value: " + headerField + " value: " + timeValue); } headers.setLongInteger(timeValue, headerField); @@ -579,7 +575,7 @@ public class PduParser { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: from address: " + headerField + Timber.v("parseHeaders: from address: " + headerField + " value: " + from.getString()); } headers.setEncodedStringValue(from, PduHeaders.FROM); @@ -597,7 +593,7 @@ public class PduParser { pduDataStream.mark(1); int messageClass = extractByteValue(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField + Timber.v("parseHeaders: MESSAGE_CLASS: " + headerField + " value: " + messageClass); } @@ -650,7 +646,7 @@ public class PduParser { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField + Timber.v("parseHeaders: MMS_VERSION: " + headerField + " value: " + version); } headers.setOctet(version, PduHeaders.MMS_VERSION); @@ -685,7 +681,7 @@ public class PduParser { if (null != previouslySentBy) { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField + Timber.v("parseHeaders: PREVIOUSLY_SENT_BY: " + headerField + " value: " + previouslySentBy.getString()); } headers.setEncodedStringValue(previouslySentBy, @@ -718,7 +714,7 @@ public class PduParser { try { long perviouslySentDate = parseLongInteger(pduDataStream); if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField + Timber.v("parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField + " value: " + perviouslySentDate); } headers.setLongInteger(perviouslySentDate, @@ -737,7 +733,7 @@ public class PduParser { * Encoded-string-value */ if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField + Timber.v("parseHeaders: MM_FLAGS: " + headerField + " NOT REALLY SUPPORTED"); } @@ -761,7 +757,7 @@ public class PduParser { case PduHeaders.MBOX_QUOTAS: { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField); + Timber.v("parseHeaders: MBOX_TOTALS: " + headerField); } /* Value-length */ parseValueLength(pduDataStream); @@ -784,7 +780,7 @@ public class PduParser { case PduHeaders.ELEMENT_DESCRIPTOR: { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField); + Timber.v("parseHeaders: ELEMENT_DESCRIPTOR: " + headerField); } parseContentType(pduDataStream, null); @@ -802,8 +798,7 @@ public class PduParser { if (null != contentType) { try { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField + - contentType.toString()); + Timber.v("parseHeaders: CONTENT_TYPE: " + headerField + contentType.toString()); } headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); } catch(NullPointerException e) { @@ -829,7 +824,7 @@ public class PduParser { case PduHeaders.ATTRIBUTES: default: { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField); + Timber.v("parseHeaders: Unknown header: " + headerField); } log("Unknown header"); } @@ -961,7 +956,7 @@ public class PduParser { */ private static void log(String text) { if (LOCAL_LOGV) { - Log.v(LOG_TAG, text); + Timber.v(text); } } @@ -1475,7 +1470,7 @@ public class PduParser { map.put(PduPart.P_CHARSET, charsetInt); } catch (UnsupportedEncodingException e) { // Not a well-known charset, use "*". - Log.e(LOG_TAG, Arrays.toString(charsetStr), e); + Timber.e(e, Arrays.toString(charsetStr)); map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET); } } else { @@ -1510,10 +1505,10 @@ public class PduParser { break; default: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Content-Type parameter"); + Timber.v("Not supported Content-Type parameter"); } if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Content-Type"); + Timber.e("Corrupt Content-Type"); } else { lastLen = 0; } @@ -1522,7 +1517,7 @@ public class PduParser { } if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Content-Type"); + Timber.e("Corrupt Content-Type"); } } @@ -1572,7 +1567,7 @@ public class PduParser { contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); } } else { - Log.e(LOG_TAG, "Corrupt content-type"); + Timber.e("Corrupt content-type"); return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" } @@ -1583,7 +1578,7 @@ public class PduParser { } if (parameterLen < 0) { - Log.e(LOG_TAG, "Corrupt MMS message"); + Timber.e("Corrupt MMS message"); return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" } } else if (cur <= TEXT_MAX) { @@ -1722,10 +1717,10 @@ public class PduParser { break; default: if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); + Timber.v("Not supported Part headers: " + header); } if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); + Timber.e("Corrupt Part headers"); return false; } lastLen = 0; @@ -1746,11 +1741,11 @@ public class PduParser { lastLen = length - (startPos - tempPos); } else { if (LOCAL_LOGV) { - Log.v(LOG_TAG, "Not supported Part headers: " + header); + Timber.v("Not supported Part headers: " + header); } // Skip all headers of this part. if (-1 == skipWapValue(pduDataStream, lastLen)) { - Log.e(LOG_TAG, "Corrupt Part headers"); + Timber.e("Corrupt Part headers"); return false; } lastLen = 0; @@ -1758,7 +1753,7 @@ public class PduParser { } if (0 != lastLen) { - Log.e(LOG_TAG, "Corrupt Part headers"); + Timber.e("Corrupt Part headers"); return false; } diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPart.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPart.java index 6bfff3de1b76a9e7ac01614d93c172c4129064fd..9cb631f6cdb5fc8cea3152894684a2ec9f99324d 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPart.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPart.java @@ -118,8 +118,6 @@ public class PduPart { */ private byte[] mPartData = null; - private static final String TAG = "PduPart"; - /** * Empty Constructor. */ diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java index 8b4680e2782256054d9c70c9c1aa2b4486759d4a..638b4c9abcf5b7e0fcb7e903fe7c9bbfea136dd7 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/PduPersister.java @@ -46,7 +46,7 @@ import com.google.android.mms.util_alt.DrmConvertSession; import com.google.android.mms.util_alt.PduCache; import com.google.android.mms.util_alt.PduCacheEntry; import com.google.android.mms.util_alt.SqliteWrapper; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.ByteArrayOutputStream; import java.io.File; @@ -65,8 +65,6 @@ import java.util.Set; * This class is the high-level manager of PDU storage. */ public class PduPersister { - private static final String TAG = "PduPersister"; - private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private static final long DUMMY_THREAD_ID = Long.MAX_VALUE; @@ -367,7 +365,7 @@ public class PduPersister { try { if ((c == null) || (c.getCount() == 0)) { if (LOCAL_LOGV) { - Log.v(TAG, "loadParts(" + msgId + "): no part to load."); + Timber.v("loadParts(" + msgId + "): no part to load."); } return null; } @@ -456,7 +454,7 @@ public class PduPersister { len = is.read(buffer); } } catch (IOException e) { - Log.e(TAG, "Failed to load part data", e); + Timber.e(e, "Failed to load part data"); c.close(); throw new MmsException(e); } finally { @@ -464,7 +462,7 @@ public class PduPersister { try { is.close(); } catch (IOException e) { - Log.e(TAG, "Failed to close stream", e); + Timber.e(e, "Failed to close stream"); } // Ignore } } @@ -508,7 +506,7 @@ public class PduPersister { addrType); break; default: - Log.e(TAG, "Unknown address type: " + addrType); + Timber.e("Unknown address type: " + addrType); break; } } @@ -535,12 +533,12 @@ public class PduPersister { synchronized (PDU_CACHE_INSTANCE) { if (PDU_CACHE_INSTANCE.isUpdating(uri)) { if (LOCAL_LOGV) { - Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); + Timber.v("load: " + uri + " blocked by isUpdating()"); } try { PDU_CACHE_INSTANCE.wait(); } catch (InterruptedException e) { - Log.e(TAG, "load: ", e); + Timber.e(e, "load: "); } cacheEntry = PDU_CACHE_INSTANCE.get(uri); if (cacheEntry != null) { @@ -845,12 +843,12 @@ public class PduPersister { try { path = convertUriToPath(mContext, uri); if (LOCAL_LOGV) { - Log.v(TAG, "drm uri: " + uri + " path: " + path); + Timber.v("drm uri: " + uri + " path: " + path); } File f = new File(path); long len = f.length(); if (LOCAL_LOGV) { - Log.v(TAG, "drm path: " + path + " len: " + len); + Timber.v("drm path: " + path + " len: " + len); } if (len > 0) { // we're not going to re-persist and re-encrypt an already @@ -858,7 +856,7 @@ public class PduPersister { return; } } catch (Exception e) { - Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); + Timber.e(e, "Can't get file info for: " + part.getDataUri()); } } // We haven't converted the file yet, start the conversion @@ -874,7 +872,7 @@ public class PduPersister { if (data == null) { dataUri = part.getDataUri(); if ((dataUri == null) || (dataUri == uri)) { - Log.w(TAG, "Can't find data for this part."); + Timber.w("Can't find data for this part."); return; } // dataUri can look like: @@ -887,7 +885,7 @@ public class PduPersister { } if (LOCAL_LOGV) { - Log.v(TAG, "Saving data to: " + uri); + Timber.v("Saving data to: " + uri); } byte[] buffer = new byte[8192]; @@ -905,7 +903,7 @@ public class PduPersister { } } else { if (LOCAL_LOGV) { - Log.v(TAG, "Saving data to: " + uri); + Timber.v("Saving data to: " + uri); } if (!isDrm) { os.write(data); @@ -921,24 +919,24 @@ public class PduPersister { } } } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to open Input/Output stream.", e); + Timber.e(e, "Failed to open Input/Output stream."); throw new MmsException(e); } catch (IOException e) { - Log.e(TAG, "Failed to read/write data.", e); + Timber.e(e, "Failed to read/write data."); throw new MmsException(e); } finally { if (os != null) { try { os.close(); } catch (IOException e) { - Log.e(TAG, "IOException while closing: " + os, e); + Timber.e(e, "IOException while closing: " + os); } // Ignore } if (is != null) { try { is.close(); } catch (IOException e) { - Log.e(TAG, "IOException while closing: " + is, e); + Timber.e(e, "IOException while closing: " + is); } // Ignore } if (drmConvertSession != null) { @@ -1025,12 +1023,12 @@ public class PduPersister { // purging it. if (PDU_CACHE_INSTANCE.isUpdating(uri)) { if (LOCAL_LOGV) { - Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()"); + Timber.v("updateHeaders: " + uri + " blocked by isUpdating()"); } try { PDU_CACHE_INSTANCE.wait(); } catch (InterruptedException e) { - Log.e(TAG, "updateHeaders: ", e); + Timber.e(e, "updateHeaders: "); } } } @@ -1193,12 +1191,12 @@ public class PduPersister { synchronized (PDU_CACHE_INSTANCE) { if (PDU_CACHE_INSTANCE.isUpdating(uri)) { if (LOCAL_LOGV) { - Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()"); + Timber.v("updateParts: " + uri + " blocked by isUpdating()"); } try { PDU_CACHE_INSTANCE.wait(); } catch (InterruptedException e) { - Log.e(TAG, "updateParts: ", e); + Timber.e(e, "updateParts: "); } cacheEntry = PDU_CACHE_INSTANCE.get(uri); if (cacheEntry != null) { @@ -1301,12 +1299,12 @@ public class PduPersister { // purging it. if (PDU_CACHE_INSTANCE.isUpdating(uri)) { if (LOCAL_LOGV) { - Log.v(TAG, "persist: " + uri + " blocked by isUpdating()"); + Timber.v("persist: " + uri + " blocked by isUpdating()"); } try { PDU_CACHE_INSTANCE.wait(); } catch (InterruptedException e) { - Log.e(TAG, "persist1: ", e); + Timber.e(e, "persist1: "); } } } @@ -1552,7 +1550,7 @@ public class PduPersister { return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); } catch (UnsupportedEncodingException e) { // Impossible to reach here! - Log.e(TAG, "ISO_8859_1 must be supported!", e); + Timber.e(e, "ISO_8859_1 must be supported!"); return ""; } } @@ -1565,7 +1563,7 @@ public class PduPersister { return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); } catch (UnsupportedEncodingException e) { // Impossible to reach here! - Log.e(TAG, "ISO_8859_1 must be supported!", e); + Timber.e(e, "ISO_8859_1 must be supported!"); return new byte[0]; } } @@ -1583,7 +1581,7 @@ public class PduPersister { */ public Cursor getPendingMessages(long dueTime) { if (!checkReadSmsPermissions()) { - Log.w(TAG, "No read sms permissions have been granted"); + Timber.w("No read sms permissions have been granted"); return null; } Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); diff --git a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/SendReq.java b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/SendReq.java index 2c527d456e06264f3d97aed92e47c08eab1bea46..47bf0150e1ad41496aa35acaab99b9c47c603ac5 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/SendReq.java +++ b/android-smsmms/src/main/java/com/google/android/mms/pdu_alt/SendReq.java @@ -17,10 +17,9 @@ package com.google.android.mms.pdu_alt; import com.google.android.mms.InvalidHeaderValueException; -import com.klinker.android.logger.Log; +import timber.log.Timber; public class SendReq extends MultimediaMessagePdu { - private static final String TAG = "SendReq"; public SendReq() { super(); @@ -35,7 +34,7 @@ public class SendReq extends MultimediaMessagePdu { setTransactionId(generateTransactionId()); } catch (InvalidHeaderValueException e) { // Impossible to reach here since all headers we set above are valid. - Log.e(TAG, "Unexpected InvalidHeaderValueException.", e); + Timber.e(e, "Unexpected InvalidHeaderValueException."); throw new RuntimeException(e); } } diff --git a/android-smsmms/src/main/java/com/google/android/mms/smil/SmilHelper.java b/android-smsmms/src/main/java/com/google/android/mms/smil/SmilHelper.java index 007e769b79967789c5d128c4a5ab92fe56bf4dfc..27053dfdda2af2b63a74bae318adcf380f9f8f8a 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/smil/SmilHelper.java +++ b/android-smsmms/src/main/java/com/google/android/mms/smil/SmilHelper.java @@ -4,12 +4,12 @@ import com.android.mms.dom.smil.SmilDocumentImpl; import com.google.android.mms.ContentType; import com.google.android.mms.pdu_alt.PduBody; import com.google.android.mms.pdu_alt.PduPart; -import com.klinker.android.logger.Log; import org.w3c.dom.smil.SMILDocument; import org.w3c.dom.smil.SMILElement; import org.w3c.dom.smil.SMILLayoutElement; import org.w3c.dom.smil.SMILMediaElement; import org.w3c.dom.smil.SMILParElement; +import timber.log.Timber; public class SmilHelper { @@ -88,7 +88,7 @@ public class SmilHelper { par.appendChild(textElement); hasMedia = true; } else { - Log.e("creating_smil_document", "unknown mimetype"); + Timber.e("creating_smil_document", "unknown mimetype"); } } diff --git a/android-smsmms/src/main/java/com/google/android/mms/util_alt/AbstractCache.java b/android-smsmms/src/main/java/com/google/android/mms/util_alt/AbstractCache.java index beebc2b8c56bd29c9422adb8b0e02ae586f636b8..69123232ac7b422136c56f0ab511f4c2c9b95c83 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/util_alt/AbstractCache.java +++ b/android-smsmms/src/main/java/com/google/android/mms/util_alt/AbstractCache.java @@ -16,12 +16,11 @@ package com.google.android.mms.util_alt; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.HashMap; public abstract class AbstractCache { - private static final String TAG = "AbstractCache"; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; @@ -35,14 +34,14 @@ public abstract class AbstractCache { public boolean put(K key, V value) { if (LOCAL_LOGV) { - Log.v(TAG, "Trying to put " + key + " into cache."); + Timber.v("Trying to put " + key + " into cache."); } if (mCacheMap.size() >= MAX_CACHED_ITEMS) { // TODO Should remove the oldest or least hit cached entry // and then cache the new one. if (LOCAL_LOGV) { - Log.v(TAG, "Failed! size limitation reached."); + Timber.v("Failed! size limitation reached."); } return false; } @@ -53,7 +52,7 @@ public abstract class AbstractCache { mCacheMap.put(key, cacheEntry); if (LOCAL_LOGV) { - Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total."); + Timber.v(key + " cached, " + mCacheMap.size() + " items total."); } return true; } @@ -62,7 +61,7 @@ public abstract class AbstractCache { public V get(K key) { if (LOCAL_LOGV) { - Log.v(TAG, "Trying to get " + key + " from cache."); + Timber.v("Trying to get " + key + " from cache."); } if (key != null) { @@ -70,7 +69,7 @@ public abstract class AbstractCache { if (cacheEntry != null) { cacheEntry.hit++; if (LOCAL_LOGV) { - Log.v(TAG, key + " hit " + cacheEntry.hit + " times."); + Timber.v(key + " hit " + cacheEntry.hit + " times."); } return cacheEntry.value; } @@ -80,13 +79,13 @@ public abstract class AbstractCache { public V purge(K key) { if (LOCAL_LOGV) { - Log.v(TAG, "Trying to purge " + key); + Timber.v("Trying to purge " + key); } CacheEntry v = mCacheMap.remove(key); if (LOCAL_LOGV) { - Log.v(TAG, mCacheMap.size() + " items cached."); + Timber.v(mCacheMap.size() + " items cached."); } return v != null ? v.value : null; @@ -94,8 +93,7 @@ public abstract class AbstractCache { public void purgeAll() { if (LOCAL_LOGV) { - Log.v(TAG, "Purging cache, " + mCacheMap.size() - + " items dropped."); + Timber.v("Purging cache, " + mCacheMap.size() + " items dropped."); } mCacheMap.clear(); } diff --git a/android-smsmms/src/main/java/com/google/android/mms/util_alt/DownloadDrmHelper.java b/android-smsmms/src/main/java/com/google/android/mms/util_alt/DownloadDrmHelper.java index 7845e52c7d4f2d6eef503f8a2acc4a90b500ed69..bb039b2f3caaa387f9f8353a991ea70d709605d3 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/util_alt/DownloadDrmHelper.java +++ b/android-smsmms/src/main/java/com/google/android/mms/util_alt/DownloadDrmHelper.java @@ -18,10 +18,10 @@ package com.google.android.mms.util_alt; import android.content.Context; import android.drm.DrmManagerClient; -import com.klinker.android.logger.Log; +import timber.log.Timber; public class DownloadDrmHelper { - private static final String TAG = "DownloadDrmHelper"; + /** The MIME type of special DRM files */ public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message"; @@ -46,10 +46,9 @@ public class DownloadDrmHelper { result = drmClient.canHandle("", mimetype); } } catch (IllegalArgumentException e) { - Log.w(TAG, - "DrmManagerClient instance could not be created, context is Illegal."); + Timber.w("DrmManagerClient instance could not be created, context is Illegal."); } catch (IllegalStateException e) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); + Timber.w("DrmManagerClient didn't initialize properly."); } } return result; @@ -99,10 +98,9 @@ public class DownloadDrmHelper { result = drmClient.getOriginalMimeType(path); } } catch (IllegalArgumentException ex) { - Log.w(TAG, - "Can't get original mime type since path is null or empty string."); + Timber.w("Can't get original mime type since path is null or empty string."); } catch (IllegalStateException ex) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); + Timber.w("DrmManagerClient didn't initialize properly."); } return result; } diff --git a/android-smsmms/src/main/java/com/google/android/mms/util_alt/DrmConvertSession.java b/android-smsmms/src/main/java/com/google/android/mms/util_alt/DrmConvertSession.java index 0de09b1b7ee8dd90f2ef543cfc42496e0502cd84..3c38ed7fc5b2bd709dbb66748e7eb490a9b40c6e 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/util_alt/DrmConvertSession.java +++ b/android-smsmms/src/main/java/com/google/android/mms/util_alt/DrmConvertSession.java @@ -18,7 +18,7 @@ package com.google.android.mms.util_alt; import android.content.Context; import android.drm.DrmConvertedStatus; import android.drm.DrmManagerClient; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.FileNotFoundException; import java.io.IOException; @@ -28,7 +28,6 @@ import java.io.RandomAccessFile; public class DrmConvertSession { private DrmManagerClient mDrmClient; private int mConvertSessionId; - private static final String TAG = "DrmConvertSession"; private static final int STATUS_UNKNOWN_ERROR = 491; private static final int STATUS_NOT_ACCEPTABLE = 406; @@ -56,16 +55,15 @@ public class DrmConvertSession { try { convertSessionId = drmClient.openConvertSession(mimeType); } catch (IllegalArgumentException e) { - Log.w(TAG, "Conversion of Mimetype: " + mimeType + Timber.w("Conversion of Mimetype: " + mimeType + " is not supported.", e); } catch (IllegalStateException e) { - Log.w(TAG, "Could not access Open DrmFramework.", e); + Timber.w(e, "Could not access Open DrmFramework."); } } catch (IllegalArgumentException e) { - Log.w(TAG, - "DrmManagerClient instance could not be created, context is Illegal."); + Timber.w("DrmManagerClient instance could not be created, context is Illegal."); } catch (IllegalStateException e) { - Log.w(TAG, "DrmManagerClient didn't initialize properly."); + Timber.w("DrmManagerClient didn't initialize properly."); } } @@ -102,10 +100,10 @@ public class DrmConvertSession { result = convertedStatus.convertedData; } } catch (IllegalArgumentException e) { - Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: " + Timber.w("Buffer with data to convert is illegal. Convertsession: " + mConvertSessionId, e); } catch (IllegalStateException e) { - Log.w(TAG, "Could not convert data. Convertsession: " + + Timber.w("Could not convert data. Convertsession: " + mConvertSessionId, e); } } else { @@ -143,15 +141,15 @@ public class DrmConvertSession { result = STATUS_SUCCESS; } catch (FileNotFoundException e) { result = STATUS_FILE_ERROR; - Log.w(TAG, "File: " + filename + " could not be found.", e); + Timber.w(e, "File: " + filename + " could not be found."); } catch (IOException e) { result = STATUS_FILE_ERROR; - Log.w(TAG, "Could not access File: " + filename + " .", e); + Timber.w(e, "Could not access File: " + filename + " ."); } catch (IllegalArgumentException e) { result = STATUS_FILE_ERROR; - Log.w(TAG, "Could not open file in mode: rw", e); + Timber.w(e, "Could not open file in mode: rw"); } catch (SecurityException e) { - Log.w(TAG, "Access to File: " + filename + + Timber.w("Access to File: " + filename + " was denied denied by SecurityManager.", e); } finally { if (rndAccessFile != null) { @@ -159,14 +157,14 @@ public class DrmConvertSession { rndAccessFile.close(); } catch (IOException e) { result = STATUS_FILE_ERROR; - Log.w(TAG, "Failed to close File:" + filename + Timber.w("Failed to close File:" + filename + ".", e); } } } } } catch (IllegalStateException e) { - Log.w(TAG, "Could not close convertsession. Convertsession: " + + Timber.w("Could not close convertsession. Convertsession: " + mConvertSessionId, e); } } diff --git a/android-smsmms/src/main/java/com/google/android/mms/util_alt/PduCache.java b/android-smsmms/src/main/java/com/google/android/mms/util_alt/PduCache.java index 878f91e764adfdf986ea8e8fe55bfbdec7551376..d677ece90433efb09a66d5c981674a4c8146e34c 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/util_alt/PduCache.java +++ b/android-smsmms/src/main/java/com/google/android/mms/util_alt/PduCache.java @@ -20,13 +20,13 @@ import android.content.ContentUris; import android.content.UriMatcher; import android.net.Uri; import android.provider.Telephony.Mms; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.util.HashMap; import java.util.HashSet; public final class PduCache extends AbstractCache { - private static final String TAG = "PduCache"; + private static final boolean LOCAL_LOGV = false; private static final int MMS_ALL = 0; @@ -82,7 +82,7 @@ public final class PduCache extends AbstractCache { synchronized public static final PduCache getInstance() { if (sInstance == null) { if (LOCAL_LOGV) { - Log.v(TAG, "Constructing new PduCache instance."); + Timber.v("Constructing new PduCache instance."); } sInstance = new PduCache(); } @@ -202,14 +202,14 @@ public final class PduCache extends AbstractCache { } if (LOCAL_LOGV) { - Log.v(TAG, uri + " -> " + normalizedKey); + Timber.v(uri + " -> " + normalizedKey); } return normalizedKey; } private void purgeByMessageBox(Integer msgBoxId) { if (LOCAL_LOGV) { - Log.v(TAG, "Purge cache in message box: " + msgBoxId); + Timber.v("Purge cache in message box: " + msgBoxId); } if (msgBoxId != null) { @@ -235,7 +235,7 @@ public final class PduCache extends AbstractCache { private void purgeByThreadId(long threadId) { if (LOCAL_LOGV) { - Log.v(TAG, "Purge cache in thread: " + threadId); + Timber.v("Purge cache in thread: " + threadId); } HashSet thread = mThreads.remove(threadId); diff --git a/android-smsmms/src/main/java/com/google/android/mms/util_alt/SqliteWrapper.java b/android-smsmms/src/main/java/com/google/android/mms/util_alt/SqliteWrapper.java index 823bac3616f73838bbf621323936e3a43a1acce5..462cf468df981624ff982aed27f272abb3d53737 100755 --- a/android-smsmms/src/main/java/com/google/android/mms/util_alt/SqliteWrapper.java +++ b/android-smsmms/src/main/java/com/google/android/mms/util_alt/SqliteWrapper.java @@ -24,10 +24,9 @@ import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.widget.Toast; -import com.klinker.android.logger.Log; +import timber.log.Timber; public final class SqliteWrapper { - private static final String TAG = "SqliteWrapper"; private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE = "unable to open database file"; @@ -69,7 +68,7 @@ public final class SqliteWrapper { try { return resolver.query(uri, projection, selection, selectionArgs, sortOrder); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when query: ", e); + Timber.e(e, "Catch a SQLiteException when query: "); checkSQLiteException(context, e); return null; } @@ -79,7 +78,7 @@ public final class SqliteWrapper { try { return cursor.requery(); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when requery: ", e); + Timber.e(e, "Catch a SQLiteException when requery: "); checkSQLiteException(context, e); return false; } @@ -89,7 +88,7 @@ public final class SqliteWrapper { try { return resolver.update(uri, values, where, selectionArgs); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when update: ", e); + Timber.e(e, "Catch a SQLiteException when update: "); checkSQLiteException(context, e); return -1; } @@ -100,7 +99,7 @@ public final class SqliteWrapper { try { return resolver.delete(uri, where, selectionArgs); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when delete: ", e); + Timber.e(e, "Catch a SQLiteException when delete: "); checkSQLiteException(context, e); return -1; } @@ -111,7 +110,7 @@ public final class SqliteWrapper { try { return resolver.insert(uri, values); } catch (SQLiteException e) { - Log.e(TAG, "Catch a SQLiteException when insert: ", e); + Timber.e(e, "Catch a SQLiteException when insert: "); checkSQLiteException(context, e); return null; } diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsFileProvider.java b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsFileProvider.java index 9a6384cab8f0bc5ced71c8b192613dd2b8fa4b96..1f55738b55aa6fdc99295ea7c711ee175e605e0e 100755 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsFileProvider.java +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsFileProvider.java @@ -33,8 +33,7 @@ public class MmsFileProvider extends ContentProvider { } @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Don't support queries. return null; } diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedReceiver.java b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedReceiver.java index 2e57fbe5ead42dea4cf7817396a06ebffba15800..b8da3699a9af24f396973437c6ca6679d03f1376 100755 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedReceiver.java +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedReceiver.java @@ -23,7 +23,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.provider.Telephony; import android.telephony.SmsManager; -import android.util.Log; import com.android.mms.service_alt.DownloadRequest; import com.android.mms.service_alt.MmsConfig; import com.android.mms.transaction.DownloadManager; @@ -41,6 +40,7 @@ import com.google.android.mms.pdu_alt.PduParser; import com.google.android.mms.pdu_alt.PduPersister; import com.google.android.mms.pdu_alt.RetrieveConf; import com.google.android.mms.util_alt.SqliteWrapper; +import timber.log.Timber; import java.io.File; import java.io.FileInputStream; @@ -54,8 +54,6 @@ import java.util.concurrent.Executors; import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_RETRIEVED; public class MmsReceivedReceiver extends BroadcastReceiver { - private static final String TAG = "MmsReceivedReceiver"; - public static final String MMS_RECEIVED = "com.klinker.android.messaging.MMS_RECEIVED"; public static final String EXTRA_FILE_PATH = "file_path"; public static final String EXTRA_LOCATION_URL = "location_url"; @@ -79,10 +77,10 @@ public class MmsReceivedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.v(TAG, "MMS has finished downloading, persisting it to the database"); + Timber.v("MMS has finished downloading, persisting it to the database"); String path = intent.getStringExtra(EXTRA_FILE_PATH); - Log.v(TAG, path); + Timber.v(path); FileInputStream reader = null; Uri messageUri = null; @@ -102,28 +100,28 @@ public class MmsReceivedReceiver extends BroadcastReceiver { intent.getStringExtra(EXTRA_LOCATION_URL), Utils.getDefaultSubscriptionId(), null); - Log.v(TAG, "response saved successfully"); - Log.v(TAG, "response length: " + response.length); + Timber.v("response saved successfully"); + Timber.v("response length: " + response.length); mDownloadFile.delete(); if (tasks != null) { - Log.v(TAG, "running the common async notifier for download"); + Timber.v("running the common async notifier for download"); for (CommonAsyncTask task : tasks) task.executeOnExecutor(RECEIVE_NOTIFICATION_EXECUTOR); } } catch (FileNotFoundException e) { errorMessage = "MMS received, file not found exception"; - Log.e(TAG, errorMessage, e); + Timber.e(e, errorMessage); } catch (IOException e) { errorMessage = "MMS received, io exception"; - Log.e(TAG, errorMessage, e); + Timber.e(e, errorMessage); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { errorMessage = "MMS received, io exception"; - Log.e(TAG, "MMS received, io exception", e); + Timber.e(e, "MMS received, io exception"); } } } @@ -275,7 +273,7 @@ public class MmsReceivedReceiver extends BroadcastReceiver { sendPdu(new PduComposer(mContext, notifyRespInd).make()); } } catch (MmsException | IOException e) { - Log.e(TAG, "error", e); + Timber.e(e, "error"); } return null; } @@ -296,7 +294,7 @@ public class MmsReceivedReceiver extends BroadcastReceiver { // the MMS proxy-relay doesn't require an ACK. byte[] tranId = mRetrieveConf.getTransactionId(); if (tranId != null) { - Log.v(TAG, "sending ACK to MMSC: " + mTransactionSettings.getMmscUrl()); + Timber.v("sending ACK to MMSC: " + mTransactionSettings.getMmscUrl()); // Create M-Acknowledge.ind com.google.android.mms.pdu_alt.AcknowledgeInd acknowledgeInd; @@ -315,7 +313,7 @@ public class MmsReceivedReceiver extends BroadcastReceiver { sendPdu(new PduComposer(mContext, acknowledgeInd).make()); } } catch (IOException | MmsException e) { - Log.e(TAG, "error", e); + Timber.e(e, "error"); } } return null; @@ -324,12 +322,12 @@ public class MmsReceivedReceiver extends BroadcastReceiver { private List getNotificationTask(Context context, Intent intent, byte[] response) { if (response.length == 0) { - Log.v(TAG, "MmsReceivedReceiver.sendNotification blank response"); + Timber.v("MmsReceivedReceiver.sendNotification blank response"); return null; } if (getMmscInfoForReceptionAck() == null) { - Log.v(TAG, "No MMSC information set, so no notification tasks will be able to complete"); + Timber.v("No MMSC information set, so no notification tasks will be able to complete"); return null; } @@ -337,7 +335,7 @@ public class MmsReceivedReceiver extends BroadcastReceiver { (new PduParser(response, new MmsConfig.Overridden(new MmsConfig(context), null). getSupportMmsContentDisposition())).parse(); if (pdu == null || !(pdu instanceof RetrieveConf)) { - android.util.Log.e(TAG, "MmsReceivedReceiver.sendNotification failed to parse pdu"); + Timber.e("MmsReceivedReceiver.sendNotification failed to parse pdu"); return null; } @@ -352,7 +350,7 @@ public class MmsReceivedReceiver extends BroadcastReceiver { return responseTasks; } catch (MmsException e) { - Log.e(TAG, "error", e); + Timber.e(e, "error"); return null; } } diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedService.java b/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedService.java deleted file mode 100755 index ab4111efaf1af76e6ebdc8106941c805d5b2fe48..0000000000000000000000000000000000000000 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/MmsReceivedService.java +++ /dev/null @@ -1,316 +0,0 @@ -package com.klinker.android.send_message; - - -import android.app.IntentService; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.provider.Telephony; -import android.telephony.SmsManager; -import com.android.mms.service_alt.DownloadRequest; -import com.android.mms.service_alt.MmsConfig; -import com.android.mms.transaction.DownloadManager; -import com.android.mms.transaction.HttpUtils; -import com.android.mms.transaction.TransactionSettings; -import com.android.mms.util.SendingProgressTokenManager; -import com.google.android.mms.InvalidHeaderValueException; -import com.google.android.mms.MmsException; -import com.google.android.mms.pdu_alt.EncodedStringValue; -import com.google.android.mms.pdu_alt.GenericPdu; -import com.google.android.mms.pdu_alt.NotificationInd; -import com.google.android.mms.pdu_alt.NotifyRespInd; -import com.google.android.mms.pdu_alt.PduComposer; -import com.google.android.mms.pdu_alt.PduHeaders; -import com.google.android.mms.pdu_alt.PduParser; -import com.google.android.mms.pdu_alt.PduPersister; -import com.google.android.mms.pdu_alt.RetrieveConf; -import com.google.android.mms.util_alt.SqliteWrapper; -import com.klinker.android.logger.Log; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; - -import static com.google.android.mms.pdu_alt.PduHeaders.STATUS_RETRIEVED; -import static com.klinker.android.send_message.MmsReceivedReceiver.EXTRA_FILE_PATH; -import static com.klinker.android.send_message.MmsReceivedReceiver.EXTRA_LOCATION_URL; -import static com.klinker.android.send_message.MmsReceivedReceiver.EXTRA_TRIGGER_PUSH; -import static com.klinker.android.send_message.MmsReceivedReceiver.EXTRA_URI; - -public class MmsReceivedService extends IntentService { - private static final String TAG = "MmsReceivedService"; - - private static final String LOCATION_SELECTION = - Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?"; - - public MmsReceivedService() { - super("MmsReceivedService"); - } - - public MmsReceivedService(String name) { - super(name); - } - - @Override - protected void onHandleIntent(Intent intent) { - Log.v(TAG, "MMS has finished downloading, persisting it to the database"); - - String path = intent.getStringExtra(EXTRA_FILE_PATH); - Log.v(TAG, path); - - FileInputStream reader = null; - try { - File mDownloadFile = new File(path); - final int nBytes = (int) mDownloadFile.length(); - reader = new FileInputStream(mDownloadFile); - final byte[] response = new byte[nBytes]; - reader.read(response, 0, nBytes); - - CommonNotificationTask task = getNotificationTask(this, intent, response); - executeNotificationTask(task); - - DownloadRequest.persist(this, response, - new MmsConfig.Overridden(new MmsConfig(this), null), - intent.getStringExtra(EXTRA_LOCATION_URL), - Utils.getDefaultSubscriptionId(), null); - - Log.v(TAG, "response saved successfully"); - Log.v(TAG, "response length: " + response.length); - mDownloadFile.delete(); - } catch (FileNotFoundException e) { - Log.e(TAG, "MMS received, file not found exception", e); - } catch (IOException e) { - Log.e(TAG, "MMS received, io exception", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - Log.e(TAG, "MMS received, io exception", e); - } - } - - handleHttpError(this, intent); - DownloadManager.finishDownload(intent.getStringExtra(EXTRA_LOCATION_URL)); - } - } - - private static void handleHttpError(Context context, Intent intent) { - final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0); - if (httpError == 404 || - httpError == 400) { - // Delete the corresponding NotificationInd - SqliteWrapper.delete(context, - context.getContentResolver(), - Telephony.Mms.CONTENT_URI, - LOCATION_SELECTION, - new String[]{ - Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND), - intent.getStringExtra(EXTRA_LOCATION_URL) - }); - } - } - - private static NotificationInd getNotificationInd(Context context, Intent intent) throws MmsException { - return (NotificationInd) PduPersister.getPduPersister(context).load((Uri) intent.getParcelableExtra(EXTRA_URI)); - } - - private static abstract class CommonNotificationTask { - protected final Context mContext; - private final TransactionSettings mTransactionSettings; - final NotificationInd mNotificationInd; - final String mContentLocation; - - CommonNotificationTask(Context context, TransactionSettings settings, NotificationInd ind) { - mContext = context; - mTransactionSettings = settings; - mNotificationInd = ind; - mContentLocation = new String(ind.getContentLocation()); - } - - /** - * A common method to send a PDU to MMSC. - * - * @param pdu A byte array which contains the data of the PDU. - * @param mmscUrl Url of the recipient MMSC. - * @return A byte array which contains the response data. - * If an HTTP error code is returned, an IOException will be thrown. - * @throws java.io.IOException if any error occurred on network interface or - * an HTTP error code(>=400) returned from the server. - * @throws com.google.android.mms.MmsException if pdu is null. - */ - byte[] sendPdu(byte[] pdu, String mmscUrl) throws IOException, MmsException { - return sendPdu(SendingProgressTokenManager.NO_TOKEN, pdu, mmscUrl); - } - - /** - * A common method to send a PDU to MMSC. - * - * @param pdu A byte array which contains the data of the PDU. - * @return A byte array which contains the response data. - * If an HTTP error code is returned, an IOException will be thrown. - * @throws java.io.IOException if any error occurred on network interface or - * an HTTP error code(>=400) returned from the server. - * @throws com.google.android.mms.MmsException if pdu is null. - */ - byte[] sendPdu(byte[] pdu) throws IOException, MmsException { - return sendPdu(SendingProgressTokenManager.NO_TOKEN, pdu, - mTransactionSettings.getMmscUrl()); - } - - /** - * A common method to send a PDU to MMSC. - * - * @param token The token to identify the sending progress. - * @param pdu A byte array which contains the data of the PDU. - * @param mmscUrl Url of the recipient MMSC. - * @return A byte array which contains the response data. - * If an HTTP error code is returned, an IOException will be thrown. - * @throws java.io.IOException if any error occurred on network interface or - * an HTTP error code(>=400) returned from the server. - * @throws com.google.android.mms.MmsException if pdu is null. - */ - private byte[] sendPdu(final long token, final byte[] pdu, - final String mmscUrl) throws IOException, MmsException { - if (pdu == null) { - throw new MmsException(); - } - - if (mmscUrl == null) { - throw new IOException("Cannot establish route: mmscUrl is null"); - } - - if (com.android.mms.transaction.Transaction.useWifi(mContext)) { - return HttpUtils.httpConnection( - mContext, token, - mmscUrl, - pdu, HttpUtils.HTTP_POST_METHOD, - false, null, 0); - } - - return Utils.ensureRouteToMmsNetwork(mContext, mmscUrl, mTransactionSettings.getProxyAddress(), new Utils.Task() { - @Override - public byte[] run() throws IOException { - return HttpUtils.httpConnection( - mContext, token, - mmscUrl, - pdu, HttpUtils.HTTP_POST_METHOD, - mTransactionSettings.isProxySet(), - mTransactionSettings.getProxyAddress(), - mTransactionSettings.getProxyPort()); - } - }); - } - - public abstract void run() throws IOException; - } - - private static class NotifyRespTask extends CommonNotificationTask { - NotifyRespTask(Context context, NotificationInd ind, TransactionSettings settings) { - super(context, settings, ind); - } - - @Override - public void run() throws IOException { - // Create the M-NotifyResp.ind - NotifyRespInd notifyRespInd; - try { - notifyRespInd = new NotifyRespInd( - PduHeaders.CURRENT_MMS_VERSION, - mNotificationInd.getTransactionId(), - STATUS_RETRIEVED); - - // Pack M-NotifyResp.ind and send it - if(com.android.mms.MmsConfig.getNotifyWapMMSC()) { - sendPdu(new PduComposer(mContext, notifyRespInd).make(), mContentLocation); - } else { - sendPdu(new PduComposer(mContext, notifyRespInd).make()); - } - } catch (MmsException e) { - Log.e(TAG, "error", e); - } - } - } - - private static class AcknowledgeIndTask extends CommonNotificationTask { - private final RetrieveConf mRetrieveConf; - - AcknowledgeIndTask(Context context, NotificationInd ind, TransactionSettings settings, RetrieveConf rc) { - super(context, settings, ind); - mRetrieveConf = rc; - } - - @Override - public void run() throws IOException { - // Send M-Acknowledge.ind to MMSC if required. - // If the Transaction-ID isn't set in the M-Retrieve.conf, it means - // the MMS proxy-relay doesn't require an ACK. - byte[] tranId = mRetrieveConf.getTransactionId(); - if (tranId != null) { - // Create M-Acknowledge.ind - com.google.android.mms.pdu_alt.AcknowledgeInd acknowledgeInd; - try { - acknowledgeInd = new com.google.android.mms.pdu_alt.AcknowledgeInd( - PduHeaders.CURRENT_MMS_VERSION, tranId); - - // insert the 'from' address per spec - String lineNumber = Utils.getMyPhoneNumber(mContext); - acknowledgeInd.setFrom(new EncodedStringValue(lineNumber)); - - // Pack M-Acknowledge.ind and send it - if(com.android.mms.MmsConfig.getNotifyWapMMSC()) { - sendPdu(new PduComposer(mContext, acknowledgeInd).make(), mContentLocation); - } else { - sendPdu(new PduComposer(mContext, acknowledgeInd).make()); - } - } catch (InvalidHeaderValueException e) { - Log.e(TAG, "error", e); - } catch (MmsException e) { - Log.e(TAG, "error", e); - } - } - } - } - - private static CommonNotificationTask getNotificationTask(Context context, Intent intent, byte[] response) { - if (response.length == 0) { - return null; - } - - final GenericPdu pdu = - (new PduParser(response, new MmsConfig.Overridden(new MmsConfig(context), null). - getSupportMmsContentDisposition())).parse(); - if (pdu == null || !(pdu instanceof RetrieveConf)) { - android.util.Log.e(TAG, "MmsReceivedReceiver.sendNotification failed to parse pdu"); - return null; - } - - try { - NotificationInd ind = getNotificationInd(context, intent); - TransactionSettings transactionSettings = new TransactionSettings(context, null); - if (intent.getBooleanExtra(EXTRA_TRIGGER_PUSH, false)) { - return new NotifyRespTask(context, ind, transactionSettings); - } else { - return new AcknowledgeIndTask(context, ind, transactionSettings, (RetrieveConf) pdu); - } - } catch (MmsException e) { - Log.e(TAG, "error", e); - return null; - } - } - - private static void executeNotificationTask(CommonNotificationTask task) throws IOException { - if (task == null) { - return; - } - - try { - // need retry ? - task.run(); - } catch (IOException e) { - Log.e(TAG, "MMS send received notification, io exception", e); - throw e; - } - } -} diff --git a/android-smsmms/src/main/java/com/klinker/android/send_message/Utils.java b/android-smsmms/src/main/java/com/klinker/android/send_message/Utils.java index c04508b066e3fe3e33c3e6e0e44ba5d3b72b1ece..2fc446932950f2f15d4a3412e346373df2461d49 100755 --- a/android-smsmms/src/main/java/com/klinker/android/send_message/Utils.java +++ b/android-smsmms/src/main/java/com/klinker/android/send_message/Utils.java @@ -17,7 +17,7 @@ import android.text.TextUtils; import com.android.mms.service_alt.MmsNetworkManager; import com.android.mms.service_alt.exception.MmsNetworkException; import com.google.android.mms.util_alt.SqliteWrapper; -import com.klinker.android.logger.Log; +import timber.log.Timber; import java.io.IOException; import java.lang.reflect.Method; @@ -39,7 +39,6 @@ public class Utils { * characters to compare against when checking for 160 character sending compatibility */ public static final String GSM_CHARACTERS_REGEX = "^[A-Za-z0-9 \\r\\n@Ł$ĽčéůěňÇŘřĹĺ\u0394_\u03A6\u0393\u039B\u03A9\u03A0\u03A8\u03A3\u0398\u039EĆćßÉ!\"#$%&'()*+,\\-./:;<=>?ĄÄÖŃܧżäöńüŕ^{}\\\\\\[~\\]|\u20AC]*$"; - private static final String TAG = "Utils"; public static final int DEFAULT_SUBSCRIPTION_ID = 1; /** @@ -130,7 +129,7 @@ public class Utils { throw new IOException("Cannot establish route to proxy " + inetAddr); } } catch (Exception e) { - Log.e(TAG, "Cannot establishh route to proxy " + inetAddr, e); + Timber.e(e, "Cannot establishh route to proxy " + inetAddr); } } else { Uri uri = Uri.parse(url); @@ -145,7 +144,7 @@ public class Utils { throw new IOException("Cannot establish route to proxy " + inetAddr); } } catch (Exception e) { - Log.e(TAG, "Cannot establishh route to proxy " + inetAddr + " for " + url, e); + Timber.e(e, "Cannot establishh route to proxy " + inetAddr + " for " + url); } } } @@ -165,7 +164,7 @@ public class Utils { m.setAccessible(true); return (Boolean) m.invoke(cm); } catch (Exception e) { - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); return null; } } @@ -181,7 +180,7 @@ public class Utils { Method m = c.getMethod("getDataEnabled"); return (boolean) m.invoke(telephonyManager); } catch (Exception e) { - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); return true; } } @@ -198,7 +197,7 @@ public class Utils { Method m = c.getMethod("getDataEnabled", int.class); return (boolean) m.invoke(telephonyManager, subId); } catch (Exception e) { - Log.e(TAG, "exception thrown", e); + Timber.e(e, "exception thrown"); return isDataEnabled(telephonyManager); } } @@ -225,7 +224,7 @@ public class Utils { m.setAccessible(true); m.invoke(telephonyService, enabled); } catch (Exception e) { - Log.e(TAG, "error enabling data on lollipop", e); + Timber.e(e, "error enabling data on lollipop"); } } diff --git a/build.gradle b/build.gradle index a794ac3081cc3f2462684b494415c7cb567fb8ca..167bd214e8b470b0231a6a2f353ba51ef7e59291 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,31 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.androidx_appcompat_version = '1.0.2' + ext.androidx_appcompat_version = '1.1.0' ext.androidx_constraintlayout_version = '1.1.3' - ext.androidx_core_version = '1.0.1' + ext.androidx_core_version = '1.1.0' ext.androidx_emoji_version = '1.0.0' ext.androidx_exifinterface_version = '1.0.0' ext.androidx_testrunner_version = '1.1.0-alpha3' - ext.androidx_viewpager_version = '1.0.0-alpha01' - ext.autodispose_version = '0.7.0' + ext.androidx_viewpager_version = '1.0.0-beta05' + ext.autodispose_version = '1.3.0' ext.conductor_version = '2.1.5' + ext.coroutines_version = '1.2.2' ext.dagger_version = "2.16" ext.espresso_version = '3.1.0-alpha3' ext.exoplayer_version = "2.8.1" ext.glide_version = "4.8.0" ext.junit_version = '4.12' - ext.kotlin_version = '1.3.0' - ext.lifecycle_version = '2.0.0' + ext.kotlin_version = '1.3.50' + ext.lifecycle_version = '2.1.0' ext.material_version = '1.0.0' ext.mockito_version = '2.18.3' - ext.moshi_version = '1.6.0' + ext.moshi_version = '1.8.0' + ext.okhttp3_version = '4.1.0' ext.realm_version = '5.8.0' ext.realm_adapters_version = '3.1.0' ext.rxandroid_version = '2.0.1' + ext.rxdogtag_version = '0.2.0' ext.rxbinding_version = '2.0.0' ext.rxjava_version = '2.1.4' ext.rxkotlin_version = '2.1.0' @@ -32,14 +35,17 @@ buildscript { ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2] repositories { + maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.google.com' } jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.google.gms:google-services:4.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'io.fabric.tools:gradle:1.29.0' classpath "io.realm:realm-gradle-plugin:$realm_version" } } @@ -49,6 +55,7 @@ allprojects { google() jcenter() maven { url "https://jitpack.io" } + maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.google.com' } maven { name 'glide-snapshot' diff --git a/common/build.gradle b/common/build.gradle index 648b5a03c4f24c48672a5c2a12f1b3a3b88c6e25..a893d1c184c792d8de3febcf74f8423af7634ccb 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,11 +20,11 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml index b9d4a22b9ff7828d0d2b5d595251d1b793944d21..969a751c5cf2b22cbe2ce7119ded8abfa6278a88 100644 --- a/common/src/main/AndroidManifest.xml +++ b/common/src/main/AndroidManifest.xml @@ -16,4 +16,4 @@ ~ You should have received a copy of the GNU General Public License ~ along with QKSMS. If not, see . --> - + diff --git a/common/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java b/common/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java index 9d4f32be1e47f3e6471ad19eb4ea87c2185e11b0..c49c8c76801f7335c0cb39fd73cdb984a5de4e04 100644 --- a/common/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java +++ b/common/src/main/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java @@ -4,9 +4,9 @@ package com.bumptech.glide.gifencoder; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import timber.log.Timber; import java.io.BufferedOutputStream; import java.io.FileOutputStream; @@ -14,7 +14,6 @@ import java.io.IOException; import java.io.OutputStream; public class AnimatedGifEncoder { - private static final String TAG = "AnimatedGifEncoder"; // The minimum % of an images pixels that must be transparent for us to set a transparent index // automatically. @@ -412,10 +411,7 @@ public class AnimatedGifEncoder { // Assume images with greater where more than n% of the pixels are transparent actually have // transparency. See issue #214. hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE; - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "got pixels for frame with " + transparentPercentage - + "% transparent pixels"); - } + Timber.d("got pixels for frame with " + transparentPercentage + "% transparent pixels"); } /** diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt similarity index 77% rename from presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt rename to common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt index 5a43e0ad34d5c3440168a48adbad884731d965d6..2ece34d438387a594c9886168660f615830228fc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt +++ b/common/src/main/java/com/moez/QKSMS/common/util/extensions/ContextExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -18,11 +18,9 @@ */ package com.moez.QKSMS.common.util.extensions -import android.app.Activity import android.content.Context import android.graphics.Color import android.util.TypedValue -import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.annotation.StringRes import androidx.core.content.ContextCompat @@ -43,6 +41,13 @@ fun Context.resolveThemeAttribute(attributeId: Int, default: Int = 0): Int { return if (wasResolved) outValue.resourceId else default } +fun Context.resolveThemeBoolean(attributeId: Int, default: Boolean = false): Boolean { + val outValue = TypedValue() + val wasResolved = theme.resolveAttribute(attributeId, outValue, true) + + return if (wasResolved) outValue.data != 0 else default +} + fun Context.resolveThemeColor(attributeId: Int, default: Int = 0): Int { val outValue = TypedValue() val wasResolved = theme.resolveAttribute(attributeId, outValue, true) @@ -58,11 +63,9 @@ fun Context.makeToast(text: String, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, text, duration).show() } -fun Activity.dismissKeyboard() { - window.currentFocus?.let { focus -> - val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(focus.windowToken, 0) +fun Context.isInstalled(packageName: String): Boolean { + return tryOrNull(false) { packageManager.getApplicationInfo(packageName, 0).enabled } ?: false +} - focus.clearFocus() - } -} \ No newline at end of file +val Context.versionCode: Int + get() = packageManager.getPackageInfo(packageName, 0).versionCode diff --git a/data/src/main/java/com/moez/QKSMS/util/Utils.kt b/common/src/main/java/com/moez/QKSMS/util/Utils.kt similarity index 93% rename from data/src/main/java/com/moez/QKSMS/util/Utils.kt rename to common/src/main/java/com/moez/QKSMS/util/Utils.kt index bea9540071e2a3a42b641343ec205de1254bdea4..5d6fd155ae8822ebae23b08b45ea170648d9a154 100644 --- a/data/src/main/java/com/moez/QKSMS/util/Utils.kt +++ b/common/src/main/java/com/moez/QKSMS/util/Utils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Moez Bhatti + * Copyright (C) 2019 Moez Bhatti * * This file is part of QKSMS. * @@ -30,4 +30,4 @@ fun tryOrNull(logOnError: Boolean = true, body: () -> T?): T? { null } -} \ No newline at end of file +} diff --git a/data/build.gradle b/data/build.gradle index fa088a5539539f181bf79b05bb7562434e10821d..e46b690bfdac50aaa03711b56655fd324dedb55c 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -21,13 +21,18 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 28 + compileSdkVersion 29 publishNonDefault true flavorDimensions "analytics" + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + defaultConfig { minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "AMPLITUDE_API_KEY", "\"${System.getenv("AMPLITUDE_API_KEY")}\"" @@ -72,10 +77,14 @@ dependencies { testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-core:$mockito_version" + implementation 'com.callcontrol:datashare:1.2.0' implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.jakewharton.timber:timber:$timber_version" implementation "com.squareup.moshi:moshi:$moshi_version" + implementation "com.squareup.okhttp3:okhttp:$okhttp3_version" + implementation 'io.michaelrocks:libphonenumber-android:8.10.16' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation project(":android-smsmms") implementation project(':common') implementation project(':domain') diff --git a/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..23d9e758600ae1c525df1a3390367fb74402289e --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/blocking/BlockingManager.kt @@ -0,0 +1,39 @@ +package com.moez.QKSMS.blocking + +import com.moez.QKSMS.util.Preferences +import io.reactivex.Completable +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Delegates requests to the active blocking client + */ +@Singleton +class BlockingManager @Inject constructor( + private val prefs: Preferences, + private val callControlBlockingClient: CallControlBlockingClient, + private val qksmsBlockingClient: QksmsBlockingClient, + private val shouldIAnswerBlockingClient: ShouldIAnswerBlockingClient +) : BlockingClient { + + private val client: BlockingClient + get() = when (prefs.blockingManager.get()) { + Preferences.BLOCKING_MANAGER_SIA -> shouldIAnswerBlockingClient + Preferences.BLOCKING_MANAGER_CC -> callControlBlockingClient + else -> qksmsBlockingClient + } + + override fun isAvailable(): Boolean = client.isAvailable() + + override fun getClientCapability(): BlockingClient.Capability = client.getClientCapability() + + override fun getAction(address: String): Single = client.getAction(address) + + override fun block(addresses: List): Completable = client.block(addresses) + + override fun unblock(addresses: List): Completable = client.unblock(addresses) + + override fun openSettings() = client.openSettings() + +} diff --git a/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..ada74816c73cf167bf01ea945af384459270e9a4 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/blocking/CallControlBlockingClient.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.blocking + +import android.content.Context +import android.content.Intent +import android.database.Cursor +import android.net.Uri +import androidx.core.database.getStringOrNull +import com.callcontrol.datashare.CallControl +import com.moez.QKSMS.common.util.extensions.isInstalled +import com.moez.QKSMS.extensions.map +import com.moez.QKSMS.util.tryOrNull +import io.reactivex.Completable +import io.reactivex.Single +import javax.inject.Inject + +class CallControlBlockingClient @Inject constructor( + private val context: Context +) : BlockingClient { + + private val projection: Array = arrayOf( + //CallControl.Lookup.DISPLAY_NAME, // This has a performance impact on the lookup, and we don't need it + CallControl.Lookup.BLOCK_REASON + ) + + class LookupResult(cursor: Cursor) { + val blockReason: String? = cursor.getStringOrNull(0) + } + + override fun isAvailable(): Boolean = context.isInstalled("com.flexaspect.android.everycallcontrol") + + override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITH_PERMISSION + + override fun getAction(address: String): Single = Single.fromCallable { + val uri = Uri.withAppendedPath(CallControl.LOOKUP_TEXT_URI, address) + val blockReason = tryOrNull { + context.contentResolver.query(uri, projection, null, null, null) // Query URI + ?.use { cursor -> cursor.map(::LookupResult) } // Map to Result object + ?.find { result -> result.blockReason != null } // Check if any are blocked + }?.blockReason // If none are blocked or we errored at some point, return false + + when (blockReason) { + null -> BlockingClient.Action.Unblock + else -> BlockingClient.Action.Block(blockReason) + } + } + + override fun block(addresses: List): Completable = Completable.fromCallable { + val reports = addresses.map { CallControl.Report(it) } + val reportsArrayList = arrayListOf().apply { addAll(reports) } + CallControl.addRule(context, reportsArrayList, Intent.FLAG_ACTIVITY_NEW_TASK) + } + + override fun unblock(addresses: List): Completable = Completable.fromCallable { + val reports = addresses.map { CallControl.Report(it, null, false) } + val reportsArrayList = arrayListOf().apply { addAll(reports) } + CallControl.addRule(context, reportsArrayList, Intent.FLAG_ACTIVITY_NEW_TASK) + } + + override fun openSettings() { + CallControl.openBlockedList(context, Intent.FLAG_ACTIVITY_NEW_TASK) + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..33be2b182eebe92f093d5df227a974d650984576 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/blocking/QksmsBlockingClient.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.blocking + +import com.moez.QKSMS.repository.BlockingRepository +import io.reactivex.Completable +import io.reactivex.Single +import javax.inject.Inject + +class QksmsBlockingClient @Inject constructor( + private val blockingRepo: BlockingRepository +) : BlockingClient { + + override fun isAvailable(): Boolean = true + + override fun getClientCapability() = BlockingClient.Capability.BLOCK_WITHOUT_PERMISSION + + override fun getAction(address: String): Single = Single.fromCallable { + when (blockingRepo.isBlocked(address)) { + true -> BlockingClient.Action.Block() + false -> BlockingClient.Action.Unblock + } + } + + override fun block(addresses: List): Completable = Completable.fromCallable { + blockingRepo.blockNumber(*addresses.toTypedArray()) + } + + override fun unblock(addresses: List): Completable = Completable.fromCallable { + blockingRepo.unblockNumbers(*addresses.toTypedArray()) + } + + override fun openSettings() = Unit // TODO: Do this here once we implement AndroidX navigation + +} diff --git a/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..bfbc65a0cc5600f61e34e9b826f01239cf87aace --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/blocking/ShouldIAnswerBlockingClient.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.blocking + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.Handler +import android.os.IBinder +import android.os.Message +import android.os.Messenger +import androidx.core.os.bundleOf +import com.moez.QKSMS.common.util.extensions.isInstalled +import com.moez.QKSMS.util.tryOrNull +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.subjects.SingleSubject +import javax.inject.Inject + +class ShouldIAnswerBlockingClient @Inject constructor( + private val context: Context +) : BlockingClient { + + companion object { + const val RATING_UNKNOWN = 0 + const val RATING_POSITIVE = 1 + const val RATING_NEGATIVE = 2 + const val RATING_NEUTRAL = 3 + + const val GET_NUMBER_RATING = 1 + } + + override fun isAvailable(): Boolean = listOf("org.mistergroup.shouldianswer", + "org.mistergroup.shouldianswerpersonal", + "org.mistergroup.muzutozvednout") + .any(context::isInstalled) + + override fun getClientCapability() = BlockingClient.Capability.CANT_BLOCK + + override fun getAction(address: String): Single { + return Binder(context, address).isBlocked() + .map { blocked -> + when (blocked) { + true -> BlockingClient.Action.Block() + false -> BlockingClient.Action.DoNothing + } + } + } + + override fun block(addresses: List): Completable = Completable.fromCallable { openSettings() } + + override fun unblock(addresses: List): Completable = Completable.fromCallable { openSettings() } + + override fun openSettings() { + val pm = context.packageManager + val intent = pm.getLaunchIntentForPackage("org.mistergroup.shouldianswer") + ?: pm.getLaunchIntentForPackage("org.mistergroup.shouldianswerpersonal") + ?: pm.getLaunchIntentForPackage("org.mistergroup.muzutozvednout") + + intent?.run(context::startActivity) + } + + private class Binder( + private val context: Context, + private val address: String + ) : ServiceConnection { + + private val subject: SingleSubject = SingleSubject.create() + private var serviceMessenger: Messenger? = null + private var isBound: Boolean = false + + fun isBlocked(): Single { + // If either version of Should I Answer? is installed and SIA is enabled, build the + // intent to request a rating + val intent: Intent? = tryOrNull(false) { + context.packageManager.getApplicationInfo("org.mistergroup.shouldianswer", 0).enabled + Intent("org.mistergroup.shouldianswer.PublicService").setPackage("org.mistergroup.shouldianswer") + } ?: tryOrNull(false) { + context.packageManager.getApplicationInfo("org.mistergroup.shouldianswerpersonal", 0).enabled + Intent("org.mistergroup.shouldianswerpersonal.PublicService") + .setPackage("org.mistergroup.shouldianswerpersonal") + } ?: tryOrNull(false) { + context.packageManager.getApplicationInfo("org.mistergroup.muzutozvednout", 0).enabled + Intent("org.mistergroup.muzutozvednout.PublicService").setPackage("org.mistergroup.muzutozvednout") + } + + // If the intent isn't null, bind the service and wait for a result. Otherwise, don't block + if (intent != null) { + context.bindService(intent, this, Context.BIND_AUTO_CREATE) + } else { + subject.onSuccess(false) + } + + return subject + } + + override fun onServiceConnected(className: ComponentName, service: IBinder) { + serviceMessenger = Messenger(service) + isBound = true + + val message = Message().apply { + what = GET_NUMBER_RATING + data = bundleOf("number" to address) + replyTo = Messenger(IncomingHandler { response -> + subject.onSuccess(response.rating == RATING_NEGATIVE || response.wantBlock) + + // We're done, so unbind the service + if (isBound && serviceMessenger != null) { + context.unbindService(this@Binder) + } + }) + } + + serviceMessenger?.send(message) + } + + override fun onServiceDisconnected(className: ComponentName) { + serviceMessenger = null + isBound = false + } + } + + private class IncomingHandler(private val callback: (response: Response) -> Unit) : Handler() { + class Response(bundle: Bundle) { + val rating: Int = bundle.getInt("rating") + val wantBlock = bundle.getInt("wantBlock") == 1 + } + + override fun handleMessage(msg: Message) { + when (msg.what) { + GET_NUMBER_RATING -> callback(Response(msg.data)) + else -> super.handleMessage(msg) + } + } + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt b/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt index a4d2135cb31bcef3d3c1aa796a3070e47468f29b..ea890c8c92deaed3d431cd4122d2bf222c186c21 100644 --- a/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt +++ b/data/src/main/java/com/moez/QKSMS/extensions/MmsPartExtensions.kt @@ -27,6 +27,6 @@ fun MmsPart.isImage() = ContentType.isImageType(type) fun MmsPart.isVideo() = ContentType.isVideoType(type) -fun MmsPart.isText() = ContentType.isTextType(type) +fun MmsPart.isText() = ContentType.TEXT_PLAIN == type -fun MmsPart.isVCard() = ContentType.TEXT_VCARD == type \ No newline at end of file +fun MmsPart.isVCard() = ContentType.TEXT_VCARD == type diff --git a/data/src/main/java/com/moez/QKSMS/extensions/StringExtensions.kt b/data/src/main/java/com/moez/QKSMS/extensions/StringExtensions.kt index 3312c78f0cbf7c5a71f1f91105658067e99946c2..1ef0a6eda6ba8f3d96e765b7fc707fba51aa0032 100644 --- a/data/src/main/java/com/moez/QKSMS/extensions/StringExtensions.kt +++ b/data/src/main/java/com/moez/QKSMS/extensions/StringExtensions.kt @@ -23,4 +23,4 @@ import java.text.Normalizer /** * Strip the accents from a string */ -fun CharSequence.removeAccents() = Normalizer.normalize(this, Normalizer.Form.NFD) +fun CharSequence.removeAccents(): String = Normalizer.normalize(this, Normalizer.Form.NFKD).replace(Regex("\\p{M}"), "") diff --git a/data/src/main/java/com/moez/QKSMS/filter/PhoneNumberFilter.kt b/data/src/main/java/com/moez/QKSMS/filter/PhoneNumberFilter.kt index 6a677939e3481b33854e6edcf36619be909425ca..470a84b6ac1fc03dd5ec9c1bbe5a1e3dc9648cd1 100644 --- a/data/src/main/java/com/moez/QKSMS/filter/PhoneNumberFilter.kt +++ b/data/src/main/java/com/moez/QKSMS/filter/PhoneNumberFilter.kt @@ -18,16 +18,17 @@ */ package com.moez.QKSMS.filter -import android.telephony.PhoneNumberUtils +import com.moez.QKSMS.util.PhoneNumberUtils import javax.inject.Inject -class PhoneNumberFilter @Inject constructor() : Filter() { +class PhoneNumberFilter @Inject constructor( + private val phoneNumberUtils: PhoneNumberUtils +) : Filter() { override fun filter(item: String, query: CharSequence): Boolean { - val allCharactersDialable = query.all { PhoneNumberUtils.isReallyDialable(it) } - - return allCharactersDialable && (PhoneNumberUtils.compare(item, query.toString()) || - PhoneNumberUtils.stripSeparators(item).contains(PhoneNumberUtils.stripSeparators(query.toString()))) + val allCharactersDialable = query.all { phoneNumberUtils.isReallyDialable(it) } + return allCharactersDialable && (phoneNumberUtils.compare(item, query.toString()) || + phoneNumberUtils.normalizeNumber(item).contains(phoneNumberUtils.normalizeNumber(query.toString()))) } } \ No newline at end of file diff --git a/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b09f6d253342f0611735b461464cbd89f43a84f --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/manager/ChangelogManagerImpl.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.manager + +import android.content.Context +import com.moez.QKSMS.common.util.extensions.versionCode +import com.moez.QKSMS.util.Preferences +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import okhttp3.Call +import okhttp3.Callback +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.IOException +import javax.inject.Inject + +class ChangelogManagerImpl @Inject constructor( + private val context: Context, + private val moshi: Moshi, + private val prefs: Preferences +) : ChangelogManager { + + override fun didUpdate(): Boolean = prefs.changelogVersion.get() != context.versionCode + + override fun getChangelog(): Single { + val url = "https://firestore.googleapis.com/v1/projects/qksms-app/databases/(default)/documents/changelog" + val request = url.toHttpUrlOrNull()?.let { Request.Builder().url(it).build() } + val call = request?.let { OkHttpClient().newCall(it) } + val adapter = moshi.adapter(ChangelogResponse::class.java) + + return Single + .create { emitter -> + call?.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (!emitter.isDisposed) { + emitter.onSuccess(response) + } + } + + override fun onFailure(call: Call, e: IOException) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + }) + emitter.setCancellable { + call?.cancel() + } + } + .map { response -> response.body?.string()?.let(adapter::fromJson) } + .map { response -> + response.documents + .sortedBy { document -> document.fields.versionCode.value } + .filter { document -> + val range = (prefs.changelogVersion.get() + 1)..context.versionCode + document.fields.versionCode.value.toInt() in range + } + } + .map { documents -> + val added = documents.fold(listOf()) { acc, document -> + acc + document.fields.added?.value?.values?.map { value -> value.value }.orEmpty() + } + val improved = documents.fold(listOf()) { acc, document -> + acc + document.fields.improved?.value?.values?.map { value -> value.value }.orEmpty() + } + val fixed = documents.fold(listOf()) { acc, document -> + acc + document.fields.fixed?.value?.values?.map { value -> value.value }.orEmpty() + } + ChangelogManager.Changelog(added, improved, fixed) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + } + + override fun markChangelogSeen() { + prefs.changelogVersion.set(context.versionCode) + } + + @JsonClass(generateAdapter = true) + data class ChangelogResponse( + @Json(name = "documents") val documents: List + ) + + @JsonClass(generateAdapter = true) + data class Document( + @Json(name = "fields") val fields: Changelog + ) + + @JsonClass(generateAdapter = true) + data class Changelog( + @Json(name = "added") val added: ArrayField?, + @Json(name = "improved") val improved: ArrayField?, + @Json(name = "fixed") val fixed: ArrayField?, + @Json(name = "versionName") val versionName: StringField, + @Json(name = "versionCode") val versionCode: IntegerField + ) + + @JsonClass(generateAdapter = true) + data class ArrayField( + @Json(name = "arrayValue") val value: ArrayValues + ) + + @JsonClass(generateAdapter = true) + data class ArrayValues( + @Json(name = "values") val values: List + ) + + @JsonClass(generateAdapter = true) + data class StringField( + @Json(name = "stringValue") val value: String + ) + + @JsonClass(generateAdapter = true) + data class IntegerField( + @Json(name = "integerValue") val value: String + ) + +} diff --git a/data/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManagerImpl.kt deleted file mode 100644 index ebf56d1c7687548937b402b7ac689c1d83bb3569..0000000000000000000000000000000000000000 --- a/data/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManagerImpl.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.manager - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.os.Handler -import android.os.IBinder -import android.os.Message -import android.os.Messenger -import androidx.core.os.bundleOf -import com.moez.QKSMS.util.Preferences -import com.moez.QKSMS.util.tryOrNull -import io.reactivex.Single -import io.reactivex.subjects.SingleSubject -import timber.log.Timber -import javax.inject.Inject - -class ExternalBlockingManagerImpl @Inject constructor( - private val context: Context, - private val prefs: Preferences -) : ExternalBlockingManager { - - companion object { - const val RATING_UNKNOWN = 0 - const val RATING_POSITIVE = 1 - const val RATING_NEGATIVE = 2 - const val RATING_NEUTRAL = 3 - - const val GET_NUMBER_RATING = 1 - } - - /** - * Return a Single which emits whether or not the given [address] should be blocked - */ - override fun shouldBlock(address: String): Single { - return Binder(context, prefs, address).shouldBlock() - } - - private class Binder( - private val context: Context, - private val prefs: Preferences, - private val address: String - ) : ServiceConnection { - - private val subject: SingleSubject = SingleSubject.create() - private var serviceMessenger: Messenger? = null - private var isBound: Boolean = false - - fun shouldBlock(): Single { - - var intent: Intent? = null - - // If either version of Should I Answer? is installed and SIA is enabled, build the - // intent to request a rating - if (prefs.sia.get()) { - intent = tryOrNull(false) { - context.packageManager.getApplicationInfo("org.mistergroup.shouldianswerpersonal", 0).enabled - Intent("org.mistergroup.shouldianswerpersonal.PublicService") - .setPackage("org.mistergroup.shouldianswerpersonal") - } ?: tryOrNull(false) { - context.packageManager.getApplicationInfo("org.mistergroup.muzutozvednout", 0).enabled - Intent("org.mistergroup.muzutozvednout.PublicService").setPackage("org.mistergroup.muzutozvednout") - } - } - - // If the intent isn't null, bind the service and wait for a result. Otherwise, don't block - if (intent != null) { - context.bindService(intent, this, Context.BIND_AUTO_CREATE) - } else { - subject.onSuccess(false) - } - - return subject - } - - override fun onServiceConnected(className: ComponentName, service: IBinder) { - serviceMessenger = Messenger(service) - isBound = true - - val msg = Message() - msg.what = GET_NUMBER_RATING - msg.data = bundleOf(Pair("number", address)) - msg.replyTo = Messenger(IncomingHandler { rating -> - subject.onSuccess(rating == RATING_NEGATIVE) - Timber.v("Should block: ${rating == RATING_NEGATIVE}") - - // We're done, so unbind the service - if (isBound && serviceMessenger != null) { - context.unbindService(this) - } - }) - - serviceMessenger?.send(msg) - } - - override fun onServiceDisconnected(className: ComponentName) { - serviceMessenger = null - isBound = false - } - } - - private class IncomingHandler(private val callback: (rating: Int) -> Unit) : Handler() { - - override fun handleMessage(msg: Message) { - when (msg.what) { - GET_NUMBER_RATING -> callback(msg.data.getInt("rating")) - else -> super.handleMessage(msg) - } - } - } - -} diff --git a/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt b/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt index 5ee266c4d7d286063bbd15281e3dfea320e105f4..f910428c93097f18a1591c12cf3d88fc1a21085f 100644 --- a/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/manager/PermissionManagerImpl.kt @@ -19,8 +19,10 @@ package com.moez.QKSMS.manager import android.Manifest +import android.app.role.RoleManager import android.content.Context import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Build import android.provider.Telephony import androidx.core.content.ContextCompat import javax.inject.Inject @@ -28,7 +30,11 @@ import javax.inject.Inject class PermissionManagerImpl @Inject constructor(private val context: Context) : PermissionManager { override fun isDefaultSms(): Boolean { - return Telephony.Sms.getDefaultSmsPackage(context) == context.packageName + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + context.getSystemService(RoleManager::class.java)?.isRoleHeld(RoleManager.ROLE_SMS) == true + } else { + Telephony.Sms.getDefaultSmsPackage(context) == context.packageName + } } override fun hasReadSms(): Boolean { diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt index fa2fccd266d32ba2744c6a087e9cf9215f1e2b9e..82d50ca9be278d6a16b210227ea657833b5d4f35 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToContactImpl.kt @@ -20,7 +20,7 @@ package com.moez.QKSMS.mapper import android.content.Context import android.database.Cursor -import android.provider.ContactsContract.CommonDataKinds.Phone.* +import android.provider.ContactsContract.CommonDataKinds.Phone import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.PhoneNumber @@ -32,23 +32,33 @@ class CursorToContactImpl @Inject constructor( ) : CursorToContact { companion object { - val URI = CONTENT_URI - val PROJECTION = arrayOf(LOOKUP_KEY, NUMBER, TYPE, DISPLAY_NAME) + val URI = Phone.CONTENT_URI + val PROJECTION = arrayOf( + Phone.LOOKUP_KEY, + Phone.NUMBER, + Phone.TYPE, + Phone.LABEL, + Phone.DISPLAY_NAME, + Phone.CONTACT_LAST_UPDATED_TIMESTAMP + ) const val COLUMN_LOOKUP_KEY = 0 const val COLUMN_NUMBER = 1 const val COLUMN_TYPE = 2 - const val COLUMN_DISPLAY_NAME = 3 + const val COLUMN_LABEL = 3 + const val COLUMN_DISPLAY_NAME = 4 + const val CONTACT_LAST_UPDATED = 5 } override fun map(from: Cursor) = Contact().apply { lookupKey = from.getString(COLUMN_LOOKUP_KEY) name = from.getString(COLUMN_DISPLAY_NAME) ?: "" - numbers.add(PhoneNumber().apply { - address = from.getString(COLUMN_NUMBER) ?: "" - type = context.getString(getTypeLabelResource(from.getInt(COLUMN_TYPE))) - }) - lastUpdate = System.currentTimeMillis() + numbers.add(PhoneNumber( + address = from.getString(COLUMN_NUMBER) ?: "", + type = Phone.getTypeLabel(context.resources, from.getInt(COLUMN_TYPE), + from.getString(COLUMN_LABEL)).toString() + )) + lastUpdate = from.getLong(CONTACT_LAST_UPDATED) } override fun getContactsCursor(): Cursor? { diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToConversationImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToConversationImpl.kt index 9ee32c36bcc940db14728e822e46565d7979032d..304a451741f25bc49be71dca5823da00ca16d650 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToConversationImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToConversationImpl.kt @@ -66,9 +66,9 @@ class CursorToConversationImpl @Inject constructor( } } - override fun getConversationsCursor(lastSync: Long): Cursor? { + override fun getConversationsCursor(): Cursor? { return when (permissionManager.hasReadSms()) { - true -> context.contentResolver.query(URI, PROJECTION, "date > $lastSync", null, "date desc") + true -> context.contentResolver.query(URI, PROJECTION, null, null, "date desc") false -> null } } diff --git a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt index a6da71a74c5ab54facf81a6c1db7c2e8a443487d..7624f4cbf57be4bf181f295b77c36acc633b7be6 100644 --- a/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/mapper/CursorToPartImpl.kt @@ -18,20 +18,13 @@ */ package com.moez.QKSMS.mapper -import android.content.ContentUris import android.content.Context import android.database.Cursor import android.net.Uri import android.provider.Telephony -import com.moez.QKSMS.extensions.isImage -import com.moez.QKSMS.extensions.isSmil -import com.moez.QKSMS.extensions.isText -import com.moez.QKSMS.extensions.isVideo +import androidx.core.database.getIntOrNull +import androidx.core.database.getStringOrNull import com.moez.QKSMS.model.MmsPart -import com.moez.QKSMS.util.tryOrNull -import timber.log.Timber -import java.io.BufferedReader -import java.io.InputStreamReader import javax.inject.Inject class CursorToPartImpl @Inject constructor(private val context: Context) : CursorToPart { @@ -42,41 +35,12 @@ class CursorToPartImpl @Inject constructor(private val context: Context) : Curso override fun map(from: Cursor) = MmsPart().apply { id = from.getLong(from.getColumnIndexOrThrow(Telephony.Mms.Part._ID)) - - // Type will sometimes return null, resulting in a crash if we don't default to an empty string - type = from.getString(from.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE)) ?: "" - - val data = from.getString(from.getColumnIndexOrThrow(Telephony.Mms.Part._DATA)) - - when { - isSmil() || isImage() || isVideo() -> { - // Do nothing special - } - - isText() -> { - text = if (data == null) { - from.getString(from.getColumnIndexOrThrow("text")) - } else { - val uri = ContentUris.withAppendedId(CONTENT_URI, id) - val sb = StringBuilder() - val inputStream = tryOrNull(false) { context.contentResolver.openInputStream(uri) } - - inputStream?.use { - val isr = InputStreamReader(inputStream, "UTF-8") - val reader = BufferedReader(isr) - var temp = reader.readLine() - while (temp != null) { - sb.append(temp) - temp = reader.readLine() - } - } - - sb.toString() - } - } - - else -> Timber.v("Unhandled type: $type") - } + type = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE)) ?: "*/*" + seq = from.getIntOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.SEQ)) ?: -1 + name = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.NAME)) + ?: from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_LOCATION)) + ?.split("/")?.last() + text = from.getStringOrNull(from.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT)) } override fun getPartsCursor(messageId: Long): Cursor? { diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt new file mode 100644 index 0000000000000000000000000000000000000000..32e192d389c1000366ae2627605d8f3e7a036374 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/migration/QkMigration.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.migration + +import android.content.Context +import com.moez.QKSMS.blocking.QksmsBlockingClient +import com.moez.QKSMS.common.util.extensions.versionCode +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.util.Preferences +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +class QkMigration @Inject constructor( + context: Context, + private val conversationRepo: ConversationRepository, + private val prefs: Preferences, + private val qksmsBlockingClient: QksmsBlockingClient +) { + + init { + GlobalScope.launch { + val oldVersion = prefs.version.get() + + if (oldVersion < 2199) { + upgradeTo370() + } + + prefs.version.set(context.versionCode) + } + } + + private fun upgradeTo370() { + // Migrate changelog version + prefs.changelogVersion.set(prefs.version.get()) + + // Migrate from old SIA preference to blocking manager preference + if (prefs.sia.get()) { + prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_SIA) + prefs.sia.delete() + } + + // Migrate blocked conversations into QK blocking client + val addresses = conversationRepo.getBlockedConversations() + .flatMap { conversation -> conversation.recipients } + .map { recipient -> recipient.address } + .distinct() + + qksmsBlockingClient.block(addresses).blockingAwait() + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt index d9e87acf50f831bbfeaf659b54aca49cf8c8e2d2..bd57a451097b46b07dccc765e04d170b37c37f19 100644 --- a/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt +++ b/data/src/main/java/com/moez/QKSMS/migration/QkRealmMigration.kt @@ -25,7 +25,7 @@ import io.realm.RealmMigration class QkRealmMigration : RealmMigration { companion object { - const val SCHEMA_VERSION: Long = 5 + const val SCHEMA_VERSION: Long = 7 } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -72,9 +72,29 @@ class QkRealmMigration : RealmMigration { version++ } + if (version == 5L) { + realm.schema.create("BlockedNumber") + .addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED) + .addField("address", String::class.java, FieldAttribute.REQUIRED) + + version++ + } + + if (version == 6L) { + realm.schema.get("Conversation") + ?.addField("blockingClient", Integer::class.java) + ?.addField("blockReason", String::class.java) + + realm.schema.get("MmsPart") + ?.addField("seq", Integer::class.java, FieldAttribute.REQUIRED) + ?.addField("name", String::class.java) + + version++ + } + if (version < newVersion) { throw IllegalStateException("Migration missing from v$oldVersion to v$newVersion") } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/receiver/MmsReceivedReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/MmsReceivedReceiver.kt index fecf096a67d63c92407632bd16f7594c1ddfc9fb..9e340b084346e5d51905357a711f391af6404771 100644 --- a/data/src/main/java/com/moez/QKSMS/receiver/MmsReceivedReceiver.kt +++ b/data/src/main/java/com/moez/QKSMS/receiver/MmsReceivedReceiver.kt @@ -42,4 +42,4 @@ class MmsReceivedReceiver : MmsReceivedReceiver() { } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/receiver/SendScheduledMessageReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/SendScheduledMessageReceiver.kt index eac9d68ef02ea888a50c437cf84b066fc394b8ef..dcd2f3938634ed20ff42a22474957ed994fac3b3 100644 --- a/data/src/main/java/com/moez/QKSMS/receiver/SendScheduledMessageReceiver.kt +++ b/data/src/main/java/com/moez/QKSMS/receiver/SendScheduledMessageReceiver.kt @@ -35,7 +35,8 @@ class SendScheduledMessageReceiver : BroadcastReceiver() { AndroidInjection.inject(this, context) intent.getLongExtra("id", -1L).takeIf { it >= 0 }?.let { id -> - sendScheduledMessage.buildObservable(id).blockingSubscribe() + val result = goAsync() + sendScheduledMessage.execute(id) { result.finish() } } } diff --git a/data/src/main/java/com/moez/QKSMS/service/SendSmsService.kt b/data/src/main/java/com/moez/QKSMS/receiver/SendSmsReceiver.kt similarity index 65% rename from data/src/main/java/com/moez/QKSMS/service/SendSmsService.kt rename to data/src/main/java/com/moez/QKSMS/receiver/SendSmsReceiver.kt index 6838ddee51ce2a54374437c932a58c6729ddf03d..5ac2aefc79b32d5e5366f12f912e1a0d82f3b6a9 100644 --- a/data/src/main/java/com/moez/QKSMS/service/SendSmsService.kt +++ b/data/src/main/java/com/moez/QKSMS/receiver/SendSmsReceiver.kt @@ -16,27 +16,31 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.service +package com.moez.QKSMS.receiver -import android.app.IntentService +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent import com.moez.QKSMS.interactor.RetrySending import com.moez.QKSMS.repository.MessageRepository import dagger.android.AndroidInjection import javax.inject.Inject -class SendSmsService : IntentService(null) { +class SendSmsReceiver : BroadcastReceiver() { @Inject lateinit var messageRepo: MessageRepository @Inject lateinit var retrySending: RetrySending - override fun onHandleIntent(intent: Intent) { - AndroidInjection.inject(this) + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) - val id = intent.getLongExtra("id", 0L) - messageRepo.getMessage(id)?.let { message -> - retrySending.buildObservable(message).blockingSubscribe() - } + intent.getLongExtra("id", -1L) + .takeIf { it >= 0 } + ?.let(messageRepo::getMessage) + ?.let { message -> + val result = goAsync() + retrySending.execute(message) { result.finish() } + } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/receiver/SmsDeliveredReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/SmsDeliveredReceiver.kt index e03bcdc0920239fe3909bae93529d7c6c06af6e4..83382c31973b210ec0cfe4285e52175c2014f918 100644 --- a/data/src/main/java/com/moez/QKSMS/receiver/SmsDeliveredReceiver.kt +++ b/data/src/main/java/com/moez/QKSMS/receiver/SmsDeliveredReceiver.kt @@ -29,16 +29,11 @@ import javax.inject.Inject class SmsDeliveredReceiver : BroadcastReceiver() { - companion object { - const val ACTION = "com.moez.QKSMS.SMS_DELIVERED" - } - @Inject lateinit var markDelivered: MarkDelivered @Inject lateinit var markDeliveryFailed: MarkDeliveryFailed override fun onReceive(context: Context, intent: Intent) { AndroidInjection.inject(this, context) - context.unregisterReceiver(this) val id = intent.getLongExtra("id", 0L) @@ -57,4 +52,4 @@ class SmsDeliveredReceiver : BroadcastReceiver() { } } -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/receiver/SmsSentReceiver.kt b/data/src/main/java/com/moez/QKSMS/receiver/SmsSentReceiver.kt index 1777dc7f9321f631d0001e8a8fd625dc77aac8f9..077e9f338fae15b53b41971c4d4579a27ffc83e8 100644 --- a/data/src/main/java/com/moez/QKSMS/receiver/SmsSentReceiver.kt +++ b/data/src/main/java/com/moez/QKSMS/receiver/SmsSentReceiver.kt @@ -22,7 +22,6 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.telephony.SmsManager import com.moez.QKSMS.interactor.MarkFailed import com.moez.QKSMS.interactor.MarkSent import dagger.android.AndroidInjection @@ -30,16 +29,11 @@ import javax.inject.Inject class SmsSentReceiver : BroadcastReceiver() { - companion object { - const val ACTION = "com.moez.QKSMS.SMS_SENT" - } - @Inject lateinit var markSent: MarkSent @Inject lateinit var markFailed: MarkFailed override fun onReceive(context: Context, intent: Intent) { AndroidInjection.inject(this, context) - context.unregisterReceiver(this) val id = intent.getLongExtra("id", 0L) @@ -49,13 +43,11 @@ class SmsSentReceiver : BroadcastReceiver() { markSent.execute(id) { pendingResult.finish() } } - SmsManager.RESULT_ERROR_GENERIC_FAILURE, - SmsManager.RESULT_ERROR_NO_SERVICE, - SmsManager.RESULT_ERROR_NULL_PDU, - SmsManager.RESULT_ERROR_RADIO_OFF -> { + else -> { val pendingResult = goAsync() markFailed.execute(MarkFailed.Params(id, resultCode)) { pendingResult.finish() } } } } -} \ No newline at end of file + +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt index 62cf7b56370e30f9e0f9c1a9de125f1fc9b2de4f..9fff3db354739212f4dbe88364bd9dac58b5d957 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt @@ -24,6 +24,7 @@ import android.provider.Telephony import androidx.core.content.contentValuesOf import com.moez.QKSMS.model.BackupFile import com.moez.QKSMS.model.Message +import com.moez.QKSMS.util.Preferences import com.moez.QKSMS.util.QkFileObserver import com.moez.QKSMS.util.tryOrNull import com.squareup.moshi.Moshi @@ -32,7 +33,8 @@ import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import io.realm.Realm -import okio.Okio +import okio.buffer +import okio.source import timber.log.Timber import java.io.File import java.io.FileOutputStream @@ -46,6 +48,7 @@ import kotlin.concurrent.schedule class BackupRepositoryImpl @Inject constructor( private val context: Context, private val moshi: Moshi, + private val prefs: Preferences, private val syncRepo: SyncRepository ) : BackupRepository { @@ -156,7 +159,7 @@ class BackupRepositoryImpl @Inject constructor( files.mapNotNull { file -> val adapter = moshi.adapter(BackupMetadata::class.java) val backup = tryOrNull(false) { - Okio.buffer(Okio.source(file)).use(adapter::fromJson) + file.source().buffer().use(adapter::fromJson) } ?: return@mapNotNull null val path = file.path @@ -175,7 +178,7 @@ class BackupRepositoryImpl @Inject constructor( restoreProgress.onNext(BackupRepository.Progress.Parsing()) val file = File(filePath) - val backup = Okio.buffer(Okio.source(file)).use { source -> + val backup = file.source().buffer().use { source -> moshi.adapter(Backup::class.java).fromJson(source) } @@ -193,7 +196,7 @@ class BackupRepositoryImpl @Inject constructor( restoreProgress.onNext(BackupRepository.Progress.Running(messageCount, index)) try { - context.contentResolver.insert(Telephony.Sms.CONTENT_URI, contentValuesOf( + val values = contentValuesOf( Telephony.Sms.TYPE to message.type, Telephony.Sms.ADDRESS to message.address, Telephony.Sms.DATE to message.date, @@ -204,9 +207,14 @@ class BackupRepositoryImpl @Inject constructor( Telephony.Sms.BODY to message.body, Telephony.Sms.PROTOCOL to message.protocol, Telephony.Sms.SERVICE_CENTER to message.serviceCenter, - Telephony.Sms.LOCKED to message.locked, - Telephony.Sms.SUBSCRIPTION_ID to message.subId) + Telephony.Sms.LOCKED to message.locked ) + + if (prefs.canUseSubId.get()) { + values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId) + } + + context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values) } catch (e: Exception) { Timber.w(e) errorCount++ diff --git a/data/src/main/java/com/moez/QKSMS/repository/BlockingRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/BlockingRepositoryImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..adc2cb1eea5240c4a6397d08e630384659e24494 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/repository/BlockingRepositoryImpl.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.repository + +import com.moez.QKSMS.extensions.anyOf +import com.moez.QKSMS.model.BlockedNumber +import com.moez.QKSMS.util.PhoneNumberUtils +import io.realm.Realm +import io.realm.RealmResults +import javax.inject.Inject + +class BlockingRepositoryImpl @Inject constructor( + private val phoneNumberUtils: PhoneNumberUtils +) : BlockingRepository { + + override fun blockNumber(vararg addresses: String) { + Realm.getDefaultInstance().use { realm -> + val blockedNumbers = realm.where(BlockedNumber::class.java).findAll() + val newAddresses = addresses.filter { address -> + blockedNumbers.none { number -> phoneNumberUtils.compare(number.address, address) } + } + + val maxId = realm.where(BlockedNumber::class.java) + .max("id")?.toLong() ?: -1 + + realm.executeTransaction { + realm.insert(newAddresses.mapIndexed { index, address -> BlockedNumber(maxId + 1 + index, address) }) + } + } + } + + override fun getBlockedNumbers(): RealmResults { + return Realm.getDefaultInstance() + .where(BlockedNumber::class.java) + .findAllAsync() + } + + override fun getBlockedNumber(id: Long): BlockedNumber? { + return Realm.getDefaultInstance() + .where(BlockedNumber::class.java) + .equalTo("id", id) + .findFirst() + } + + override fun isBlocked(address: String): Boolean { + return Realm.getDefaultInstance().use { realm -> + realm.where(BlockedNumber::class.java) + .findAll() + .any { number -> phoneNumberUtils.compare(number.address, address) } + } + } + + override fun unblockNumber(id: Long) { + Realm.getDefaultInstance().use { realm -> + realm.executeTransaction { + realm.where(BlockedNumber::class.java) + .equalTo("id", id) + .findAll() + .deleteAllFromRealm() + } + } + } + + override fun unblockNumbers(vararg addresses: String) { + Realm.getDefaultInstance().use { realm -> + val ids = realm.where(BlockedNumber::class.java) + .findAll() + .filter { number -> + addresses.any { address -> phoneNumberUtils.compare(number.address, address) } + } + .map { number -> number.id } + .toLongArray() + + realm.executeTransaction { + realm.where(BlockedNumber::class.java) + .anyOf("id", ids) + .findAll() + .deleteAllFromRealm() + } + } + } + +} diff --git a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt index f36e001c3181b7a86d4c8277053d9992c67f447b..309278f1c0505b943719315871f6f3080aa30df1 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ConversationRepositoryImpl.kt @@ -21,17 +21,19 @@ package com.moez.QKSMS.repository import android.content.ContentUris import android.content.Context import android.provider.Telephony -import android.telephony.PhoneNumberUtils import com.moez.QKSMS.compat.TelephonyCompat import com.moez.QKSMS.extensions.anyOf import com.moez.QKSMS.extensions.map +import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.filter.ConversationFilter import com.moez.QKSMS.mapper.CursorToConversation import com.moez.QKSMS.mapper.CursorToRecipient import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message +import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SearchResult +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.tryOrNull import io.realm.Case import io.realm.Realm @@ -44,7 +46,8 @@ class ConversationRepositoryImpl @Inject constructor( private val context: Context, private val conversationFilter: ConversationFilter, private val cursorToConversation: CursorToConversation, - private val cursorToRecipient: CursorToRecipient + private val cursorToRecipient: CursorToRecipient, + private val phoneNumberUtils: PhoneNumberUtils ) : ConversationRepository { override fun getConversations(archived: Boolean): RealmResults { @@ -60,17 +63,17 @@ class ConversationRepositoryImpl @Inject constructor( } override fun getConversationsSnapshot(): List { - val realm = Realm.getDefaultInstance() - return realm.copyFromRealm( - realm.where(Conversation::class.java) - .notEqualTo("id", 0L) - .greaterThan("count", 0) - .equalTo("archived", false) - .equalTo("blocked", false) - .isNotEmpty("recipients") - .sort("pinned", Sort.DESCENDING, "date", Sort.DESCENDING) - .findAll() - ) + return Realm.getDefaultInstance().use { realm -> + realm.refresh() + realm.copyFromRealm(realm.where(Conversation::class.java) + .notEqualTo("id", 0L) + .greaterThan("count", 0) + .equalTo("archived", false) + .equalTo("blocked", false) + .isNotEmpty("recipients") + .sort("pinned", Sort.DESCENDING, "date", Sort.DESCENDING) + .findAll()) + } } override fun getTopConversations(): List { @@ -99,26 +102,40 @@ class ConversationRepositoryImpl @Inject constructor( } } - override fun searchConversations(query: String): List { + override fun searchConversations(query: CharSequence): List { + val normalizedQuery = query.removeAccents() val conversations = getConversationsSnapshot() val messagesByConversation = Realm.getDefaultInstance() .where(Message::class.java) - .contains("body", query, Case.INSENSITIVE) + .beginGroup() + .contains("body", normalizedQuery, Case.INSENSITIVE) + .or() + .contains("parts.text", normalizedQuery, Case.INSENSITIVE) + .endGroup() .findAll() + .asSequence() .groupBy { message -> message.threadId } .filter { (threadId, _) -> conversations.firstOrNull { it.id == threadId } != null } .map { (threadId, messages) -> Pair(conversations.first { it.id == threadId }, messages.size) } - .map { (conversation, messages) -> SearchResult(query, conversation, messages) } + .map { (conversation, messages) -> SearchResult(normalizedQuery, conversation, messages) } .sortedByDescending { result -> result.messages } + .toList() return conversations - .filter { conversation -> conversationFilter.filter(conversation, query) } - .map { conversation -> SearchResult(query, conversation, 0) } + .filter { conversation -> conversationFilter.filter(conversation, normalizedQuery) } + .map { conversation -> SearchResult(normalizedQuery, conversation, 0) } .plus(messagesByConversation) } override fun getBlockedConversations(): RealmResults { + return Realm.getDefaultInstance() + .where(Conversation::class.java) + .equalTo("blocked", true) + .findAll() + } + + override fun getBlockedConversationsAsync(): RealmResults { return Realm.getDefaultInstance() .where(Conversation::class.java) .equalTo("blocked", true) @@ -139,6 +156,20 @@ class ConversationRepositoryImpl @Inject constructor( .findFirst() } + override fun getConversations(vararg threadIds: Long): RealmResults { + return Realm.getDefaultInstance() + .where(Conversation::class.java) + .anyOf("id", threadIds) + .findAll() + } + + override fun getRecipient(recipientId: Long): Recipient? { + return Realm.getDefaultInstance() + .where(Recipient::class.java) + .equalTo("id", recipientId) + .findFirst() + } + override fun getThreadId(recipient: String): Long? { return getThreadId(listOf(recipient)) } @@ -147,12 +178,13 @@ class ConversationRepositoryImpl @Inject constructor( return Realm.getDefaultInstance().use { realm -> realm.where(Conversation::class.java) .findAll() - .firstOrNull { conversation -> - conversation.recipients.size == recipients.size && conversation.recipients.map { it.address }.all { address -> - recipients.any { recipient -> PhoneNumberUtils.compare(recipient, address) } + .asSequence() + .filter { conversation -> conversation.recipients.size == recipients.size } + .find { conversation -> + conversation.recipients.map { it.address }.all { address -> + recipients.any { recipient -> phoneNumberUtils.compare(recipient, address) } } - } - ?.id + }?.id } } @@ -165,13 +197,16 @@ class ConversationRepositoryImpl @Inject constructor( } override fun getOrCreateConversation(addresses: List): Conversation? { - return tryOrNull { TelephonyCompat.getOrCreateThreadId(context, addresses.toSet()) } + if (addresses.isEmpty()) { + return null + } + + return (getThreadId(addresses) ?: tryOrNull { TelephonyCompat.getOrCreateThreadId(context, addresses.toSet()) }) ?.takeIf { threadId -> threadId != 0L } ?.let { threadId -> - var conversation = getConversation(threadId) - if (conversation != null) conversation = Realm.getDefaultInstance().copyFromRealm(conversation) - - conversation ?: getConversationFromCp(threadId) + getConversation(threadId) + ?.let(Realm.getDefaultInstance()::copyFromRealm) + ?: getConversationFromCp(threadId) } } @@ -266,14 +301,19 @@ class ConversationRepositoryImpl @Inject constructor( } } - override fun markBlocked(vararg threadIds: Long) { + override fun markBlocked(threadIds: List, blockingClient: Int, blockReason: String?) { Realm.getDefaultInstance().use { realm -> val conversations = realm.where(Conversation::class.java) - .anyOf("id", threadIds) + .anyOf("id", threadIds.toLongArray()) + .equalTo("blocked", false) .findAll() realm.executeTransaction { - conversations.forEach { it.blocked = true } + conversations.forEach { conversation -> + conversation.blocked = true + conversation.blockingClient = blockingClient + conversation.blockReason = blockReason + } } } } @@ -285,7 +325,11 @@ class ConversationRepositoryImpl @Inject constructor( .findAll() realm.executeTransaction { - conversations.forEach { it.blocked = false } + conversations.forEach { conversation -> + conversation.blocked = false + conversation.blockingClient = null + conversation.blockReason = null + } } } } @@ -333,7 +377,7 @@ class ConversationRepositoryImpl @Inject constructor( .map { recipient -> recipient.apply { contact = contacts.firstOrNull { - it.numbers.any { PhoneNumberUtils.compare(recipient.address, it.address) } + it.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) } } } } diff --git a/data/src/main/java/com/moez/QKSMS/repository/ImageRepostoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/ImageRepostoryImpl.kt index 0b597f8ded958e70d089730222fbb41e01ed7d01..06bc586f4e7b3ef845dcb4729cbe98ea92c988c3 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/ImageRepostoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/ImageRepostoryImpl.kt @@ -22,23 +22,16 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix -import android.media.MediaScannerConnection import android.net.Uri -import android.os.Environment import androidx.exifinterface.media.ExifInterface -import java.io.File -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException import javax.inject.Inject class ImageRepostoryImpl @Inject constructor(private val context: Context) : ImageRepository { override fun loadImage(uri: Uri): Bitmap? { - val exif = ExifInterface(context.contentResolver.openInputStream(uri)) + val exif = context.contentResolver.openInputStream(uri)?.let(::ExifInterface) val bitmap = BitmapFactory.decodeStream(context.contentResolver.openInputStream(uri)) - val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - + val orientation = exif?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) return when (orientation) { ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(bitmap, 90f) @@ -58,26 +51,4 @@ class ImageRepostoryImpl @Inject constructor(private val context: Context) : Ima return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) } - override fun saveImage(uri: Uri) { - val type = context.contentResolver.getType(uri)?.split("/") ?: return - val dir = File(Environment.getExternalStorageDirectory(), "QKSMS").apply { mkdirs() } - val file = File(dir, "${type.first()}${System.currentTimeMillis()}.${type.last()}") - - try { - val outputStream = FileOutputStream(file) - val inputStream = context.contentResolver.openInputStream(uri) - - inputStream.copyTo(outputStream, 1024) - - inputStream.close() - outputStream.close() - } catch (e: FileNotFoundException) { - e.printStackTrace() - } catch (e: IOException) { - e.printStackTrace() - } - - MediaScannerConnection.scanFile(context, arrayOf(file.path), null, null) - } - } diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 91d8cd7e2b2eddad36c112d193ffe2e567c6f789..f596b48d488988d4b9715d5aa72b1642c4e57e64 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -24,11 +24,13 @@ import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent -import android.content.IntentFilter +import android.media.MediaScannerConnection import android.os.Build +import android.os.Environment import android.provider.Telephony -import android.telephony.PhoneNumberUtils import android.telephony.SmsManager +import android.webkit.MimeTypeMap +import androidx.core.content.contentValuesOf import com.google.android.mms.ContentType import com.google.android.mms.MMSPart import com.klinker.android.send_message.SmsManagerFactory @@ -42,10 +44,11 @@ import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart +import com.moez.QKSMS.receiver.SendSmsReceiver import com.moez.QKSMS.receiver.SmsDeliveredReceiver import com.moez.QKSMS.receiver.SmsSentReceiver -import com.moez.QKSMS.service.SendSmsService import com.moez.QKSMS.util.ImageUtils +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import com.moez.QKSMS.util.tryOrNull import io.realm.Case @@ -53,6 +56,10 @@ import io.realm.Realm import io.realm.RealmResults import io.realm.Sort import timber.log.Timber +import java.io.File +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -60,8 +67,9 @@ import javax.inject.Singleton class MessageRepositoryImpl @Inject constructor( private val activeConversationManager: ActiveConversationManager, private val context: Context, - private val messageIds: KeyManager, private val imageRepository: ImageRepository, + private val messageIds: KeyManager, + private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val syncRepository: SyncRepository ) : MessageRepository { @@ -70,7 +78,17 @@ class MessageRepositoryImpl @Inject constructor( return Realm.getDefaultInstance() .where(Message::class.java) .equalTo("threadId", threadId) - .let { if (query.isEmpty()) it else it.contains("body", query, Case.INSENSITIVE) } + .let { + when (query.isEmpty()) { + true -> it + false -> it + .beginGroup() + .contains("body", query, Case.INSENSITIVE) + .or() + .contains("parts.text", query, Case.INSENSITIVE) + .endGroup() + } + } .sort("date") .findAllAsync() } @@ -90,12 +108,14 @@ class MessageRepositoryImpl @Inject constructor( } override fun getUnreadCount(): Long { - return Realm.getDefaultInstance() - .where(Conversation::class.java) - .equalTo("archived", false) - .equalTo("blocked", false) - .equalTo("read", false) - .count() + return Realm.getDefaultInstance().use { realm -> + realm.refresh() + realm.where(Conversation::class.java) + .equalTo("archived", false) + .equalTo("blocked", false) + .equalTo("read", false) + .count() + } } override fun getPart(id: Long): MmsPart? { @@ -118,6 +138,38 @@ class MessageRepositoryImpl @Inject constructor( .findAllAsync() } + override fun savePart(id: Long): File? { + val part = getPart(id) ?: return null + + val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(part.type) ?: return null + val date = part.messages?.first()?.date + val dir = File(Environment.getExternalStorageDirectory(), "QKSMS/Media").apply { mkdirs() } + val fileName = part.name?.takeIf { name -> name.endsWith(extension) } + ?: "${part.type.split("/").last()}_$date.$extension" + var file: File + var index = 0 + do { + file = File(dir, if (index == 0) fileName else fileName.replace(".$extension", " ($index).$extension")) + index++ + } while (file.exists()) + + try { + FileOutputStream(file).use { outputStream -> + context.contentResolver.openInputStream(part.getUri())?.use { inputStream -> + inputStream.copyTo(outputStream, 1024) + } + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + + MediaScannerConnection.scanFile(context, arrayOf(file.path), null, null) + + return file.takeIf { it.exists() } + } + /** * Retrieves the list of messages which should be shown in the notification * for a given conversation @@ -218,10 +270,16 @@ class MessageRepositoryImpl @Inject constructor( attachments: List, delay: Int ) { + val signedBody = when { + prefs.signature.get().isEmpty() -> body + body.isNotEmpty() -> body + '\n' + prefs.signature.get() + else -> prefs.signature.get() + } + if (addresses.size == 1 && attachments.isEmpty()) { // SMS if (delay > 0) { // With delay val sendTime = System.currentTimeMillis() + delay - val message = insertSentSms(subId, threadId, addresses.first(), body, sendTime) + val message = insertSentSms(subId, threadId, addresses.first(), signedBody, sendTime) val intent = getIntentForDelayedSms(message.id) @@ -232,14 +290,14 @@ class MessageRepositoryImpl @Inject constructor( alarmManager.setExact(AlarmManager.RTC_WAKEUP, sendTime, intent) } } else { // No delay - val message = insertSentSms(subId, threadId, addresses.first(), body, System.currentTimeMillis()) + val message = insertSentSms(subId, threadId, addresses.first(), signedBody, System.currentTimeMillis()) sendSms(message) } } else { // MMS val parts = arrayListOf() - if (body.isNotBlank()) { - parts += MMSPart("text", ContentType.TEXT_PLAIN, body.toByteArray()) + if (signedBody.isNotBlank()) { + parts += MMSPart("text", ContentType.TEXT_PLAIN, signedBody.toByteArray()) } // Add the GIFs as attachments @@ -271,7 +329,7 @@ class MessageRepositoryImpl @Inject constructor( .map { vCard -> MMSPart("contact", ContentType.TEXT_VCARD, vCard) } val transaction = Transaction(context) - transaction.sendNewMessage(subId, threadId, addresses.map(PhoneNumberUtils::stripSeparators), parts, null) + transaction.sendNewMessage(subId, threadId, addresses.map(phoneNumberUtils::normalizeNumber), parts, null) } } @@ -285,26 +343,29 @@ class MessageRepositoryImpl @Inject constructor( ?: arrayListOf() val sentIntents = parts.map { - context.registerReceiver(SmsSentReceiver(), IntentFilter(SmsSentReceiver.ACTION)) - val intent = Intent(SmsSentReceiver.ACTION).putExtra("id", message.id) + val intent = Intent(context, SmsSentReceiver::class.java).putExtra("id", message.id) PendingIntent.getBroadcast(context, message.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) } val deliveredIntents = parts.map { - context.registerReceiver(SmsDeliveredReceiver(), IntentFilter(SmsDeliveredReceiver.ACTION)) - val intent = Intent(SmsDeliveredReceiver.ACTION).putExtra("id", message.id) + val intent = Intent(context, SmsDeliveredReceiver::class.java).putExtra("id", message.id) val pendingIntent = PendingIntent .getBroadcast(context, message.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) if (prefs.delivery.get()) pendingIntent else null } - smsManager.sendMultipartTextMessage( - message.address, - null, - parts, - ArrayList(sentIntents), - ArrayList(deliveredIntents) - ) + try { + smsManager.sendMultipartTextMessage( + message.address, + null, + parts, + ArrayList(sentIntents), + ArrayList(deliveredIntents) + ) + } catch (e: IllegalArgumentException) { + Timber.w(e, "Message body lengths: ${parts.map { it?.length }}") + markFailed(message.id, Telephony.MmsSms.ERR_TYPE_GENERIC) + } } override fun cancelDelayedSms(id: Long) { @@ -313,8 +374,8 @@ class MessageRepositoryImpl @Inject constructor( } private fun getIntentForDelayedSms(id: Long): PendingIntent { - val intent = Intent(context, SendSmsService::class.java).putExtra("id", id) - return PendingIntent.getService(context, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + val intent = Intent(context, SendSmsReceiver::class.java).putExtra("id", id) + return PendingIntent.getBroadcast(context, id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT) } override fun insertSentSms(subId: Int, threadId: Long, address: String, body: String, date: Long): Message { @@ -338,16 +399,20 @@ class MessageRepositoryImpl @Inject constructor( realm.executeTransaction { managedMessage = realm.copyToRealmOrUpdate(message) } // Insert the message to the native content provider - val values = ContentValues().apply { - put(Telephony.Sms.ADDRESS, address) - put(Telephony.Sms.BODY, body) - put(Telephony.Sms.DATE, System.currentTimeMillis()) - put(Telephony.Sms.READ, true) - put(Telephony.Sms.SEEN, true) - put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_OUTBOX) - put(Telephony.Sms.THREAD_ID, threadId) - put(Telephony.Sms.SUBSCRIPTION_ID, subId) + val values = contentValuesOf( + Telephony.Sms.ADDRESS to address, + Telephony.Sms.BODY to body, + Telephony.Sms.DATE to System.currentTimeMillis(), + Telephony.Sms.READ to true, + Telephony.Sms.SEEN to true, + Telephony.Sms.TYPE to Telephony.Sms.MESSAGE_TYPE_OUTBOX, + Telephony.Sms.THREAD_ID to threadId + ) + + if (prefs.canUseSubId.get()) { + values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId) } + val uri = context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values) // Update the contentId after the message has been inserted to the content provider @@ -355,7 +420,9 @@ class MessageRepositoryImpl @Inject constructor( // // We do this after inserting the message because it might be slow, and we want the message // to be inserted into Realm immediately. We don't need to do this after receiving one - realm.executeTransaction { managedMessage?.takeIf { it.isValid }?.contentId = uri.lastPathSegment.toLong() } + uri?.lastPathSegment?.toLong()?.let { id -> + realm.executeTransaction { managedMessage?.takeIf { it.isValid }?.contentId = id } + } realm.close() // On some devices, we can't obtain a threadId until after the first message is sent in a @@ -389,16 +456,19 @@ class MessageRepositoryImpl @Inject constructor( realm.executeTransaction { managedMessage = realm.copyToRealmOrUpdate(message) } // Insert the message to the native content provider - val values = ContentValues().apply { - put(Telephony.Sms.ADDRESS, address) - put(Telephony.Sms.BODY, body) - put(Telephony.Sms.DATE_SENT, sentTime) - put(Telephony.Sms.SUBSCRIPTION_ID, subId) + val values = contentValuesOf( + Telephony.Sms.ADDRESS to address, + Telephony.Sms.BODY to body, + Telephony.Sms.DATE_SENT to sentTime + ) + + if (prefs.canUseSubId.get()) { + values.put(Telephony.Sms.SUBSCRIPTION_ID, message.subId) } - context.contentResolver.insert(Telephony.Sms.Inbox.CONTENT_URI, values)?.let { uri -> + context.contentResolver.insert(Telephony.Sms.Inbox.CONTENT_URI, values)?.lastPathSegment?.toLong()?.let { id -> // Update the contentId after the message has been inserted to the content provider - realm.executeTransaction { managedMessage?.contentId = uri.lastPathSegment.toLong() } + realm.executeTransaction { managedMessage?.contentId = id } } realm.close() diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index fb9a601d4153406f6df6b79122d6bee62560d57e..882f3033b94828447482d039a76e082a69c93216 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -22,7 +22,6 @@ import android.content.ContentResolver import android.content.ContentUris import android.net.Uri import android.provider.Telephony -import android.telephony.PhoneNumberUtils import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.extensions.insertOrUpdate import com.moez.QKSMS.extensions.map @@ -37,6 +36,7 @@ import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SyncLog +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.tryOrNull import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject @@ -54,20 +54,10 @@ class SyncRepositoryImpl @Inject constructor( private val cursorToRecipient: CursorToRecipient, private val cursorToContact: CursorToContact, private val keys: KeyManager, + private val phoneNumberUtils: PhoneNumberUtils, private val rxPrefs: RxSharedPreferences ) : SyncRepository { - /** - * Holds data that should be persisted across full syncs - */ - private data class PersistedData( - val id: Long, - val archived: Boolean, - val blocked: Boolean, - val pinned: Boolean, - val name: String - ) - override val syncProgress: Subject = BehaviorSubject.createDefault(SyncRepository.SyncProgress.Idle()) @@ -80,7 +70,7 @@ class SyncRepositoryImpl @Inject constructor( val realm = Realm.getDefaultInstance() realm.beginTransaction() - var persistedData = realm.where(Conversation::class.java) + val persistedData = realm.copyFromRealm(realm.where(Conversation::class.java) .beginGroup() .equalTo("archived", true) .or() @@ -89,9 +79,14 @@ class SyncRepositoryImpl @Inject constructor( .equalTo("pinned", true) .or() .isNotEmpty("name") + .or() + .isNotNull("blockingClient") + .or() + .isNotEmpty("blockReason") .endGroup() - .findAll() - .map { PersistedData(it.id, it.archived, it.blocked, it.pinned, it.name) } + .findAll()) + .associateBy { conversation -> conversation.id } + .toMutableMap() realm.delete(Contact::class.java) realm.delete(Conversation::class.java) @@ -124,26 +119,26 @@ class SyncRepositoryImpl @Inject constructor( // Migrate blocked conversations from 2.7.3 val oldBlockedSenders = rxPrefs.getStringSet("pref_key_blocked_senders") - persistedData += oldBlockedSenders.get() + oldBlockedSenders.get() .map { threadIdString -> threadIdString.toLong() } - .filter { threadId -> persistedData.none { it.id == threadId } } - .map { threadId -> PersistedData(threadId, false, true, false, "") } + .filter { threadId -> !persistedData.contains(threadId) } + .forEach { threadId -> persistedData[threadId] = Conversation(id = threadId, blocked = true) } // Sync conversations conversationCursor?.use { - val conversations = conversationCursor - .map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToConversation.map(cursor) + val conversations = conversationCursor.map { cursor -> + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + cursorToConversation.map(cursor).apply { + persistedData[id]?.let { persistedConversation -> + archived = persistedConversation.archived + blocked = persistedConversation.blocked + pinned = persistedConversation.pinned + name = persistedConversation.name + blockingClient = persistedConversation.blockingClient + blockReason = persistedConversation.blockReason } - - persistedData.forEach { data -> - val conversation = conversations.firstOrNull { conversation -> conversation.id == data.id } - conversation?.archived = data.archived - conversation?.blocked = data.blocked - conversation?.pinned = data.pinned - conversation?.name = data.name + } } realm.where(Message::class.java) @@ -151,8 +146,7 @@ class SyncRepositoryImpl @Inject constructor( .distinct("threadId") .findAll() .forEach { message -> - val conversation = conversations - .firstOrNull { conversation -> conversation.id == message.threadId } + val conversation = conversations.find { conversation -> conversation.id == message.threadId } conversation?.date = message.date conversation?.snippet = message.getSummary() conversation?.me = message.isMe() @@ -164,16 +158,15 @@ class SyncRepositoryImpl @Inject constructor( // Sync recipients recipientCursor?.use { val contacts = realm.copyToRealm(getContacts()) - val recipients = recipientCursor - .map { cursor -> - progress++ - syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) - cursorToRecipient.map(cursor).apply { - contact = contacts.firstOrNull { contact -> - contact.numbers.any { PhoneNumberUtils.compare(address, it.address) } - } - } + val recipients = recipientCursor.map { cursor -> + progress++ + syncProgress.onNext(SyncRepository.SyncProgress.Running(max, progress, false)) + cursorToRecipient.map(cursor).apply { + contact = contacts.firstOrNull { contact -> + contact.numbers.any { phoneNumberUtils.compare(address, it.address) } } + } + } realm.insertOrUpdate(recipients) } @@ -250,7 +243,7 @@ class SyncRepositoryImpl @Inject constructor( val updatedRecipients = recipients.map { recipient -> recipient.apply { contact = contacts.firstOrNull { - it.numbers.any { PhoneNumberUtils.compare(recipient.address, it.address) } + it.numbers.any { phoneNumberUtils.compare(recipient.address, it.address) } } } } @@ -264,7 +257,7 @@ class SyncRepositoryImpl @Inject constructor( override fun syncContact(address: String): Boolean { // See if there's a contact that matches this phone number var contact = getContacts().firstOrNull { - it.numbers.any { number -> PhoneNumberUtils.compare(number.address, address) } + it.numbers.any { number -> phoneNumberUtils.compare(number.address, address) } } ?: return false Realm.getDefaultInstance().use { realm -> @@ -277,7 +270,7 @@ class SyncRepositoryImpl @Inject constructor( val updatedRecipients = recipients .filter { recipient -> contact.numbers.any { number -> - PhoneNumberUtils.compare(recipient.address, number.address) + phoneNumberUtils.compare(recipient.address, number.address) } } .map { recipient -> recipient.apply { this.contact = contact } } diff --git a/data/src/main/java/com/moez/QKSMS/service/HeadlessSmsSendService.kt b/data/src/main/java/com/moez/QKSMS/service/HeadlessSmsSendService.kt index f653d72ad00940fc7749fe0e07f66ec53c2ea174..ce3909342407fb2e46217d2166f19a1af8139532 100644 --- a/data/src/main/java/com/moez/QKSMS/service/HeadlessSmsSendService.kt +++ b/data/src/main/java/com/moez/QKSMS/service/HeadlessSmsSendService.kt @@ -38,7 +38,7 @@ class HeadlessSmsSendService : IntentService("HeadlessSmsSendService") { AndroidInjection.inject(this) intent.extras?.getString(Intent.EXTRA_TEXT)?.takeIf { it.isNotBlank() }?.let { body -> val intentUri = intent.data - val recipients = getRecipients(intentUri).split(";") + val recipients = intentUri?.let(::getRecipients)?.split(";") ?: return@let val threadId = conversationRepo.getOrCreateConversation(recipients)?.id ?: 0L sendMessage.execute(SendMessage.Params(-1, threadId, recipients, body)) } diff --git a/data/src/main/java/com/moez/QKSMS/util/ContactImageLoader.kt b/data/src/main/java/com/moez/QKSMS/util/ContactImageLoader.kt index 8ab0f75676e6ba4a89678923670e77581960c125..c50ef35a56fc517dbd80eaed086ecd6a73398062 100644 --- a/data/src/main/java/com/moez/QKSMS/util/ContactImageLoader.kt +++ b/data/src/main/java/com/moez/QKSMS/util/ContactImageLoader.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.util import android.content.Context import android.provider.ContactsContract -import android.telephony.PhoneNumberUtils import com.bumptech.glide.Priority import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.Key @@ -38,11 +37,12 @@ import java.security.MessageDigest class ContactImageLoader( private val context: Context, - private val contactRepo: ContactRepository + private val contactRepo: ContactRepository, + private val phoneNumberUtils: PhoneNumberUtils ) : ModelLoader { override fun handles(model: String): Boolean { - return PhoneNumberUtils.isGlobalPhoneNumber(model) + return model.startsWith("tel:") } override fun buildLoadData( @@ -51,12 +51,18 @@ class ContactImageLoader( height: Int, options: Options ): ModelLoader.LoadData? { - return ModelLoader.LoadData(ContactImageKey(model), ContactImageFetcher(context, contactRepo, model)) + return ModelLoader.LoadData( + ContactImageKey(phoneNumberUtils.normalizeNumber(model)), + ContactImageFetcher(context, contactRepo, model)) } - class Factory(val context: Context, val prefs: Preferences) : ModelLoaderFactory { + class Factory( + private val context: Context, + private val prefs: Preferences + ) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ContactImageLoader { - return ContactImageLoader(context, ContactRepositoryImpl(context, prefs)) + return ContactImageLoader(context, ContactRepositoryImpl(context, prefs), PhoneNumberUtils(context)) } override fun teardown() {} // nothing to do here @@ -80,7 +86,9 @@ class ContactImageLoader( override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { loadPhotoDisposable = contactRepo.findContactUri(address) - .map { uri -> ContactsContract.Contacts.openContactPhotoInputStream(context.contentResolver, uri) } + .map { uri -> + ContactsContract.Contacts.openContactPhotoInputStream(context.contentResolver, uri, true) + } .subscribeOn(Schedulers.io()) .subscribe( { inputStream -> callback.onDataReady(inputStream) }, diff --git a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt index 59bd98737bd3ab6c242e3c8b42c1b3be9dac9980..f3b43507676395572afd21b260373fcf677b71f2 100644 --- a/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt +++ b/data/src/main/java/com/moez/QKSMS/util/GlideAppModule.kt @@ -38,8 +38,9 @@ class GlideAppModule : AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { // TODO use DI to create the ContactImageLoader.Factory - registry.append(String::class.java, InputStream::class.java, ContactImageLoader.Factory(context, - Preferences(RxSharedPreferences.create(PreferenceManager.getDefaultSharedPreferences(context))))) + val rxPrefs = RxSharedPreferences.create(PreferenceManager.getDefaultSharedPreferences(context)) + val prefs = Preferences(context, rxPrefs) + registry.append(String::class.java, InputStream::class.java, ContactImageLoader.Factory(context, prefs)) } } \ No newline at end of file diff --git a/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt b/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt index 4ecaa1ebd35dcd6f6506798f22c7f0f9a51c49c2..243bfb53cd23ea530543bb6dec77fd84aad334cb 100644 --- a/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt +++ b/data/src/main/java/com/moez/QKSMS/util/NightModeManager.kt @@ -22,6 +22,7 @@ import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import androidx.appcompat.app.AppCompatDelegate import com.moez.QKSMS.manager.WidgetManager import com.moez.QKSMS.receiver.NightModeReceiver import java.text.SimpleDateFormat @@ -38,17 +39,33 @@ class NightModeManager @Inject constructor( ) { fun updateCurrentTheme() { - // If night mode is not on auto, then there's nothing to do here - if (prefs.nightMode.get() != Preferences.NIGHT_MODE_AUTO) { - return + when (prefs.nightMode.get()) { + Preferences.NIGHT_MODE_SYSTEM -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + } + + Preferences.NIGHT_MODE_OFF -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + } + + Preferences.NIGHT_MODE_ON -> { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + Preferences.NIGHT_MODE_AUTO -> { + val nightStartTime = getPreviousInstanceOfTime(prefs.nightStart.get()) + val nightEndTime = getPreviousInstanceOfTime(prefs.nightEnd.get()) + + // If the last nightStart was more recent than the last nightEnd, then it's night time + val night = nightStartTime > nightEndTime + prefs.night.set(night) + AppCompatDelegate.setDefaultNightMode(when (night) { + true -> AppCompatDelegate.MODE_NIGHT_YES + false -> AppCompatDelegate.MODE_NIGHT_NO + }) + widgetManager.updateTheme() + } } - - val nightStartTime = getPreviousInstanceOfTime(prefs.nightStart.get()) - val nightEndTime = getPreviousInstanceOfTime(prefs.nightEnd.get()) - - // If the last nightStart was more recent than the last nightEnd, then it's night time - prefs.night.set(nightStartTime > nightEndTime) - widgetManager.updateTheme() } fun updateNightMode(mode: Int) { @@ -57,6 +74,12 @@ class NightModeManager @Inject constructor( // If it's not on auto mode, set the appropriate night mode if (mode != Preferences.NIGHT_MODE_AUTO) { prefs.night.set(mode == Preferences.NIGHT_MODE_ON) + AppCompatDelegate.setDefaultNightMode(when (mode) { + Preferences.NIGHT_MODE_OFF -> AppCompatDelegate.MODE_NIGHT_NO + Preferences.NIGHT_MODE_ON -> AppCompatDelegate.MODE_NIGHT_YES + Preferences.NIGHT_MODE_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + else -> AppCompatDelegate.MODE_NIGHT_NO + }) widgetManager.updateTheme() } diff --git a/data/src/main/java/com/moez/QKSMS/util/PhoneNumberUtils.kt b/data/src/main/java/com/moez/QKSMS/util/PhoneNumberUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..4f4ffa5e916115479764fa460e7c6de6df5eee44 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/util/PhoneNumberUtils.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.util + +import android.content.Context +import android.telephony.PhoneNumberUtils +import io.michaelrocks.libphonenumber.android.PhoneNumberUtil +import io.michaelrocks.libphonenumber.android.Phonenumber +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PhoneNumberUtils @Inject constructor(context: Context) { + + private val countryCode = Locale.getDefault().country + private val phoneNumberUtil = PhoneNumberUtil.createInstance(context) + + /** + * Android's implementation is too loose and causes false positives + * libphonenumber is stricter but too slow + * + * This method will run successfully stricter checks without compromising much speed + */ + fun compare(first: String, second: String): Boolean { + if (first.equals(second, true)) { + return true + } + + if (PhoneNumberUtils.compare(first, second)) { + val firstNumber = parse(first) ?: return false + val secondNumber = parse(second) ?: return false + val matchType = phoneNumberUtil.isNumberMatch(firstNumber, secondNumber) + if (matchType >= PhoneNumberUtil.MatchType.NSN_MATCH) { + return true + } + } + + return false + } + + fun isPossibleNumber(number: CharSequence): Boolean { + return parse(number) != null + } + + fun isReallyDialable(digit: Char): Boolean { + return PhoneNumberUtils.isReallyDialable(digit) + } + + fun formatNumber(number: CharSequence): String { + // PhoneNumberUtil doesn't maintain country code input + return PhoneNumberUtils.formatNumber(number.toString(), countryCode) ?: number.toString() + } + + fun normalizeNumber(number: String): String { + return PhoneNumberUtils.stripSeparators(number) + } + + private fun parse(number: CharSequence): Phonenumber.PhoneNumber? { + return tryOrNull(false) { phoneNumberUtil.parse(number, countryCode) } + } + +} diff --git a/domain/build.gradle b/domain/build.gradle index 301d4f0ff305195f834561d6c01f702dcba9747f..a145f7896e6835067f6977d9cf5439f745030aad 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -22,11 +22,16 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 28 + compileSdkVersion 29 + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } defaultConfig { minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 } } @@ -47,6 +52,7 @@ dependencies { implementation "io.reactivex.rxjava2:rxkotlin:$rxkotlin_version" implementation "androidx.core:core-ktx:$androidx_core_version" + implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.jakewharton.timber:timber:$timber_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation project(":common") diff --git a/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt new file mode 100644 index 0000000000000000000000000000000000000000..97534f8f84d7c661d3d7c9a49dfab48b1f80e80c --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/blocking/BlockingClient.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.blocking + +import io.reactivex.Completable +import io.reactivex.Single + +interface BlockingClient { + + enum class Capability { + BLOCK_WITHOUT_PERMISSION, + BLOCK_WITH_PERMISSION , + CANT_BLOCK + } + + sealed class Action { + class Block(val reason: String? = null) : Action() + object Unblock : Action() + + // We only need these for Should I Answer, because they don't allow us to block numbers in their app directly. + // This means there's a good chance that if a number is blocked in QK, it won't be blocked there, so we + // shouldn't unblock the conversation in that case + object DoNothing : Action() + } + + /** + * Returns true if the target blocking client is available for use, ie. it is installed + */ + fun isAvailable(): Boolean + + /** + * Returns the level of access that the given blocking client provides to QKSMS + */ + fun getClientCapability(): Capability + + /** + * Returns the recommendation action to perform given a message from the [address] + */ + fun getAction(address: String): Single + + /** + * Blocks the numbers or opens the manager + */ + fun block(addresses: List): Completable + + /** + * Unblocks the numbers or opens the manager + */ + fun unblock(addresses: List): Completable + + /** + * Opens the settings page for the blocking manager + */ + fun openSettings() + +} diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt b/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt index 4e7400d2ffb7daf926f5a3958de4e7c37eb569b1..da6d5e764ca9681a2606c2f1afd28266c2b4ac64 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/MarkBlocked.kt @@ -22,11 +22,19 @@ import com.moez.QKSMS.repository.ConversationRepository import io.reactivex.Flowable import javax.inject.Inject -class MarkBlocked @Inject constructor(private val conversationRepo: ConversationRepository) : Interactor>() { +class MarkBlocked @Inject constructor( + private val conversationRepo: ConversationRepository, + private val markRead: MarkRead +) : Interactor() { - override fun buildObservable(params: List): Flowable<*> { - return Flowable.just(params.toLongArray()) - .doOnNext { threadIds -> conversationRepo.markBlocked(*threadIds) } + data class Params(val threadIds: List, val blockingClient: Int, val blockReason: String?) + + override fun buildObservable(params: Params): Flowable<*> { + return Flowable.just(params) + .doOnNext { (threadIds, blockingClient, blockReason) -> + conversationRepo.markBlocked(threadIds, blockingClient, blockReason) + } + .flatMap { (threadIds) -> markRead.buildObservable(threadIds) } } -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt index bf540dfe01e0a1e318bab3ee9802cb951fde8a70..0d1ff0838526c2d1143c6b5e5616fe80838baa4b 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveMms.kt @@ -19,20 +19,23 @@ package com.moez.QKSMS.interactor import android.net.Uri +import com.moez.QKSMS.blocking.BlockingClient import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.manager.ActiveConversationManager -import com.moez.QKSMS.manager.ExternalBlockingManager import com.moez.QKSMS.manager.NotificationManager import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.SyncRepository +import com.moez.QKSMS.util.Preferences import io.reactivex.Flowable +import timber.log.Timber import javax.inject.Inject class ReceiveMms @Inject constructor( private val activeConversationManager: ActiveConversationManager, private val conversationRepo: ConversationRepository, - private val externalBlockingManager: ExternalBlockingManager, + private val blockingClient: BlockingClient, + private val prefs: Preferences, private val syncManager: SyncRepository, private val messageRepo: MessageRepository, private val notificationManager: NotificationManager, @@ -49,14 +52,30 @@ class ReceiveMms @Inject constructor( messageRepo.markRead(message.threadId) } } - .filter { message -> + .mapNotNull { message -> // Because we use the smsmms library for receiving and storing MMS, we'll need // to check if it should be blocked after we've pulled it into realm. If it - // turns out that it should be blocked, then delete it + // turns out that it should be dropped, then delete it // TODO Don't store blocked messages in the first place - !externalBlockingManager.shouldBlock(message.address).blockingGet().also { blocked -> - if (blocked) messageRepo.deleteMessages(message.id) + val action = blockingClient.getAction(message.address).blockingGet() + val shouldDrop = prefs.drop.get() + Timber.v("block=$action, drop=$shouldDrop") + + if (action is BlockingClient.Action.Block && shouldDrop) { + messageRepo.deleteMessages(message.id) + return@mapNotNull null } + + when (action) { + is BlockingClient.Action.Block -> { + messageRepo.markRead(message.threadId) + conversationRepo.markBlocked(listOf(message.threadId), prefs.blockingManager.get(), action.reason) + } + is BlockingClient.Action.Unblock -> conversationRepo.markUnblocked(message.threadId) + else -> Unit + } + + message } .doOnNext { message -> conversationRepo.updateConversations(message.threadId) // Update the conversation diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt index aaa6052e0381a8de8d177d8b10a9e858bdb2a687..6c8254c8bc9e7ba9ce1d9581db25a6a9409914cb 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/ReceiveSms.kt @@ -19,18 +19,21 @@ package com.moez.QKSMS.interactor import android.telephony.SmsMessage +import com.moez.QKSMS.blocking.BlockingClient import com.moez.QKSMS.extensions.mapNotNull -import com.moez.QKSMS.manager.ExternalBlockingManager import com.moez.QKSMS.manager.NotificationManager import com.moez.QKSMS.manager.ShortcutManager import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository +import com.moez.QKSMS.util.Preferences import io.reactivex.Flowable +import timber.log.Timber import javax.inject.Inject class ReceiveSms @Inject constructor( private val conversationRepo: ConversationRepository, - private val externalBlockingManager: ExternalBlockingManager, + private val blockingClient: BlockingClient, + private val prefs: Preferences, private val messageRepo: MessageRepository, private val notificationManager: NotificationManager, private val updateBadge: UpdateBadge, @@ -42,20 +45,37 @@ class ReceiveSms @Inject constructor( override fun buildObservable(params: Params): Flowable<*> { return Flowable.just(params) .filter { it.messages.isNotEmpty() } - .filter { + .mapNotNull { // Don't continue if the sender is blocked - val address = it.messages[0].displayOriginatingAddress - !externalBlockingManager.shouldBlock(address).blockingGet() - } - .map { val messages = it.messages val address = messages[0].displayOriginatingAddress + val action = blockingClient.getAction(address).blockingGet() + val shouldDrop = prefs.drop.get() + Timber.v("block=$action, drop=$shouldDrop") + + // If we should drop the message, don't even save it + if (action is BlockingClient.Action.Block && shouldDrop) { + return@mapNotNull null + } + val time = messages[0].timestampMillis val body: String = messages .mapNotNull { message -> message.displayMessageBody } .reduce { body, new -> body + new } - messageRepo.insertReceivedSms(it.subId, address, body, time) // Add the message to the db + // Add the message to the db + val message = messageRepo.insertReceivedSms(it.subId, address, body, time) + + when (action) { + is BlockingClient.Action.Block -> { + messageRepo.markRead(message.threadId) + conversationRepo.markBlocked(listOf(message.threadId), prefs.blockingManager.get(), action.reason) + } + is BlockingClient.Action.Unblock -> conversationRepo.markUnblocked(message.threadId) + else -> Unit + } + + message } .doOnNext { message -> conversationRepo.updateConversations(message.threadId) // Update the conversation diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SaveImage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SaveImage.kt index 809a31fb33754487220d79c8314770213e56f829..3c5e5838ce977a0ed090c96849acfca1c2c23234 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/SaveImage.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SaveImage.kt @@ -18,21 +18,17 @@ */ package com.moez.QKSMS.interactor -import com.moez.QKSMS.repository.ImageRepository import com.moez.QKSMS.repository.MessageRepository import io.reactivex.Flowable import javax.inject.Inject class SaveImage @Inject constructor( - private val imageRepository: ImageRepository, private val messageRepo: MessageRepository ) : Interactor() { override fun buildObservable(params: Long): Flowable<*> { return Flowable.just(params) - .map { partId -> messageRepo.getPart(partId) } - .map { part -> part.getUri() } - .doOnNext { uri -> imageRepository.saveImage(uri) } + .doOnNext { partId -> messageRepo.savePart(partId) } } } \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SendMessage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SendMessage.kt index 687a5f8195ece46bde83b7c48cece823aa7a8b2f..5a5b727d94baf8b56acddb417874be39c55c4b6d 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/SendMessage.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SendMessage.kt @@ -18,6 +18,9 @@ */ package com.moez.QKSMS.interactor +import android.content.Context +import com.moez.QKSMS.compat.TelephonyCompat +import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository @@ -25,6 +28,7 @@ import io.reactivex.Flowable import javax.inject.Inject class SendMessage @Inject constructor( + private val context: Context, private val conversationRepo: ConversationRepository, private val messageRepo: MessageRepository ) : Interactor() { @@ -41,16 +45,20 @@ class SendMessage @Inject constructor( override fun buildObservable(params: Params): Flowable<*> = Flowable.just(Unit) .filter { params.addresses.isNotEmpty() } .doOnNext { - messageRepo.sendMessage(params.subId, params.threadId, params.addresses, params.body, - params.attachments, params.delay) + // If a threadId isn't provided, try to obtain one + val threadId = when (params.threadId) { + 0L -> TelephonyCompat.getOrCreateThreadId(context, params.addresses.toSet()) + else -> params.threadId + } + messageRepo.sendMessage(params.subId, threadId, params.addresses, params.body, params.attachments, + params.delay) } - .map { - // On some manufacturers, we can't obtain a threadId for a new conversation. In - // this case, find the threadId manually now that it contains a message - if (params.threadId == 0L) { - conversationRepo.getOrCreateConversation(params.addresses)?.id ?: 0 - } else { - params.threadId + .mapNotNull { + // If the threadId wasn't provided, then it's probably because it doesn't exist in Realm. + // Sync it now and get the id + when (params.threadId) { + 0L -> conversationRepo.getOrCreateConversation(params.addresses)?.id + else -> params.threadId } } .doOnNext { threadId -> conversationRepo.updateConversations(threadId) } diff --git a/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt b/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..37f714b787bd413b3142e5d2f4db587529a0b122 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/manager/ChangelogManager.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.manager + +import io.reactivex.Single + +interface ChangelogManager { + + data class Changelog( + val added: List, + val improved: List, + val fixed: List + ) + + /** + * Returns true if the app has benn updated since the last time this method was called + */ + fun didUpdate(): Boolean + + fun getChangelog(): Single + + fun markChangelogSeen() + +} diff --git a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToConversation.kt b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToConversation.kt index fcbdeaa3629f2e114252640380a091e72da0fd2a..56f15e3ebd4c664879c438a1af3312eced11ff36 100644 --- a/domain/src/main/java/com/moez/QKSMS/mapper/CursorToConversation.kt +++ b/domain/src/main/java/com/moez/QKSMS/mapper/CursorToConversation.kt @@ -23,6 +23,6 @@ import com.moez.QKSMS.model.Conversation interface CursorToConversation : Mapper { - fun getConversationsCursor(lastSync: Long = 0): Cursor? + fun getConversationsCursor(): Cursor? } \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt index ca2ff3b4a3a7177ca16f12fde30eff97802cdee8..07298feeaead06894a2f7681744c4dbdaedfa79c 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt @@ -42,7 +42,7 @@ sealed class Attachment { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && inputContent != null) { inputContent.description.hasMimeType("image/gif") } else { - context.contentResolver.getType(uri) == "image/gif" + uri?.let(context.contentResolver::getType) == "image/gif" } } } diff --git a/domain/src/main/java/com/moez/QKSMS/model/BlockedNumber.kt b/domain/src/main/java/com/moez/QKSMS/model/BlockedNumber.kt new file mode 100644 index 0000000000000000000000000000000000000000..f2bf54a1c858f204cece3633ffde932064efd3b2 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/model/BlockedNumber.kt @@ -0,0 +1,10 @@ +package com.moez.QKSMS.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class BlockedNumber( + @PrimaryKey var id: Long = 0, + + var address: String = "" +) : RealmObject() diff --git a/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt b/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt index dfb60780debfbbc71f1efbef5bbc94b03f732b78..de068b99300488a3981f909bc010b14d902a468a 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Conversation.kt @@ -36,6 +36,9 @@ open class Conversation( var me: Boolean = false, var draft: String = "", + var blockingClient: Int? = null, + var blockReason: String? = null, + var name: String = "" // For group chats, the user is allowed to set a custom title for the conversation ) : RealmObject() { diff --git a/domain/src/main/java/com/moez/QKSMS/model/Message.kt b/domain/src/main/java/com/moez/QKSMS/model/Message.kt index 8ab4c230dd4479866e96bd24bead813e22b72b33..82d040598ec860c4a947b63f8e09d979b75b95d5 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Message.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Message.kt @@ -105,6 +105,7 @@ open class Message : RealmObject() { isSms() -> body else -> parts + .filter { it.type == "text/plain" } .mapNotNull { it.text } .joinToString("\n") { text -> text } } diff --git a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt index acc655102e4c1b32fa75da6c2077db5143cf309a..d1afb35d545948e1c6ad66e89da36f9c5d7c3c62 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt @@ -28,6 +28,8 @@ open class MmsPart : RealmObject() { @PrimaryKey var id: Long = 0 var type: String = "" + var seq: Int = -1 + var name: String? = null var text: String? = null @LinkingObjects("parts") diff --git a/domain/src/main/java/com/moez/QKSMS/model/Recipient.kt b/domain/src/main/java/com/moez/QKSMS/model/Recipient.kt index d05937fc08ea134f80641b5313c0225fd109c539..2beebaaed323bbb6796d7e6822cb4beec267783b 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Recipient.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Recipient.kt @@ -34,7 +34,7 @@ open class Recipient( * Return a string that can be displayed to represent the name of this contact */ fun getDisplayName(): String = contact?.name?.takeIf { it.isNotBlank() } - ?: PhoneNumberUtils.formatNumber(address, Locale.getDefault().country) + ?: PhoneNumberUtils.formatNumber(address, Locale.getDefault().country) // TODO: Use our own PhoneNumberUtils ?: address -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/BlockingRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/BlockingRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..9af81b95d69b96598bc06348f37c9786ce6073c4 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/repository/BlockingRepository.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.repository + +import com.moez.QKSMS.model.BlockedNumber +import io.realm.RealmResults + +interface BlockingRepository { + + fun blockNumber(vararg addresses: String) + + fun getBlockedNumbers(): RealmResults + + fun getBlockedNumber(id: Long): BlockedNumber? + + fun isBlocked(address: String): Boolean + + fun unblockNumber(id: Long) + + fun unblockNumbers(vararg addresses: String) + +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt index bf07859ff41fef1b3899a81823072b1d190a5832..7c517c105aa04d2ace3c152e9a21bddb61f8411d 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ConversationRepository.kt @@ -19,6 +19,7 @@ package com.moez.QKSMS.repository import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.SearchResult import io.realm.RealmResults @@ -35,14 +36,23 @@ interface ConversationRepository { fun setConversationName(id: Long, name: String) - fun searchConversations(query: String): List + fun searchConversations(query: CharSequence): List fun getBlockedConversations(): RealmResults + fun getBlockedConversationsAsync(): RealmResults + fun getConversationAsync(threadId: Long): Conversation fun getConversation(threadId: Long): Conversation? + /** + * Returns all conversations with an id in [threadIds] + */ + fun getConversations(vararg threadIds: Long): RealmResults + + fun getRecipient(recipientId: Long): Recipient? + fun getThreadId(recipient: String): Long? fun getThreadId(recipients: Collection): Long? @@ -68,7 +78,7 @@ interface ConversationRepository { fun markUnpinned(vararg threadIds: Long) - fun markBlocked(vararg threadIds: Long) + fun markBlocked(threadIds: List, blockingClient: Int, blockReason: String?) fun markUnblocked(vararg threadIds: Long) diff --git a/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt index 6ec8e4b7ca3957a5c5fa6cd7634e7056b408065c..70c6ecbb314e3843b7951a4ef406fdc497ca5d59 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/ImageRepository.kt @@ -25,6 +25,4 @@ interface ImageRepository { fun loadImage(uri: Uri): Bitmap? - fun saveImage(uri: Uri) - -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt index b3ec7b5891b26a707b9156537cc118fb7a911fd9..80530bb19eaa41d3e27682778a016d5f1ded96d0 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/MessageRepository.kt @@ -22,6 +22,7 @@ import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import io.realm.RealmResults +import java.io.File interface MessageRepository { @@ -37,6 +38,8 @@ interface MessageRepository { fun getPartsForConversation(threadId: Long): RealmResults + fun savePart(id: Long): File? + /** * Retrieves the list of messages which should be shown in the notification * for a given conversation @@ -95,4 +98,4 @@ interface MessageRepository { fun deleteMessages(vararg messageIds: Long) -} \ No newline at end of file +} diff --git a/data/src/main/java/com/moez/QKSMS/util/Preferences.kt b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt similarity index 75% rename from data/src/main/java/com/moez/QKSMS/util/Preferences.kt rename to domain/src/main/java/com/moez/QKSMS/util/Preferences.kt index eb6c4cdc39cf3e58182c1e66aadcbd653e96b20f..5e73c28968f2fb989f5844511d5340a39fbb55d1 100644 --- a/data/src/main/java/com/moez/QKSMS/util/Preferences.kt +++ b/domain/src/main/java/com/moez/QKSMS/util/Preferences.kt @@ -18,20 +18,23 @@ */ package com.moez.QKSMS.util +import android.content.Context import android.os.Build import android.provider.Settings import com.f2prateek.rx.preferences2.Preference import com.f2prateek.rx.preferences2.RxSharedPreferences +import com.moez.QKSMS.common.util.extensions.versionCode import javax.inject.Inject import javax.inject.Singleton @Singleton -class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) { +class Preferences @Inject constructor(context: Context, private val rxPrefs: RxSharedPreferences) { companion object { - const val NIGHT_MODE_OFF = 0 - const val NIGHT_MODE_ON = 1 - const val NIGHT_MODE_AUTO = 2 + const val NIGHT_MODE_SYSTEM = 0 + const val NIGHT_MODE_OFF = 1 + const val NIGHT_MODE_ON = 2 + const val NIGHT_MODE_AUTO = 3 const val TEXT_SIZE_SMALL = 0 const val TEXT_SIZE_NORMAL = 1 @@ -58,20 +61,33 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) const val SWIPE_ACTION_DELETE = 2 const val SWIPE_ACTION_CALL = 3 const val SWIPE_ACTION_READ = 4 + const val SWIPE_ACTION_UNREAD = 5 + + const val BLOCKING_MANAGER_QKSMS = 0 + const val BLOCKING_MANAGER_CC = 1 + const val BLOCKING_MANAGER_SIA = 2 } // Internal val night = rxPrefs.getBoolean("night", false) val canUseSubId = rxPrefs.getBoolean("canUseSubId", true) + val version = rxPrefs.getInteger("version", context.versionCode) + val changelogVersion = rxPrefs.getInteger("changelogVersion", context.versionCode) + @Deprecated("This should only be accessed when migrating to @blockingManager") + val sia = rxPrefs.getBoolean("sia", false) // User configurable - val nightMode = rxPrefs.getInteger("nightModeSummary", NIGHT_MODE_OFF) + val nightMode = rxPrefs.getInteger("nightMode", when (Build.VERSION.SDK_INT >= 29) { + true -> NIGHT_MODE_SYSTEM + false -> NIGHT_MODE_OFF + }) val nightStart = rxPrefs.getString("nightStart", "18:00") val nightEnd = rxPrefs.getString("nightEnd", "6:00") val black = rxPrefs.getBoolean("black", false) val systemFont = rxPrefs.getBoolean("systemFont", false) val textSize = rxPrefs.getInteger("textSize", TEXT_SIZE_NORMAL) - val sia = rxPrefs.getBoolean("sia", false) + val blockingManager = rxPrefs.getInteger("blockingManager", BLOCKING_MANAGER_QKSMS) + val drop = rxPrefs.getBoolean("drop", false) val notifAction1 = rxPrefs.getInteger("notifAction1", NOTIFICATION_ACTION_READ) val notifAction2 = rxPrefs.getInteger("notifAction2", NOTIFICATION_ACTION_REPLY) val notifAction3 = rxPrefs.getInteger("notifAction3", NOTIFICATION_ACTION_NONE) @@ -82,11 +98,26 @@ class Preferences @Inject constructor(private val rxPrefs: RxSharedPreferences) val swipeLeft = rxPrefs.getInteger("swipeLeft", SWIPE_ACTION_ARCHIVE) val autoEmoji = rxPrefs.getBoolean("autoEmoji", true) val delivery = rxPrefs.getBoolean("delivery", false) + val signature = rxPrefs.getString("signature", "") val unicode = rxPrefs.getBoolean("unicode", false) val mobileOnly = rxPrefs.getBoolean("mobileOnly", false) val mmsSize = rxPrefs.getInteger("mmsSize", 300) val logging = rxPrefs.getBoolean("logging", false) + init { + // Migrate from old night mode preference to new one, now that we support android Q night mode + val nightModeSummary = rxPrefs.getInteger("nightModeSummary") + if (nightModeSummary.isSet) { + nightMode.set(when (nightModeSummary.get()) { + 0 -> NIGHT_MODE_OFF + 1 -> NIGHT_MODE_ON + 2 -> NIGHT_MODE_AUTO + else -> NIGHT_MODE_OFF + }) + nightModeSummary.delete() + } + } + fun theme(threadId: Long = 0): Preference { val default = rxPrefs.getInteger("theme", 0xFF0097A7.toInt()) diff --git a/gradle.properties b/gradle.properties index ff2a5cd5cf3e44fb8f1cd6655b0397e2ab7e52a2..bb91abfe1f7fc7f9bdcf3ff2dff9365ce92956a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,6 @@ org.gradle.jvmargs=-Xmx1536m org.gradle.caching=true android.useAndroidX=true android.enableJetifier=true -android.enableR8=false # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 013022680dae6360029068d062f66904b2923e98..6914a81f75ef85693e46ba0e16291df0ad72d9ae 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jan 19 18:02:14 EST 2019 +#Thu Jun 06 19:27:58 EDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/keystore.enc b/keystore.enc deleted file mode 100644 index 89817b4dec6f8b8932dcd5a62d941fb725920592..0000000000000000000000000000000000000000 Binary files a/keystore.enc and /dev/null differ diff --git a/presentation/build.gradle b/presentation/build.gradle index 9c68528d6588a9433cb97e7f65bec8b0028ba591..cd4006baf14e8f2adef3be47000f5f175d02a71f 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -23,30 +23,39 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 29 + buildToolsVersion "29.0.2" flavorDimensions "analytics" defaultConfig { - applicationId "foundation.e.message" + applicationId "com.moez.QKSMS" minSdkVersion 21 - targetSdkVersion 28 - versionCode 194 - versionName "3.6.4" + targetSdkVersion 29 + versionCode 2203 + versionName "3.7.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + /* signingConfigs { + release + }*/ + buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + //signingConfig signingConfigs.release } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + kotlinOptions { + jvmTarget = "1.8" } lintOptions { @@ -66,23 +75,19 @@ android { universalApk true } } + /*if (System.getenv("CI") == "true") { + signingConfigs.release.storeFile = file("../keystore") + signingConfigs.release.storePassword = System.getenv("keystore_password") + signingConfigs.release.keyAlias = System.getenv("key_alias") + signingConfigs.release.keyPassword = System.getenv("key_password") + }*/ + } androidExtensions { experimental = true } -import com.android.build.OutputFile - -// For each APK output variant, override versionCode with one that is unique -// https://developer.android.com/studio/build/gradle-tips.html#configure-dynamic-version-codes -android.applicationVariants.all { variant -> - variant.outputs.each { output -> - def baseAbiVersionCode = abiCodes.get(output.getFilter(OutputFile.ABI), 0) - output.versionCodeOverride = baseAbiVersionCode * 1000 + variant.versionCode - } -} - configurations { noAnalyticsDebug noAnalyticsRelease @@ -105,7 +110,7 @@ dependencies { // conductor implementation "com.bluelinelabs:conductor:$conductor_version" - implementation "com.bluelinelabs:conductor-autodispose:$conductor_version" + implementation "com.bluelinelabs:conductor-archlifecycle:$conductor_version" // glide implementation "com.github.bumptech.glide:glide:$glide_version" @@ -122,10 +127,11 @@ dependencies { implementation "com.jakewharton.rxbinding2:rxbinding-support-v4-kotlin:$rxbinding_version" // autodispose - implementation "com.uber.autodispose:autodispose-android-archcomponents-kotlin:$autodispose_version" - implementation "com.uber.autodispose:autodispose-android-archcomponents-test-kotlin:$autodispose_version" - implementation "com.uber.autodispose:autodispose-android-kotlin:$autodispose_version" - implementation "com.uber.autodispose:autodispose-kotlin:$autodispose_version" + implementation "com.uber.autodispose:autodispose-android-archcomponents:$autodispose_version" + implementation "com.uber.autodispose:autodispose-android-archcomponents-test:$autodispose_version" + implementation "com.uber.autodispose:autodispose-android:$autodispose_version" + implementation "com.uber.autodispose:autodispose:$autodispose_version" + implementation "com.uber.autodispose:autodispose-lifecycle:$autodispose_version" // dagger implementation "com.google.dagger:dagger:$dagger_version" @@ -147,10 +153,12 @@ dependencies { kapt "io.realm:realm-annotations-processor:$realm_version" // rxjava - implementation 'com.akaita.java:rxjava2-debug:1.2.2' implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version" implementation "io.reactivex.rxjava2:rxjava:$rxjava_version" implementation "io.reactivex.rxjava2:rxkotlin:$rxkotlin_version" + implementation "com.uber.rxdogtag:rxdogtag:$rxdogtag_version" + implementation "com.uber.rxdogtag:rxdogtag-autodispose:$rxdogtag_version" + // testing androidTestImplementation("androidx.test.espresso:espresso-core:$espresso_version", { @@ -161,20 +169,36 @@ dependencies { testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-core:$mockito_version" + // moshi + implementation "com.squareup.moshi:moshi:$moshi_version" + debugImplementation "com.squareup.moshi:moshi-kotlin:$moshi_version" + kaptRelease "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" + implementation "com.android.billingclient:billing:1.0" implementation "com.github.chrisbanes:PhotoView:2.0.0" implementation "com.f2prateek.rx.preferences2:rx-preferences:$rx_preferences_version" implementation "com.google.android:flexbox:0.3.1" implementation "com.jakewharton.timber:timber:$timber_version" - implementation "com.squareup.moshi:moshi:$moshi_version" + implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" implementation "me.leolin:ShortcutBadger:1.1.21" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation project(":android-smsmms") implementation project(":common") implementation project(':data') implementation project(':domain') + + withAnalyticsImplementation 'com.google.firebase:firebase-core:16.0.9' + withAnalyticsImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' + noAnalyticsDebug project(path: ':data', configuration: 'noAnalyticsDebug') noAnalyticsRelease project(path: ':data', configuration: 'noAnalyticsRelease') withAnalyticsDebug project(path: ':data', configuration: 'withAnalyticsDebug') withAnalyticsRelease project(path: ':data', configuration: 'withAnalyticsRelease') } + +if (getGradle().getStartParameter().getTaskRequests().toString().contains("WithAnalytics")) { + apply plugin: 'io.fabric' + apply plugin: 'com.google.gms.google-services' +} diff --git a/presentation/proguard-rules.pro b/presentation/proguard-rules.pro index 10ac2a92971b5aeae334d8d4f64b3aa32f473ca3..5d87be3fe48b4aec13b7aa43c380105922bbeaa5 100644 --- a/presentation/proguard-rules.pro +++ b/presentation/proguard-rules.pro @@ -15,4 +15,78 @@ -dontwarn org.python.core.** # okio --dontwarn okio.** \ No newline at end of file +-dontwarn okio.** + +# okhttp3 +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform + +# moshi +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +-keepclasseswithmembers class * { + @com.squareup.moshi.* ; +} + +-keep @com.squareup.moshi.JsonQualifier interface * + +# Enum field names are used by the integrated EnumJsonAdapter. +# Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi. +-keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum { + ; +} + +# The name of @JsonClass types is used to look up the generated adapter. +-keepnames @com.squareup.moshi.JsonClass class * + +# Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's +# name. We will look this up reflectively to invoke the type's constructor. +# +# We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard +# matching preceding parameters. +-keepnames class kotlin.jvm.internal.DefaultConstructorMarker +-keepclassmembers @com.squareup.moshi.JsonClass class * { + (...); +} + +# Retain generated JsonAdapters if annotated type is retained. +-if @com.squareup.moshi.JsonClass class * +-keep class <1>JsonAdapter { + (...); + ; +} +-if @com.squareup.moshi.JsonClass class **$* +-keep class <1>_<2>JsonAdapter { + (...); + ; +} +-if @com.squareup.moshi.JsonClass class **$*$* +-keep class <1>_<2>_<3>JsonAdapter { + (...); + ; +} +-if @com.squareup.moshi.JsonClass class **$*$*$* +-keep class <1>_<2>_<3>_<4>JsonAdapter { + (...); + ; +} +-if @com.squareup.moshi.JsonClass class **$*$*$*$* +-keep class <1>_<2>_<3>_<4>_<5>JsonAdapter { + (...); + ; +} +-if @com.squareup.moshi.JsonClass class **$*$*$*$*$* +-keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter { + (...); + ; +} diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index 34634dbc74ab065f950e0dbe5a4224fbee70502c..4bdf9f3f8b668d26fd370dd01467a8fc0578e1f0 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - - - + + @@ -41,7 +41,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppThemeDark"> + android:theme="@style/AppLaunchTheme"> + + android:theme="@style/AppTheme.Black" /> - + + android:theme="@style/AppThemeDialog" + android:windowSoftInputMode="adjustResize" /> @@ -133,6 +135,8 @@ + + @@ -162,6 +166,7 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_info" /> + @@ -190,12 +195,24 @@ - + + + + + \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt index 10239b841336178bbdc54f92ab02f2a113a08910..5109e3f905603a737e6fc88ec09adf8872c287eb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/Navigator.kt @@ -18,6 +18,8 @@ */ package com.moez.QKSMS.common +import android.app.Activity +import android.app.role.RoleManager import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -26,26 +28,32 @@ import android.os.Build import android.provider.ContactsContract import android.provider.Settings import android.provider.Telephony +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import com.moez.QKSMS.BuildConfig +import com.moez.QKSMS.common.util.BillingManager import com.moez.QKSMS.feature.backup.BackupActivity -import com.moez.QKSMS.feature.blocked.BlockedActivity +import com.moez.QKSMS.feature.blocking.BlockingActivity import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.conversationinfo.ConversationInfoActivity import com.moez.QKSMS.feature.gallery.GalleryActivity import com.moez.QKSMS.feature.notificationprefs.NotificationPrefsActivity +import com.moez.QKSMS.feature.plus.PlusActivity import com.moez.QKSMS.feature.scheduled.ScheduledActivity import com.moez.QKSMS.feature.settings.SettingsActivity import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.manager.NotificationManager import com.moez.QKSMS.manager.PermissionManager +import java.io.File import javax.inject.Inject import javax.inject.Singleton - @Singleton class Navigator @Inject constructor( private val context: Context, private val analyticsManager: AnalyticsManager, private val notificationManager: NotificationManager, + private val billingManager: BillingManager, private val permissions: PermissionManager ) { @@ -62,12 +70,29 @@ class Navigator @Inject constructor( } } - fun showDefaultSmsDialog() { - val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) - if (Telephony.Sms.getDefaultSmsPackage(context) != context.packageName) { + /** + * @param source String to indicate where this QKSMS+ screen was launched from. This should be + * one of [main_menu, compose_schedule, settings_night, settings_theme] + */ + fun showQksmsPlusActivity(source: String) { + analyticsManager.track("Viewed QKSMS+", Pair("source", source)) + val intent = Intent(context, PlusActivity::class.java) + startActivity(intent) + } + + /** + * This won't work unless we use startActivityForResult + */ + fun showDefaultSmsDialog(context: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val roleManager = context.getSystemService(RoleManager::class.java) as RoleManager + val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_SMS) + context.startActivityForResult(intent, 42389) + } else { + val intent = Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT) intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, context.packageName) + context.startActivity(intent) } - startActivity(intent) } fun showCompose(body: String? = null, images: List? = null) { @@ -116,14 +141,14 @@ class Navigator @Inject constructor( startActivity(intent) } - fun showSourceCode() { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://gitlab.e.foundation/e/apps/message")) - startActivity(intent) + fun showDeveloper() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti")) + startActivityExternal(intent) } - fun showLicense() { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://gitlab.e.foundation/e/apps/message/blob/e-features/LICENSE")) - startActivity(intent) + fun showSourceCode() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms")) + startActivityExternal(intent) } fun showFork() { @@ -136,8 +161,18 @@ class Navigator @Inject constructor( startActivity(intent) } + fun showChangelog() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/releases")) + startActivityExternal(intent) + } + + fun showLicense() { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/moezbhatti/qksms/blob/master/LICENSE")) + startActivityExternal(intent) + } + fun showBlockedConversations() { - val intent = Intent(context, BlockedActivity::class.java) + val intent = Intent(context, BlockingActivity::class.java) startActivity(intent) } @@ -149,10 +184,9 @@ class Navigator @Inject constructor( fun showDonation() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://bit.ly/QKSMSDonation")) - startActivity(intent) + startActivityExternal(intent) } - fun showRating() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.moez.QKSMS")) .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY @@ -160,19 +194,56 @@ class Navigator @Inject constructor( or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) try { - startActivity(intent) + startActivityExternal(intent) } catch (e: ActivityNotFoundException) { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.moez.QKSMS"))) + val url = "http://play.google.com/store/apps/details?id=com.moez.QKSMS" + startActivityExternal(Intent(Intent.ACTION_VIEW, Uri.parse(url))) } } + /** + * Launch the Play Store and display the Call Control listing + */ + fun installCallControl() { + val url = "https://play.google.com/store/apps/details?id=com.flexaspect.android.everycallcontrol" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivityExternal(intent) + } + /** * Launch the Play Store and display the Should I Answer? listing */ - fun showSia() { - val url = "https://play.google.com/store/apps/details?id=org.mistergroup.shouldianswerpersonal" + fun installSia() { + val url = "https://play.google.com/store/apps/details?id=org.mistergroup.shouldianswer" val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + startActivityExternal(intent) + } + + fun showSupport() { + val intent = Intent(Intent.ACTION_SENDTO) + intent.data = Uri.parse("mailto:") + intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("moez@qklabs.com")) + intent.putExtra(Intent.EXTRA_SUBJECT, "QKSMS Support") + intent.putExtra(Intent.EXTRA_TEXT, StringBuilder("\n\n") + .append("\n\n--- Please write your message above this line ---\n\n") + .append("Package: ${context.packageName}\n") + .append("Version: ${BuildConfig.VERSION_NAME}\n") + .append("Device: ${Build.BRAND} ${Build.MODEL}\n") + .append("SDK: ${Build.VERSION.SDK_INT}\n") + .append("Upgraded" + .takeIf { BuildConfig.FLAVOR != "noAnalytics" } + .takeIf { billingManager.upgradeStatus.blockingFirst() } ?: "") + .toString()) + startActivityExternal(intent) + } + + fun showInvite() { + analyticsManager.track("Clicked Invite") + Intent(Intent.ACTION_SEND) + .setType("text/plain") + .putExtra(Intent.EXTRA_TEXT, "http://qklabs.com/download") + .let { Intent.createChooser(it, null) } + .let(::startActivityExternal) } fun addContact(address: String) { @@ -188,9 +259,19 @@ class Navigator @Inject constructor( startActivityExternal(intent) } - fun saveVcard(uri: Uri) { + fun showContact(lookupKey: String) { + val intent = Intent(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)) + + startActivityExternal(intent) + } + + fun viewFile(file: File) { + val data = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) + val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.name.split(".").last()) val intent = Intent(Intent.ACTION_VIEW) - .setDataAndType(uri, "text/x-vcard") + .setDataAndType(data, type) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) startActivityExternal(intent) } @@ -217,4 +298,4 @@ class Navigator @Inject constructor( -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt index 359daededc5dd033e797c5abb10af1c16b8713c3..bd5eec858de77d3938b767ebf31c5e00a17224cb 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QKApplication.kt @@ -25,15 +25,22 @@ import android.content.BroadcastReceiver import androidx.core.provider.FontRequest import androidx.emoji.text.EmojiCompat import androidx.emoji.text.FontRequestEmojiCompatConfig -import com.akaita.java.rxjava2debug.RxJava2Debug import com.moez.QKSMS.R +import com.moez.QKSMS.common.util.CrashlyticsTree import com.moez.QKSMS.common.util.FileLoggingTree import com.moez.QKSMS.injection.AppComponentManager import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.migration.QkMigration import com.moez.QKSMS.migration.QkRealmMigration import com.moez.QKSMS.util.NightModeManager -import dagger.android.* +import com.uber.rxdogtag.RxDogTag +import com.uber.rxdogtag.autodispose.AutoDisposeConfigurer +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasActivityInjector +import dagger.android.HasBroadcastReceiverInjector +import dagger.android.HasServiceInjector import io.realm.Realm import io.realm.RealmConfiguration import timber.log.Timber @@ -42,10 +49,12 @@ import javax.inject.Inject class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverInjector, HasServiceInjector { /** - * Inject this so that it is forced to initialize + * Inject these so that they are forced to initialize */ @Suppress("unused") @Inject lateinit var analyticsManager: AnalyticsManager + @Suppress("unused") + @Inject lateinit var qkMigration: QkMigration @Inject lateinit var dispatchingActivityInjector: DispatchingAndroidInjector @Inject lateinit var dispatchingBroadcastReceiverInjector: DispatchingAndroidInjector @@ -54,13 +63,9 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn lateinit var fileLoggingTree: FileLoggingTree @Inject lateinit var nightModeManager: NightModeManager - private val packages = arrayOf("com.moez.QKSMS") - override fun onCreate() { super.onCreate() - RxJava2Debug.enableRxJava2AssemblyTracking() - Realm.init(this) Realm.setDefaultConfiguration(RealmConfiguration.Builder() .compactOnLaunch() @@ -85,6 +90,11 @@ class QKApplication : Application(), HasActivityInjector, HasBroadcastReceiverIn EmojiCompat.init(FontRequestEmojiCompatConfig(this, fontRequest)) Timber.plant(Timber.DebugTree(), fileLoggingTree) + Timber.plant(Timber.DebugTree(), CrashlyticsTree(), fileLoggingTree) + + RxDogTag.builder() + .configureWith(AutoDisposeConfigurer::configure) + .install() } override fun activityInjector(): AndroidInjector { diff --git a/presentation/src/main/java/com/moez/QKSMS/common/QkChangeHandler.kt b/presentation/src/main/java/com/moez/QKSMS/common/QkChangeHandler.kt index b898a74e86bdce23bb20069d1520c70e1ac1520a..d18805e1be266dac6b00187875bb096bf548f981 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/QkChangeHandler.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/QkChangeHandler.kt @@ -30,10 +30,16 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler import com.moez.QKSMS.common.util.extensions.dpToPx -class QkChangeHandler(removesFromViewOnPush: Boolean = true) : AnimatorChangeHandler(250, removesFromViewOnPush) { +class QkChangeHandler : AnimatorChangeHandler(250, true) { @NonNull - override fun getAnimator(@NonNull container: ViewGroup, @Nullable from: View?, @Nullable to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator { + override fun getAnimator( + @NonNull container: ViewGroup, + @Nullable from: View?, + @Nullable to: View?, + isPush: Boolean, + toAddedToContainer: Boolean + ): Animator { val animatorSet = AnimatorSet() animatorSet.interpolator = DecelerateInterpolator() @@ -67,7 +73,7 @@ class QkChangeHandler(removesFromViewOnPush: Boolean = true) : AnimatorChangeHan @NonNull override fun copy(): ControllerChangeHandler { - return QkChangeHandler(removesFromViewOnPush()) + return QkChangeHandler() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AndroidLifecycleScopeProvider.java b/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AndroidLifecycleScopeProvider.java deleted file mode 100644 index 7969273219a17cc45baf0312652833f5bfa88445..0000000000000000000000000000000000000000 --- a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AndroidLifecycleScopeProvider.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ - -package com.moez.QKSMS.common.androidxcompat; - -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; -import com.uber.autodispose.LifecycleEndedException; -import com.uber.autodispose.LifecycleScopeProvider; -import io.reactivex.Observable; -import io.reactivex.functions.Function; - -/** - * A {@link LifecycleScopeProvider} that can provide scoping for Android {@link Lifecycle} and - * {@link LifecycleOwner} classes. - *

- *


- *   AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycleOwner))
- * 
- */ -public final class AndroidLifecycleScopeProvider - implements LifecycleScopeProvider { - - private static final Function DEFAULT_CORRESPONDING_EVENTS = - new Function() { - @Override public Lifecycle.Event apply(Lifecycle.Event lastEvent) throws Exception { - switch (lastEvent) { - case ON_CREATE: - return Lifecycle.Event.ON_DESTROY; - case ON_START: - return Lifecycle.Event.ON_STOP; - case ON_RESUME: - return Lifecycle.Event.ON_PAUSE; - case ON_PAUSE: - return Lifecycle.Event.ON_STOP; - case ON_STOP: - case ON_DESTROY: - default: - throw new LifecycleEndedException("Lifecycle has ended! Last event was " + lastEvent); - } - } - }; - - private final Function boundaryResolver; - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android LifecycleOwners. - * - * @param owner the owner to scope for. - * @return a {@link AndroidLifecycleScopeProvider} against this owner. - */ - public static AndroidLifecycleScopeProvider from(LifecycleOwner owner) { - return from(owner.getLifecycle()); - } - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android LifecycleOwners. - * - * @param owner the owner to scope for. - * @param untilEvent the event until the scope is valid. - * @return a {@link AndroidLifecycleScopeProvider} against this owner. - */ - public static AndroidLifecycleScopeProvider from( - LifecycleOwner owner, - Lifecycle.Event untilEvent) { - return from(owner.getLifecycle(), untilEvent); - } - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android Lifecycles. - * - * @param lifecycle the lifecycle to scope for. - * @return a {@link AndroidLifecycleScopeProvider} against this lifecycle. - */ - public static AndroidLifecycleScopeProvider from(Lifecycle lifecycle) { - return from(lifecycle, DEFAULT_CORRESPONDING_EVENTS); - } - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android Lifecycles. - * - * @param lifecycle the lifecycle to scope for. - * @param untilEvent the event until the scope is valid. - * @return a {@link AndroidLifecycleScopeProvider} against this lifecycle. - */ - public static AndroidLifecycleScopeProvider from( - Lifecycle lifecycle, - Lifecycle.Event untilEvent) { - return from(lifecycle, new UntilEventFunction(untilEvent)); - } - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android Lifecycles. - * - * @param owner the owner to scope for. - * @param boundaryResolver function that resolves the event boundary. - * @return a {@link AndroidLifecycleScopeProvider} against this owner. - */ - public static AndroidLifecycleScopeProvider from( - LifecycleOwner owner, - Function boundaryResolver) { - return from(owner.getLifecycle(), boundaryResolver); - } - - /** - * Creates a {@link AndroidLifecycleScopeProvider} for Android Lifecycles. - * - * @param lifecycle the lifecycle to scope for. - * @param boundaryResolver function that resolves the event boundary. - * @return a {@link AndroidLifecycleScopeProvider} against this lifecycle. - */ - public static AndroidLifecycleScopeProvider from( - Lifecycle lifecycle, - Function boundaryResolver) { - return new AndroidLifecycleScopeProvider(lifecycle, boundaryResolver); - } - - private final LifecycleEventsObservable lifecycleObservable; - - private AndroidLifecycleScopeProvider(Lifecycle lifecycle, - Function boundaryResolver) { - this.lifecycleObservable = new LifecycleEventsObservable(lifecycle); - this.boundaryResolver = boundaryResolver; - } - - @Override public Observable lifecycle() { - return lifecycleObservable; - } - - @Override public Function correspondingEvents() { - return boundaryResolver; - } - - @Override public Lifecycle.Event peekLifecycle() { - lifecycleObservable.backfillEvents(); - return lifecycleObservable.getValue(); - } - - private static class UntilEventFunction implements Function { - private final Lifecycle.Event untilEvent; - - UntilEventFunction(Lifecycle.Event untilEvent) { - this.untilEvent = untilEvent; - } - - @Override public Lifecycle.Event apply(Lifecycle.Event event) throws Exception { - return untilEvent; - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AutodisposeAndroidLifecycle.kt b/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AutodisposeAndroidLifecycle.kt deleted file mode 100644 index 49e05b7f173f03f499ebaf16dc1f8150b214e5bc..0000000000000000000000000000000000000000 --- a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/AutodisposeAndroidLifecycle.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.common.androidxcompat - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.Lifecycle.Event -import androidx.lifecycle.LifecycleOwner -import com.uber.autodispose.LifecycleScopeProvider -import io.reactivex.annotations.CheckReturnValue -import io.reactivex.functions.Function - -/** - * Extension that returns a [LifecycleScopeProvider] for this [LifecycleOwner]. - */ -@CheckReturnValue -inline fun LifecycleOwner.scope(): LifecycleScopeProvider<*> = AndroidLifecycleScopeProvider.from(this) - -/** - * Extension that returns a [LifecycleScopeProvider] for this [LifecycleOwner]. - * - * @param untilEvent the event until the scope is valid. - */ -@CheckReturnValue -inline fun LifecycleOwner.scope(untilEvent: Lifecycle.Event): LifecycleScopeProvider<*> = - AndroidLifecycleScopeProvider.from(this, untilEvent) - -/** - * Extension that returns a [LifecycleScopeProvider] for this [LifecycleOwner]. - * - * @param boundaryResolver function that resolves the event boundary. - */ -@CheckReturnValue -inline fun LifecycleOwner.scope(boundaryResolver: Function): LifecycleScopeProvider<*> = - AndroidLifecycleScopeProvider.from(this, boundaryResolver) - -/** - * Extension that returns a [LifecycleScopeProvider] for this [Lifecycle]. - */ -@CheckReturnValue -inline fun Lifecycle.scope(): LifecycleScopeProvider<*> = AndroidLifecycleScopeProvider.from(this) - -/** - * Extension that returns a [LifecycleScopeProvider] for this [Lifecycle]. - * - * @param untilEvent the event until the scope is valid. - */ -@CheckReturnValue -inline fun Lifecycle.scope(untilEvent: Lifecycle.Event): LifecycleScopeProvider<*> = - AndroidLifecycleScopeProvider.from(this, untilEvent) - -/** - * Extension that returns a [LifecycleScopeProvider] for this [Lifecycle]. - * - * @param boundaryResolver function that resolves the event boundary. - */ -@CheckReturnValue -inline fun Lifecycle.scope(boundaryResolver: Function): LifecycleScopeProvider<*> = - AndroidLifecycleScopeProvider.from(this, boundaryResolver) \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/LifecycleEventsObservable.java b/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/LifecycleEventsObservable.java deleted file mode 100644 index c11ca095b30321f988fe1510e82f0057c04342b9..0000000000000000000000000000000000000000 --- a/presentation/src/main/java/com/moez/QKSMS/common/androidxcompat/LifecycleEventsObservable.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ - -package com.moez.QKSMS.common.androidxcompat; - -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.Lifecycle.Event; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.OnLifecycleEvent; -import io.reactivex.Observable; -import io.reactivex.Observer; -import io.reactivex.android.MainThreadDisposable; -import io.reactivex.subjects.BehaviorSubject; - -import static androidx.annotation.RestrictTo.Scope.LIBRARY; -import static androidx.lifecycle.Lifecycle.Event.ON_CREATE; -import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY; -import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; -import static androidx.lifecycle.Lifecycle.Event.ON_START; -import static com.uber.autodispose.android.internal.AutoDisposeAndroidUtil.isMainThread; - -@RestrictTo(LIBRARY) class LifecycleEventsObservable extends Observable { - - private final Lifecycle lifecycle; - private final BehaviorSubject eventsObservable = BehaviorSubject.create(); - - @SuppressWarnings("CheckReturnValue") LifecycleEventsObservable(Lifecycle lifecycle) { - this.lifecycle = lifecycle; - } - - Event getValue() { - return eventsObservable.getValue(); - } - - /** - * Backfill if already created for boundary checking. We do a trick here for corresponding events - * where we pretend something is created upon initialized state so that it assumes the - * corresponding event is DESTROY. - */ - void backfillEvents() { - @Nullable Lifecycle.Event correspondingEvent; - switch (lifecycle.getCurrentState()) { - case INITIALIZED: - correspondingEvent = ON_CREATE; - break; - case CREATED: - correspondingEvent = ON_START; - break; - case STARTED: - case RESUMED: - correspondingEvent = ON_RESUME; - break; - case DESTROYED: - default: - correspondingEvent = ON_DESTROY; - break; - } - eventsObservable.onNext(correspondingEvent); - } - - @Override protected void subscribeActual(Observer observer) { - ArchLifecycleObserver archObserver = - new ArchLifecycleObserver(lifecycle, observer, eventsObservable); - observer.onSubscribe(archObserver); - if (!isMainThread()) { - observer.onError( - new IllegalStateException("Lifecycles can only be bound to on the main thread!")); - return; - } - lifecycle.addObserver(archObserver); - if (archObserver.isDisposed()) { - lifecycle.removeObserver(archObserver); - } - } - - static final class ArchLifecycleObserver extends MainThreadDisposable - implements LifecycleObserver { - private final Lifecycle lifecycle; - private final Observer observer; - private final BehaviorSubject eventsObservable; - - ArchLifecycleObserver(Lifecycle lifecycle, Observer observer, - BehaviorSubject eventsObservable) { - this.lifecycle = lifecycle; - this.observer = observer; - this.eventsObservable = eventsObservable; - } - - @Override protected void onDispose() { - lifecycle.removeObserver(this); - } - - @OnLifecycleEvent(Event.ON_ANY) - void onStateChange(@SuppressWarnings("unused") LifecycleOwner owner, Event event) { - if (!isDisposed()) { - if (!(event == ON_CREATE && eventsObservable.getValue() == event)) { - // Due to the INITIALIZED->ON_CREATE mapping trick we do in backfill(), - // we fire this conditionally to avoid duplicate CREATE events. - eventsObservable.onNext(event); - } - observer.onNext(event); - } - } - } -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt index 9c56f119cc27414fa48c223c1dbe019175a08cae..deefa8e216ea7c682324425d95d02e911e338505 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkController.kt @@ -24,15 +24,12 @@ import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity -import com.bluelinelabs.conductor.Controller -import com.bluelinelabs.conductor.autodispose.ControllerEvent -import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider -import com.uber.autodispose.LifecycleScopeProvider +import com.bluelinelabs.conductor.archlifecycle.LifecycleController import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.* import kotlinx.android.synthetic.main.toolbar.view.* -abstract class QkController, State, Presenter : QkPresenter> : Controller(), LayoutContainer { +abstract class QkController, State, Presenter : QkPresenter> : LifecycleController(), LayoutContainer { abstract var presenter: Presenter @@ -80,8 +77,4 @@ abstract class QkController, State, Present presenter.onCleared() } - fun scope(): LifecycleScopeProvider { - return ControllerScopeProvider.from(this) - } - -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt index d2b64c46088013c0e170bff6a4155fa9527d9fa0..7d152ffeefa4a38aabf163707bc160e0e05a2472 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt @@ -19,7 +19,8 @@ package com.moez.QKSMS.common.base import androidx.annotation.CallSuper -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.BehaviorSubject diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt index 6934ead8ca4b2e2083365779cff0fe4d958d4267..7b1d8997ec7825a7ac70d6823df2abe834c81672 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkRealmAdapter.kt @@ -50,7 +50,7 @@ abstract class QkRealmAdapter : RealmRecyclerViewAdapter> = BehaviorSubject.create() - private val selection = mutableListOf() + private var selection = listOf() /** * Toggles the selected state for a particular view @@ -61,9 +61,9 @@ abstract class QkRealmAdapter : RealmRecyclerViewAdapter selection.remove(id) - false -> selection.add(id) + selection = when (selection.contains(id)) { + true -> selection - id + false -> selection + id } selectionChanges.onNext(selection) @@ -75,7 +75,7 @@ abstract class QkRealmAdapter : RealmRecyclerViewAdapter 0 - Build.VERSION.SDK_INT < Build.VERSION_CODES.O -> View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - else -> View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + // We can only set light nav bar on API 27 in attrs, but we can do it in API 26 here + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { + val night = !resolveThemeBoolean(R.attr.isLightTheme) + window.decorView.systemUiVisibility = if (night) 0 else + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR } // Some devices don't let you modify android.R.attr.navigationBarColor @@ -127,10 +125,9 @@ abstract class QkThemedActivity : QkActivity() { /** * This can be overridden in case an activity does not want to use the default themes */ - open fun getActivityThemeRes(night: Boolean, black: Boolean) = when { - night && black -> R.style.AppThemeBlack - night && !black -> R.style.AppThemeDark - else -> R.style.AppThemeLight + open fun getActivityThemeRes(black: Boolean) = when { + black -> R.style.AppTheme_Black + else -> R.style.AppTheme } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewContract.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewContract.kt index f50bb95c7a35d76a50a412968bf002c79df72b2b..19ade510c08830b61421080ca1f6a6a5bae9e733 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewContract.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewContract.kt @@ -18,13 +18,10 @@ */ package com.moez.QKSMS.common.base -import com.bluelinelabs.conductor.autodispose.ControllerEvent -import com.uber.autodispose.LifecycleScopeProvider +import androidx.lifecycle.LifecycleOwner -interface QkViewContract { +interface QkViewContract: LifecycleOwner { fun render(state: State) - fun scope(): LifecycleScopeProvider - } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewModel.kt index e73b4a25193909e72bde0f1c74696d2bce225441..c59e75afd418c7262a033e0fc89b38e77ad1bf17 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkViewModel.kt @@ -20,8 +20,8 @@ package com.moez.QKSMS.common.base import androidx.annotation.CallSuper import androidx.lifecycle.ViewModel -import com.moez.QKSMS.common.androidxcompat.scope -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.subjects.BehaviorSubject diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..69f0c0654661488513d346b93209a291e7b2f252 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/BillingManager.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.util + +import android.app.Activity +import android.content.Context +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClient.BillingResponse +import com.android.billingclient.api.BillingClient.SkuType +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingFlowParams +import com.android.billingclient.api.Purchase +import com.android.billingclient.api.PurchasesUpdatedListener +import com.android.billingclient.api.SkuDetails +import com.android.billingclient.api.SkuDetailsParams +import com.moez.QKSMS.BuildConfig +import com.moez.QKSMS.manager.AnalyticsManager +import io.reactivex.Flowable +import io.reactivex.Observable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.Subject +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class BillingManager @Inject constructor( + context: Context, + private val analyticsManager: AnalyticsManager +) : PurchasesUpdatedListener { + + companion object { + const val SKU_PLUS = "remove_ads" + const val SKU_PLUS_DONATE = "qksms_plus_donate" + } + + val products: Observable> = BehaviorSubject.create() + val upgradeStatus: Observable + + private val skus = listOf(SKU_PLUS, SKU_PLUS_DONATE) + private val purchaseListObservable = BehaviorSubject.create>() + + private val billingClient: BillingClient = BillingClient.newBuilder(context).setListener(this).build() + private var isServiceConnected = false + + init { + startServiceConnection { + queryPurchases() + querySkuDetailsAsync() + } + + upgradeStatus = when (BuildConfig.FLAVOR) { + "noAnalytics" -> BehaviorSubject.createDefault(true) + + else -> purchaseListObservable + .map { purchases -> purchases.any { it.sku == SKU_PLUS } || purchases.any { it.sku == SKU_PLUS_DONATE } } + .doOnNext { upgraded -> analyticsManager.setUserProperty("Upgraded", upgraded) } + } + } + + private fun queryPurchases() { + executeServiceRequest { + // Load the cached data + purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) + + // On a fresh device, the purchase might not be cached, and so we'll need to force a refresh + billingClient.queryPurchaseHistoryAsync(SkuType.INAPP) { _, _ -> + purchaseListObservable.onNext(billingClient.queryPurchases(SkuType.INAPP).purchasesList.orEmpty()) + } + } + } + + + private fun startServiceConnection(onSuccess: () -> Unit) { + val listener = object : BillingClientStateListener { + override fun onBillingSetupFinished(@BillingResponse billingResponseCode: Int) { + if (billingResponseCode == BillingResponse.OK) { + isServiceConnected = true + onSuccess() + } else { + Timber.w("Billing response: $billingResponseCode") + purchaseListObservable.onNext(listOf()) + } + } + + override fun onBillingServiceDisconnected() { + isServiceConnected = false + } + } + + Flowable.fromCallable { billingClient.startConnection(listener) } + .subscribeOn(Schedulers.io()) + .subscribe() + } + + private fun querySkuDetailsAsync() { + executeServiceRequest { + val subParams = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP) + billingClient.querySkuDetailsAsync(subParams.build()) { responseCode, skuDetailsList -> + if (responseCode == BillingResponse.OK) { + (products as Subject).onNext(skuDetailsList) + } + } + } + } + + fun initiatePurchaseFlow(activity: Activity, sku: String) { + executeServiceRequest { + val params = BillingFlowParams.newBuilder().setSku(sku).setType(SkuType.INAPP) + billingClient.launchBillingFlow(activity, params.build()) + } + } + + private fun executeServiceRequest(runnable: () -> Unit) { + when (isServiceConnected) { + true -> runnable() + false -> startServiceConnection(runnable) + } + } + + override fun onPurchasesUpdated(resultCode: Int, purchases: List?) { + if (resultCode == BillingResponse.OK) { + purchaseListObservable.onNext(purchases.orEmpty()) + } + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/ClipboardUtils.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/ClipboardUtils.kt index 53eb0996539649aa61b2644c311a48380c9f8999..6e6e01396f5ec0ca9be1669a02d4bbdce4fb6e65 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/ClipboardUtils.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/ClipboardUtils.kt @@ -27,7 +27,7 @@ object ClipboardUtils { fun copy(context: Context, string: String) { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("SMS", string) - clipboard.primaryClip = clip + clipboard.setPrimaryClip(clip) } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt index e5361d4152748528240ae0a1f402b331916a9fb6..1cc8ada1b9528c895b12831cfb3b7aa3c84de148 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/NotificationManagerImpl.kt @@ -29,14 +29,12 @@ import android.graphics.Color import android.net.Uri import android.os.Build import android.provider.ContactsContract -import android.telephony.PhoneNumberUtils import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person import androidx.core.app.RemoteInput import androidx.core.app.TaskStackBuilder import androidx.core.graphics.drawable.IconCompat -import androidx.core.graphics.get import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.extensions.isImage @@ -48,10 +46,10 @@ import com.moez.QKSMS.receiver.DeleteMessagesReceiver import com.moez.QKSMS.receiver.MarkReadReceiver import com.moez.QKSMS.receiver.MarkSeenReceiver import com.moez.QKSMS.receiver.RemoteMessagingReceiver -import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.util.GlideApp +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import com.moez.QKSMS.util.tryOrNull import javax.inject.Inject @@ -64,7 +62,8 @@ class NotificationManagerImpl @Inject constructor( private val conversationRepo: ConversationRepository, private val prefs: Preferences, private val messageRepo: MessageRepository, - private val permissions: PermissionManager + private val permissions: PermissionManager, + private val phoneNumberUtils: PhoneNumberUtils ) : com.moez.QKSMS.manager.NotificationManager { companion object { @@ -151,14 +150,14 @@ class NotificationManagerImpl @Inject constructor( if (!message.isMe()) { val recipient = conversation.recipients - .firstOrNull { PhoneNumberUtils.compare(it.address, message.address) } + .firstOrNull { phoneNumberUtils.compare(it.address, message.address) } person.setName(recipient?.getDisplayName() ?: message.address) person.setIcon(GlideApp.with(context) .asBitmap() .circleCrop() - .load(PhoneNumberUtils.stripSeparators(message.address)) + .load("tel:${message.address}") .submit(64.dpToPx(context), 64.dpToPx(context)) .let { futureGet -> tryOrNull(false) { futureGet.get() } } ?.let(IconCompat::createWithBitmap)) @@ -183,7 +182,7 @@ class NotificationManagerImpl @Inject constructor( GlideApp.with(context) .asBitmap() .circleCrop() - .load(PhoneNumberUtils.stripSeparators(address)) + .load("tel:$address") .submit(64.dpToPx(context), 64.dpToPx(context)) } ?.let { futureGet -> tryOrNull(false) { futureGet.get() } } @@ -212,9 +211,9 @@ class NotificationManagerImpl @Inject constructor( // Add all of the people from this conversation to the notification, so that the system can // appropriately bypass DND mode - conversation.recipients - .mapNotNull { recipient -> recipient.contact?.lookupKey } - .forEach { uri -> notification.addPerson(uri) } + conversation.recipients.forEach { recipient -> + notification.addPerson("tel:${recipient.address}") + } // Add the action buttons val actionLabels = context.resources.getStringArray(R.array.notification_actions) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/QkChooserTargetService.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/QkChooserTargetService.kt index c0c017f9173b12571b248483a88f55b745d1aaaa..0d97a67541cda01e373b92338d350a29bbd38858 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/QkChooserTargetService.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/QkChooserTargetService.kt @@ -24,7 +24,6 @@ import android.graphics.drawable.Icon import android.os.Build import android.service.chooser.ChooserTarget import android.service.chooser.ChooserTargetService -import android.telephony.PhoneNumberUtils import androidx.annotation.RequiresApi import androidx.core.os.bundleOf import com.moez.QKSMS.R @@ -59,7 +58,7 @@ class QkChooserTargetService : ChooserTargetService() { val request = GlideApp.with(this) .asBitmap() .circleCrop() - .load(PhoneNumberUtils.stripSeparators(address)) + .load("tel:$address") .submit() val bitmap = tryOrNull(false) { request.get() } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/ShortcutManagerImpl.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/ShortcutManagerImpl.kt index 087922248cf71207a92bf1f6760cffe41fa2f1d6..7493b02f08a1e8e41eb0deac3f2a884e757c19e7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/ShortcutManagerImpl.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/ShortcutManagerImpl.kt @@ -25,7 +25,6 @@ import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Build -import android.telephony.PhoneNumberUtils import com.moez.QKSMS.R import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.model.Conversation @@ -66,7 +65,7 @@ class ShortcutManagerImpl @Inject constructor( val request = GlideApp.with(context) .asBitmap() .circleCrop() - .load(PhoneNumberUtils.stripSeparators(address)) + .load("tel:$address") .submit(shortcutManager.iconMaxWidth, shortcutManager.iconMaxHeight) val bitmap = tryOrNull(false) { request.get() } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/TextViewStyler.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/TextViewStyler.kt index 92c14d5ab0ecf4a39320e5918f446672529e9acc..ccfd1f99da0f07fa256abbfe81d20edcf6d05958 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/util/TextViewStyler.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/TextViewStyler.kt @@ -19,7 +19,9 @@ package com.moez.QKSMS.common.util import android.graphics.Typeface +import android.os.Build import android.util.AttributeSet +import android.widget.EditText import android.widget.TextView import com.moez.QKSMS.R import com.moez.QKSMS.common.util.TextViewStyler.Companion.SIZE_PRIMARY @@ -27,12 +29,13 @@ import com.moez.QKSMS.common.util.TextViewStyler.Companion.SIZE_SECONDARY import com.moez.QKSMS.common.util.TextViewStyler.Companion.SIZE_TERTIARY import com.moez.QKSMS.common.util.TextViewStyler.Companion.SIZE_TOOLBAR import com.moez.QKSMS.common.util.extensions.getColorCompat -import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.widget.QkEditText import com.moez.QKSMS.common.widget.QkTextView import com.moez.QKSMS.util.Preferences import javax.inject.Inject + + class TextViewStyler @Inject constructor( private val prefs: Preferences, private val colors: Colors, @@ -40,18 +43,17 @@ class TextViewStyler @Inject constructor( ) { companion object { - const val COLOR_PRIMARY = 0 - const val COLOR_SECONDARY = 1 - const val COLOR_TERTIARY = 2 - const val COLOR_PRIMARY_ON_THEME = 3 - const val COLOR_SECONDARY_ON_THEME = 4 - const val COLOR_TERTIARY_ON_THEME = 5 - const val COLOR_THEME = 6 + const val COLOR_THEME = 0 + const val COLOR_PRIMARY_ON_THEME = 1 + const val COLOR_SECONDARY_ON_THEME = 2 + const val COLOR_TERTIARY_ON_THEME = 3 const val SIZE_PRIMARY = 0 const val SIZE_SECONDARY = 1 const val SIZE_TERTIARY = 2 const val SIZE_TOOLBAR = 3 + const val SIZE_DIALOG = 4 + const val SIZE_EMOJI = 5 fun applyEditModeAttributes(textView: TextView, attrs: AttributeSet?) { textView.run { @@ -74,9 +76,6 @@ class TextViewStyler @Inject constructor( else -> return } setTextColor(when (colorAttr) { - COLOR_PRIMARY -> context.getColorCompat(R.color.textPrimary) - COLOR_SECONDARY -> context.getColorCompat(R.color.textSecondary) - COLOR_TERTIARY -> context.getColorCompat(R.color.textTertiary) COLOR_PRIMARY_ON_THEME -> context.getColorCompat(R.color.textPrimaryDark) COLOR_SECONDARY_ON_THEME -> context.getColorCompat(R.color.textSecondaryDark) COLOR_TERTIARY_ON_THEME -> context.getColorCompat(R.color.textTertiaryDark) @@ -89,6 +88,8 @@ class TextViewStyler @Inject constructor( SIZE_SECONDARY -> 14f SIZE_TERTIARY -> 12f SIZE_TOOLBAR -> 20f + SIZE_DIALOG -> 18f + SIZE_EMOJI -> 32f else -> textSize / paint.density } } @@ -96,44 +97,45 @@ class TextViewStyler @Inject constructor( } fun applyAttributes(textView: TextView, attrs: AttributeSet?) { - textView.run { - var colorAttr = 0 - var textSizeAttr = 0 + var colorAttr = 0 + var textSizeAttr = 0 - if (!prefs.systemFont.get()) { - fontProvider.getLato { lato -> - setTypeface(lato, typeface?.style ?: Typeface.NORMAL) - } + if (!prefs.systemFont.get()) { + fontProvider.getLato { lato -> + textView.setTypeface(lato, textView.typeface?.style ?: Typeface.NORMAL) } + } - when (this) { - is QkTextView -> context.obtainStyledAttributes(attrs, R.styleable.QkTextView)?.run { - colorAttr = getInt(R.styleable.QkTextView_textColor, -1) - textSizeAttr = getInt(R.styleable.QkTextView_textSize, -1) - recycle() - } - - is QkEditText -> context.obtainStyledAttributes(attrs, R.styleable.QkEditText)?.run { - colorAttr = getInt(R.styleable.QkEditText_textColor, -1) - textSizeAttr = getInt(R.styleable.QkEditText_textSize, -1) - recycle() - } + when (textView) { + is QkTextView -> textView.context.obtainStyledAttributes(attrs, R.styleable.QkTextView).run { + colorAttr = getInt(R.styleable.QkTextView_textColor, -1) + textSizeAttr = getInt(R.styleable.QkTextView_textSize, -1) + recycle() + } - else -> return + is QkEditText -> textView.context.obtainStyledAttributes(attrs, R.styleable.QkEditText).run { + colorAttr = getInt(R.styleable.QkEditText_textColor, -1) + textSizeAttr = getInt(R.styleable.QkEditText_textSize, -1) + recycle() } - setTextColor(when (colorAttr) { - COLOR_PRIMARY -> context.resolveThemeColor(android.R.attr.textColorPrimary) - COLOR_SECONDARY -> context.resolveThemeColor(android.R.attr.textColorSecondary) - COLOR_TERTIARY -> context.resolveThemeColor(android.R.attr.textColorTertiary) - COLOR_PRIMARY_ON_THEME -> colors.theme().textPrimary - COLOR_SECONDARY_ON_THEME -> colors.theme().textSecondary - COLOR_TERTIARY_ON_THEME -> colors.theme().textTertiary - COLOR_THEME -> colors.theme().theme - else -> currentTextColor - }) - - setTextSize(textView, textSizeAttr) + else -> return + } + + when (colorAttr) { + COLOR_THEME -> textView.setTextColor(colors.theme().theme) + COLOR_PRIMARY_ON_THEME -> textView.setTextColor(colors.theme().textPrimary) + COLOR_SECONDARY_ON_THEME -> textView.setTextColor(colors.theme().textSecondary) + COLOR_TERTIARY_ON_THEME -> textView.setTextColor(colors.theme().textTertiary) + } + + setTextSize(textView, textSizeAttr) + + if (textView is EditText) { + val drawable = textView.resources.getDrawable(R.drawable.cursor).apply { setTint(colors.theme().theme) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + textView.textCursorDrawable = drawable + } } } @@ -177,6 +179,22 @@ class TextViewStyler @Inject constructor( Preferences.TEXT_SIZE_LARGER -> 26f else -> 20f } + + SIZE_DIALOG -> textView.textSize = when (textSizePref) { + Preferences.TEXT_SIZE_SMALL -> 16f + Preferences.TEXT_SIZE_NORMAL -> 18f + Preferences.TEXT_SIZE_LARGE -> 20f + Preferences.TEXT_SIZE_LARGER -> 24f + else -> 18f + } + + SIZE_EMOJI -> textView.textSize = when (textSizePref) { + Preferences.TEXT_SIZE_SMALL -> 28f + Preferences.TEXT_SIZE_NORMAL -> 32f + Preferences.TEXT_SIZE_LARGE -> 36f + Preferences.TEXT_SIZE_LARGER -> 40f + else -> 32f + } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt new file mode 100644 index 0000000000000000000000000000000000000000..83c103adfabb521d65cfe126d710c5f43b7acae7 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/util/extensions/ActivityExtensions.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.util.extensions + +import android.app.Activity +import android.content.Context +import android.view.inputmethod.InputMethodManager + +fun Activity.dismissKeyboard() { + window.currentFocus?.let { focus -> + val imm = getSystemService( + Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(focus.windowToken, 0) + + focus.clearFocus() + } +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 1f12c5575fbe4d4b8f12f624c51a73bc7dcf2b8a..ba4e854a36915cc1077a476de6fd83d7fc0bda6e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -19,31 +19,25 @@ package com.moez.QKSMS.common.widget import android.content.Context -import android.net.Uri -import android.provider.ContactsContract -import android.telephony.PhoneNumberUtils import android.util.AttributeSet import android.view.View import android.widget.FrameLayout +import com.bumptech.glide.signature.ObjectKey import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent -import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.util.GlideApp -import com.uber.autodispose.android.ViewScopeProvider -import com.uber.autodispose.kotlin.autoDisposable import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { @Inject lateinit var colors: Colors - @Inject lateinit var contactAddedListener: ContactAddedListener @Inject lateinit var navigator: Navigator /** @@ -59,6 +53,7 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet private var lookupKey: String? = null private var name: String? = null private var address: String? = null + private var lastUpdated: Long? = null init { if (!isInEditMode) { @@ -69,24 +64,6 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet setBackgroundResource(R.drawable.circle) clipToOutline = true - - setOnClickListener { - if (lookupKey.isNullOrEmpty()) { - address?.let { address -> - // Allow the user to add the contact - navigator.addContact(address) - - // Listen for contact changes - contactAddedListener.listen(address) - .autoDisposable(ViewScopeProvider.from(this)) - .subscribe() - } - } else { - val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) - ContactsContract.QuickContact.showQuickContact(context, this@AvatarView, uri, - ContactsContract.QuickContact.MODE_MEDIUM, null) - } - } } /** @@ -103,6 +80,7 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet lookupKey = null name = null address = recipient?.address + lastUpdated = 0 updateView() } @@ -115,6 +93,7 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet name = contact?.name // If a contactAddress has been given, we use it. Use the contact address otherwise. address = contactAddress ?: contact?.numbers?.firstOrNull()?.address + lastUpdated = contact?.lastUpdate updateView() } @@ -146,7 +125,10 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet photo.setImageDrawable(null) address?.let { address -> - GlideApp.with(photo).load(PhoneNumberUtils.stripSeparators(address)).into(photo) + GlideApp.with(photo) + .load("tel:$address") + .signature(ObjectKey(lastUpdated ?: 0L)) + .into(photo) } } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..1649edf68f18aef8ae1d02341d1d6b665b36de88 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/FieldDialog.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.widget + +import android.app.Activity +import android.content.DialogInterface +import android.view.LayoutInflater +import androidx.appcompat.app.AlertDialog +import com.moez.QKSMS.R +import kotlinx.android.synthetic.main.field_dialog.view.* + +class FieldDialog(context: Activity, hint: String, listener: (String) -> Unit) : AlertDialog(context) { + + private val layout = LayoutInflater.from(context).inflate(R.layout.field_dialog, null) + + init { + layout.field.hint = hint + + setView(layout) + setButton(DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.button_cancel)) { _, _ -> } + setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.button_delete)) { _, _ -> listener("") } + setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.button_save)) { _, _ -> + listener(layout.field.text.toString()) + } + } + + fun setText(text: String): FieldDialog { + layout.field.setText(text) + return this + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt index 870752b46b09b321ee9ec84f232f40b68528cf7f..3f2ff65feb7e3b7860c5625a04832e49da410646 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PagerTitleView.kt @@ -31,7 +31,7 @@ import com.moez.QKSMS.common.util.extensions.forEach import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.injection.appComponent import com.uber.autodispose.android.ViewScopeProvider -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.autoDisposable import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.tab_view.view.* diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt index d905c66c5e893120711011d60c04328deb77f7fd..d26a0357030b9e0a190639eebdc464eb27084cfd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/PreferenceView.kt @@ -26,13 +26,13 @@ import android.widget.TextView import androidx.appcompat.widget.LinearLayoutCompat import com.moez.QKSMS.R import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute -import com.moez.QKSMS.common.util.extensions.resolveThemeColor -import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.injection.appComponent import kotlinx.android.synthetic.main.preference_view.view.* -class PreferenceView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayoutCompat(context, attrs) { +class PreferenceView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : LinearLayoutCompat(context, attrs) { var title: String? = null set(value) { @@ -71,9 +71,6 @@ class PreferenceView @JvmOverloads constructor(context: Context, attrs: Attribut orientation = HORIZONTAL gravity = Gravity.CENTER_VERTICAL - val textSecondary = context.resolveThemeColor(android.R.attr.textColorSecondary) - icon.setTint(textSecondary) - context.obtainStyledAttributes(attrs, R.styleable.PreferenceView)?.run { title = getString(R.styleable.PreferenceView_title) summary = getString(R.styleable.PreferenceView_summary) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkSwitch.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkSwitch.kt index e58a7bf00bdc4e4728e1af17be2b9e8264a9a775..500377d84177e754815dfca68108a197d266a419 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkSwitch.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkSwitch.kt @@ -24,7 +24,7 @@ import android.util.AttributeSet import androidx.appcompat.widget.SwitchCompat import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors -import com.moez.QKSMS.common.util.extensions.getColorCompat +import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.withAlpha import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.util.Preferences @@ -50,26 +50,15 @@ class QkSwitch @JvmOverloads constructor(context: Context, attrs: AttributeSet? intArrayOf(android.R.attr.state_checked), intArrayOf()) - val themeColor = colors.theme().theme + thumbTintList = ColorStateList(states, intArrayOf( + context.resolveThemeColor(R.attr.switchThumbDisabled), + colors.theme().theme, + context.resolveThemeColor(R.attr.switchThumbEnabled))) - val switchThumbEnabled: Int = prefs.night.get() - .let { night -> if (night) R.color.switchThumbEnabledDark else R.color.switchThumbEnabledLight } - .let { res -> context.getColorCompat(res) } - - val switchThumbDisabled: Int = prefs.night.get() - .let { night -> if (night) R.color.switchThumbDisabledDark else R.color.switchThumbDisabledLight } - .let { res -> context.getColorCompat(res) } - - val switchTrackEnabled: Int = prefs.night.get() - .let { night -> if (night) R.color.switchTrackEnabledDark else R.color.switchTrackEnabledLight } - .let { res -> context.getColorCompat(res) } - - val switchTrackDisabled: Int = prefs.night.get() - .let { night -> if (night) R.color.switchTrackDisabledDark else R.color.switchTrackDisabledLight } - .let { res -> context.getColorCompat(res) } - - thumbTintList = ColorStateList(states, intArrayOf(switchThumbDisabled, themeColor, switchThumbEnabled)) - trackTintList = ColorStateList(states, intArrayOf(switchTrackDisabled, themeColor.withAlpha(0x4D), switchTrackEnabled)) + trackTintList = ColorStateList(states, intArrayOf( + context.resolveThemeColor(R.attr.switchTrackDisabled), + colors.theme().theme.withAlpha(0x4D), + context.resolveThemeColor(R.attr.switchTrackEnabled))) } } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkTextView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkTextView.kt index 36c909003468cf5a0ef49b661ddec91407e006c6..bc771a1cff35b5422a0d71a82423144c95a23cdd 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/QkTextView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/QkTextView.kt @@ -25,11 +25,26 @@ import com.moez.QKSMS.common.util.TextViewStyler import com.moez.QKSMS.injection.appComponent import javax.inject.Inject -open class QkTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) - : EmojiAppCompatTextView(context, attrs) { +open class QkTextView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : EmojiAppCompatTextView(context, attrs) { @Inject lateinit var textViewStyler: TextViewStyler + /** + * Collapse a multiline list of strings into a single line + * + * Ex. + * + * Toronto, New York, Los Angeles, + * Seattle, Portland + * + * Will be converted to + * + * Toronto, New York, Los Angeles, +2 + */ + var collapseEnabled: Boolean = false + init { if (!isInEditMode) { appComponent.inject(this) @@ -39,6 +54,23 @@ open class QkTextView @JvmOverloads constructor(context: Context, attrs: Attribu } } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + + if (collapseEnabled) { + layout + ?.takeIf { layout -> layout.lineCount > 0 } + ?.let { layout -> layout.getEllipsisCount(layout.lineCount - 1) } + ?.takeIf { ellipsisCount -> ellipsisCount > 0 } + ?.let { ellipsisCount -> text.dropLast(ellipsisCount).lastIndexOf(',') } + ?.takeIf { lastComma -> lastComma >= 0 } + ?.let { lastComma -> + val remainingNames = text.drop(lastComma).count { c -> c == ',' } + text = "${text.take(lastComma)}, +$remainingNames" + } + } + } + override fun setTextColor(color: Int) { super.setTextColor(color) setLinkTextColor(color) diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f129c1af7cfb81e91f419b6fe7a634ffccb8fd0 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/RadioPreferenceView.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.common.widget + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.View +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import com.moez.QKSMS.R +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.forwardTouches +import com.moez.QKSMS.common.util.extensions.resolveThemeAttribute +import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.injection.appComponent +import kotlinx.android.synthetic.main.radio_preference_view.view.* +import javax.inject.Inject + +class RadioPreferenceView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : ConstraintLayout(context, attrs) { + + @Inject lateinit var colors: Colors + + var title: String? = null + set(value) { + field = value + + if (isInEditMode) { + findViewById(R.id.titleView).text = value + } else { + titleView.text = value + } + } + + var summary: String? = null + set(value) { + field = value + + + if (isInEditMode) { + findViewById(R.id.summaryView).run { + text = value + setVisible(value?.isNotEmpty() == true) + } + } else { + summaryView.text = value + summaryView.setVisible(value?.isNotEmpty() == true) + } + } + + init { + if (!isInEditMode) { + appComponent.inject(this) + } + + View.inflate(context, R.layout.radio_preference_view, this) + setBackgroundResource(context.resolveThemeAttribute(R.attr.selectableItemBackground)) + + val states = arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked)) + + val themeColor = when (isInEditMode) { + true -> context.resources.getColor(R.color.tools_theme) + false -> colors.theme().theme + } + val textSecondary = context.resolveThemeColor(android.R.attr.textColorTertiary) + radioButton.buttonTintList = ColorStateList(states, intArrayOf(themeColor, textSecondary)) + radioButton.forwardTouches(this) + + context.obtainStyledAttributes(attrs, R.styleable.RadioPreferenceView)?.run { + title = getString(R.styleable.RadioPreferenceView_title) + summary = getString(R.styleable.RadioPreferenceView_summary) + + // If there's a custom view used for the preference's widget, inflate it + getResourceId(R.styleable.RadioPreferenceView_widget, -1).takeIf { it != -1 }?.let { id -> + View.inflate(context, id, widgetFrame) + } + + recycle() + } + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt index 5d90ded5cda1da74f9ae7cfc06f60225b4f1cad9..f207eb9c704ff74bf82d132e1068f02b182856ed 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt @@ -27,7 +27,8 @@ import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.PerformBackup import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.repository.BackupRepository -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.BehaviorSubject diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivity.kt deleted file mode 100644 index df536113ccd52b2a1fa7578e5eb59c478226259c..0000000000000000000000000000000000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivity.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.blocked - -import android.app.AlertDialog -import android.os.Bundle -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelProviders -import com.jakewharton.rxbinding2.view.clicks -import com.moez.QKSMS.R -import com.moez.QKSMS.common.base.QkThemedActivity -import dagger.android.AndroidInjection -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject -import kotlinx.android.synthetic.main.blocked_activity.* -import kotlinx.android.synthetic.main.settings_switch_widget.view.* -import javax.inject.Inject - -class BlockedActivity : QkThemedActivity(), BlockedView { - - @Inject lateinit var blockedAdapter: BlockedAdapter - @Inject lateinit var viewModelFactory: ViewModelProvider.Factory - - override val siaClickedIntent by lazy { shouldIAnswer.clicks() } - override val unblockIntent by lazy { blockedAdapter.unblock } - override val confirmUnblockIntent: Subject = PublishSubject.create() - - private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[BlockedViewModel::class.java] } - - override fun onCreate(savedInstanceState: Bundle?) { - AndroidInjection.inject(this) - super.onCreate(savedInstanceState) - setContentView(R.layout.blocked_activity) - setTitle(R.string.blocked_title) - showBackButton(true) - viewModel.bindView(this) - - blockedAdapter.emptyView = empty - conversations.adapter = blockedAdapter - } - - override fun render(state: BlockedState) { - shouldIAnswer.checkbox.isChecked = state.siaEnabled - - blockedAdapter.updateData(state.data) - } - - override fun showUnblockDialog(threadId: Long) { - AlertDialog.Builder(this) - .setTitle(R.string.blocked_unblock_dialog_title) - .setMessage(R.string.blocked_unblock_dialog_message) - .setPositiveButton(R.string.button_unblock) { _, _ -> confirmUnblockIntent.onNext(threadId) } - .setNegativeButton(R.string.button_cancel, null) - .show() - } - -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedViewModel.kt deleted file mode 100644 index dfd6ad394947a0d0f91e6f6424e2b473a505b4cb..0000000000000000000000000000000000000000 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedViewModel.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2017 Moez Bhatti - * - * This file is part of QKSMS. - * - * QKSMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * QKSMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with QKSMS. If not, see . - */ -package com.moez.QKSMS.feature.blocked - -import android.content.Context -import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope -import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.interactor.MarkUnblocked -import com.moez.QKSMS.manager.AnalyticsManager -import com.moez.QKSMS.repository.ConversationRepository -import com.moez.QKSMS.util.Preferences -import com.moez.QKSMS.util.tryOrNull -import com.uber.autodispose.kotlin.autoDisposable -import io.reactivex.rxkotlin.plusAssign -import io.reactivex.rxkotlin.withLatestFrom -import javax.inject.Inject - -class BlockedViewModel @Inject constructor( - private val context: Context, - private val analytics: AnalyticsManager, - private val conversationRepo: ConversationRepository, - private val markUnblocked: MarkUnblocked, - private val navigator: Navigator, - private val prefs: Preferences -) : QkViewModel(BlockedState()) { - - init { - newState { copy(data = conversationRepo.getBlockedConversations()) } - - disposables += prefs.sia.asObservable() - .subscribe { enabled -> newState { copy(siaEnabled = enabled) } } - } - - override fun bindView(view: BlockedView) { - super.bindView(view) - - view.siaClickedIntent - .map { - tryOrNull(false) { context.packageManager.getApplicationInfo("org.mistergroup.shouldianswerpersonal", 0).enabled } - ?: tryOrNull(false) { context.packageManager.getApplicationInfo("org.mistergroup.muzutozvednout", 0).enabled } - ?: false - } - .doOnNext { installed -> if (!installed) navigator.showSia() } - .withLatestFrom(prefs.sia.asObservable()) { installed, enabled -> - analytics.track("Clicked SIA", Pair("enable", !enabled), Pair("installed", installed)) - installed && !enabled - } - .autoDisposable(view.scope()) - .subscribe { shouldEnable -> prefs.sia.set(shouldEnable) } - - // Show confirm unblock conversation dialog - view.unblockIntent - .autoDisposable(view.scope()) - .subscribe { threadId -> view.showUnblockDialog(threadId) } - - // Unblock conversation - view.confirmUnblockIntent - .autoDisposable(view.scope()) - .subscribe { threadId -> markUnblocked.execute(listOf(threadId)) } - } - -} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..e028400fb9c4a00d6cac22d8b339271ea068e386 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingActivity.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking + +import android.os.Bundle +import com.bluelinelabs.conductor.Conductor +import com.bluelinelabs.conductor.Router +import com.bluelinelabs.conductor.RouterTransaction +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkThemedActivity +import dagger.android.AndroidInjection +import kotlinx.android.synthetic.main.container_activity.* + +class BlockingActivity : QkThemedActivity() { + + private lateinit var router: Router + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + setContentView(R.layout.container_activity) + + router = Conductor.attachRouter(this, container, savedInstanceState) + if (!router.hasRootController()) { + router.setRoot(RouterTransaction.with(BlockingController())) + } + } + + override fun onBackPressed() { + if (!router.handleBack()) { + super.onBackPressed() + } + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt new file mode 100644 index 0000000000000000000000000000000000000000..1289b9c49d83530ba90b474b5298751e6d71a575 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingController.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking + +import android.view.View +import com.bluelinelabs.conductor.RouterTransaction +import com.jakewharton.rxbinding2.view.clicks +import com.moez.QKSMS.R +import com.moez.QKSMS.common.QkChangeHandler +import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.animateLayoutChanges +import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController +import com.moez.QKSMS.feature.blocking.messages.BlockedMessagesController +import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController +import com.moez.QKSMS.injection.appComponent +import kotlinx.android.synthetic.main.blocking_controller.* +import kotlinx.android.synthetic.main.settings_switch_widget.view.* +import javax.inject.Inject + +class BlockingController : QkController(), BlockingView { + + override val blockingManagerIntent by lazy { blockingManager.clicks() } + override val blockedNumbersIntent by lazy { blockedNumbers.clicks() } + override val blockedMessagesIntent by lazy { blockedMessages.clicks() } + override val dropClickedIntent by lazy { drop.clicks() } + + @Inject lateinit var colors: Colors + @Inject override lateinit var presenter: BlockingPresenter + + init { + appComponent.inject(this) + retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.blocking_controller + } + + override fun onViewCreated() { + super.onViewCreated() + parent.postDelayed({ parent?.animateLayoutChanges = true }, 100) + } + + override fun onAttach(view: View) { + super.onAttach(view) + presenter.bindIntents(this) + setTitle(R.string.blocking_title) + showBackButton(true) + } + + override fun render(state: BlockingState) { + blockingManager.summary = state.blockingManager + drop.checkbox.isChecked = state.dropEnabled + blockedMessages.isEnabled = !state.dropEnabled + } + + override fun openBlockedNumbers() { + router.pushController(RouterTransaction.with(BlockedNumbersController()) + .pushChangeHandler(QkChangeHandler()) + .popChangeHandler(QkChangeHandler())) + } + + override fun openBlockedMessages() { + router.pushController(RouterTransaction.with(BlockedMessagesController()) + .pushChangeHandler(QkChangeHandler()) + .popChangeHandler(QkChangeHandler())) + } + + override fun openBlockingManager() { + router.pushController(RouterTransaction.with(BlockingManagerController()) + .pushChangeHandler(QkChangeHandler()) + .popChangeHandler(QkChangeHandler())) + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..04d6daa59b95059ed3adb5523cf1ad9bfe882a04 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingDialog.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking + +import android.app.Activity +import android.app.AlertDialog +import android.content.Context +import com.moez.QKSMS.R +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.interactor.MarkBlocked +import com.moez.QKSMS.interactor.MarkUnblocked +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.util.Preferences +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +// TODO: Once we have a custom dialog based on conductor, turn this into a controller +class BlockingDialog @Inject constructor( + private val blockingManager: BlockingClient, + private val context: Context, + private val conversationRepo: ConversationRepository, + private val prefs: Preferences, + private val markBlocked: MarkBlocked, + private val markUnblocked: MarkUnblocked +) { + + fun show(activity: Activity, conversationIds: List, block: Boolean) = GlobalScope.launch { + val addresses = conversationIds.toLongArray() + .let { conversationRepo.getConversations(*it) } + .flatMap { conversation -> conversation.recipients } + .map { it.address } + .distinct() + + if (addresses.isEmpty()) { + return@launch + } + + if (blockingManager.getClientCapability() == BlockingClient.Capability.BLOCK_WITHOUT_PERMISSION) { + // If we can block/unblock in the external manager, then just fire that off and exit + if (block) { + markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) + blockingManager.block(addresses).subscribe() + } else { + markUnblocked.execute(conversationIds) + blockingManager.unblock(addresses).subscribe() + } + } else if (block == allBlocked(addresses)) { + // If all of the addresses are already in their correct state in the blocking manager, just marked the + // conversations blocked and exit + when (block) { + true -> markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) + false -> markUnblocked.execute(conversationIds) + } + } else { + // Otherwise, show the UI that lets the users know they need to mark the number as blocked in the client + showDialog(activity, conversationIds, addresses, block) + } + } + + private fun allBlocked(addresses: List): Boolean = addresses.all { address -> + blockingManager.getAction(address).blockingGet() is BlockingClient.Action.Block + } + + private suspend fun showDialog( + activity: Activity, + conversationIds: List, + addresses: List, + block: Boolean + ) = withContext(MainScope().coroutineContext) { + val res = when (block) { + true -> R.plurals.blocking_block_external + false -> R.plurals.blocking_unblock_external + } + + val manager = context.getString(when (prefs.blockingManager.get()) { + Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title + Preferences.BLOCKING_MANAGER_CC -> R.string.blocking_manager_call_control_title + else -> R.string.blocking_manager_qksms_title + }) + + val message = context.resources.getQuantityString(res, addresses.size, manager) + + // Otherwise, show a dialog asking the user if they want to be directed to the external + // blocking manager + AlertDialog.Builder(activity) + .setTitle(when (block) { + true -> R.string.blocking_block_title + false -> R.string.blocking_unblock_title + }) + .setMessage(message) + .setPositiveButton(R.string.button_continue) { _, _ -> + if (block) { + markBlocked.execute(MarkBlocked.Params(conversationIds, prefs.blockingManager.get(), null)) + blockingManager.block(addresses).subscribe() + } else { + markUnblocked.execute(conversationIds) + blockingManager.unblock(addresses).subscribe() + } + } + .setNegativeButton(R.string.button_cancel) { _, _ -> } + .create() + .show() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..749ae671873974832cd25e877649f06e8b073636 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingPresenter.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking + +import android.content.Context +import com.moez.QKSMS.R +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.common.base.QkPresenter +import com.moez.QKSMS.util.Preferences +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.rxkotlin.plusAssign +import javax.inject.Inject + +class BlockingPresenter @Inject constructor( + context: Context, + private val blockingClient: BlockingClient, + private val prefs: Preferences +) : QkPresenter(BlockingState()) { + + init { + disposables += prefs.blockingManager.asObservable() + .map { client -> + when (client) { + Preferences.BLOCKING_MANAGER_SIA -> R.string.blocking_manager_sia_title + Preferences.BLOCKING_MANAGER_CC -> R.string.blocking_manager_call_control_title + else -> R.string.blocking_manager_qksms_title + } + } + .map(context::getString) + .subscribe { manager -> newState { copy(blockingManager = manager) } } + + disposables += prefs.drop.asObservable() + .subscribe { enabled -> newState { copy(dropEnabled = enabled) } } + } + + override fun bindIntents(view: BlockingView) { + super.bindIntents(view) + + view.blockingManagerIntent + .autoDisposable(view.scope()) + .subscribe { view.openBlockingManager() } + + view.blockedNumbersIntent + .autoDisposable(view.scope()) + .subscribe { + if (prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_QKSMS) { + // TODO: This is a hack, get rid of it once we implement AndroidX navigation + view.openBlockedNumbers() + } else { + blockingClient.openSettings() + } + } + + view.blockedMessagesIntent + .autoDisposable(view.scope()) + .subscribe { view.openBlockedMessages() } + + view.dropClickedIntent + .autoDisposable(view.scope()) + .subscribe { prefs.drop.set(!prefs.drop.get()) } + } + +} diff --git a/domain/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManager.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingState.kt similarity index 73% rename from domain/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManager.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingState.kt index 21cdbe5088c095062523fce8b4f613992ecf2919..8f3e31256842a421dee27e52ba32840921148bb9 100644 --- a/domain/src/main/java/com/moez/QKSMS/manager/ExternalBlockingManager.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingState.kt @@ -16,15 +16,9 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.manager +package com.moez.QKSMS.feature.blocking -import io.reactivex.Single - -interface ExternalBlockingManager { - - /** - * Return a Single which emits whether or not the given [address] should be blocked - */ - fun shouldBlock(address: String): Single - -} \ No newline at end of file +data class BlockingState( + val blockingManager: String = "", + val dropEnabled: Boolean = false +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingView.kt new file mode 100644 index 0000000000000000000000000000000000000000..8ca05048ec7e36dda105d37a5a05160bff752bed --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/BlockingView.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking + +import com.moez.QKSMS.common.base.QkViewContract +import io.reactivex.Observable + +interface BlockingView : QkViewContract { + + val blockingManagerIntent: Observable<*> + val blockedNumbersIntent: Observable<*> + val blockedMessagesIntent: Observable<*> + val dropClickedIntent: Observable<*> + + fun openBlockingManager() + fun openBlockedNumbers() + fun openBlockedMessages() +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt new file mode 100644 index 0000000000000000000000000000000000000000..ed64b1d5cf363db4fc5556e640143c4d664ae84c --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerController.kt @@ -0,0 +1,69 @@ +package com.moez.QKSMS.feature.blocking.manager + +import android.app.Activity +import android.app.AlertDialog +import android.view.View +import androidx.core.view.isVisible +import com.jakewharton.rxbinding2.view.clicks +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.util.Preferences +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.subjects.PublishSubject +import kotlinx.android.synthetic.main.blocking_manager_controller.* +import kotlinx.android.synthetic.main.blocking_manager_list_option.view.* +import kotlinx.android.synthetic.main.radio_preference_view.view.* +import javax.inject.Inject + +class BlockingManagerController : QkController(), + BlockingManagerView { + + @Inject override lateinit var presenter: BlockingManagerPresenter + + private val activityResumedSubject: PublishSubject = PublishSubject.create() + + init { + appComponent.inject(this) + retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.blocking_manager_controller + } + + override fun onAttach(view: View) { + super.onAttach(view) + presenter.bindIntents(this) + setTitle(R.string.blocking_manager_title) + showBackButton(true) + } + + override fun onActivityResumed(activity: Activity) { + activityResumedSubject.onNext(Unit) + } + + override fun render(state: BlockingManagerState) { + qksms.radioButton.isChecked = state.blockingManager == Preferences.BLOCKING_MANAGER_QKSMS + + callControl.radioButton.isChecked = state.blockingManager == Preferences.BLOCKING_MANAGER_CC + callControl.install.isVisible = !state.callControlInstalled + + shouldIAnswer.radioButton.isChecked = state.blockingManager == Preferences.BLOCKING_MANAGER_SIA + shouldIAnswer.install.isVisible = !state.siaInstalled + } + + override fun activityResumed(): Observable<*> = activityResumedSubject + override fun qksmsClicked(): Observable<*> = qksms.clicks() + override fun callControlClicked(): Observable<*> = callControl.clicks() + override fun siaClicked(): Observable<*> = shouldIAnswer.clicks() + + override fun showCopyDialog(manager: String): Single = Single.create { emitter -> + AlertDialog.Builder(activity) + .setTitle(R.string.blocking_manager_copy_title) + .setMessage(resources?.getString(R.string.blocking_manager_copy_summary, manager)) + .setPositiveButton(R.string.button_continue) { _, _ -> emitter.onSuccess(true) } + .setNegativeButton(R.string.button_cancel) { _, _ -> emitter.onSuccess(false) } + .setCancelable(false) + .show() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..1ef5d91fc706362e3684e4582ab8bb6fca8e3020 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerPresenter.kt @@ -0,0 +1,122 @@ +package com.moez.QKSMS.feature.blocking.manager + +import android.content.Context +import com.moez.QKSMS.R +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.blocking.CallControlBlockingClient +import com.moez.QKSMS.blocking.QksmsBlockingClient +import com.moez.QKSMS.blocking.ShouldIAnswerBlockingClient +import com.moez.QKSMS.common.Navigator +import com.moez.QKSMS.common.base.QkPresenter +import com.moez.QKSMS.manager.AnalyticsManager +import com.moez.QKSMS.repository.ConversationRepository +import com.moez.QKSMS.util.Preferences +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.rxkotlin.plusAssign +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +class BlockingManagerPresenter @Inject constructor( + private val analytics: AnalyticsManager, + private val callControl: CallControlBlockingClient, + private val context: Context, + private val conversationRepo: ConversationRepository, + private val navigator: Navigator, + private val prefs: Preferences, + private val qksms: QksmsBlockingClient, + private val shouldIAnswer: ShouldIAnswerBlockingClient +) : QkPresenter(BlockingManagerState( + blockingManager = prefs.blockingManager.get(), + callControlInstalled = callControl.isAvailable(), + siaInstalled = shouldIAnswer.isAvailable() +)) { + + init { + disposables += prefs.blockingManager.asObservable() + .subscribe { manager -> newState { copy(blockingManager = manager) } } + } + + override fun bindIntents(view: BlockingManagerView) { + super.bindIntents(view) + + view.activityResumed() + .map { callControl.isAvailable() } + .distinctUntilChanged() + .autoDisposable(view.scope()) + .subscribe { available -> newState { copy(callControlInstalled = available) } } + + view.activityResumed() + .map { shouldIAnswer.isAvailable() } + .distinctUntilChanged() + .autoDisposable(view.scope()) + .subscribe { available -> newState { copy(siaInstalled = available) } } + + view.qksmsClicked() + .observeOn(Schedulers.io()) + .map { getAddressesToBlock(qksms) } + .switchMap { numbers -> qksms.block(numbers).andThen(Observable.just(Unit)) } // Hack + .autoDisposable(view.scope()) + .subscribe { + analytics.setUserProperty("Blocking Manager", "QKSMS") + prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_QKSMS) + } + + view.callControlClicked() + .filter { + val installed = callControl.isAvailable() + if (!installed) { + analytics.track("Install Call Control") + navigator.installCallControl() + } + + val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_CC + installed && !enabled + } + .observeOn(Schedulers.io()) + .map { getAddressesToBlock(callControl) } + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { numbers -> + when (numbers.size) { + 0 -> Observable.just(true) + else -> view.showCopyDialog(context.getString(R.string.blocking_manager_call_control_title)) + .toObservable() + } + } + .doOnNext { newState { copy() } } // Radio button may have been selected when it shouldn't, fix it + .filter { it } + .observeOn(Schedulers.io()) + .map { getAddressesToBlock(callControl) } // This sucks. Can't wait to use coroutines + .switchMap { numbers -> callControl.block(numbers).andThen(Observable.just(Unit)) } // Hack + .autoDisposable(view.scope()) + .subscribe { + callControl.getAction("callcontrol").blockingGet() + analytics.setUserProperty("Blocking Manager", "Call Control") + prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_CC) + } + + view.siaClicked() + .filter { + val installed = shouldIAnswer.isAvailable() + if (!installed) { + analytics.track("Install SIA") + navigator.installSia() + } + + val enabled = prefs.blockingManager.get() == Preferences.BLOCKING_MANAGER_SIA + installed && !enabled + } + .autoDisposable(view.scope()) + .subscribe { + analytics.setUserProperty("Blocking Manager", "SIA") + prefs.blockingManager.set(Preferences.BLOCKING_MANAGER_SIA) + } + } + + private fun getAddressesToBlock(client: BlockingClient) = conversationRepo.getBlockedConversations() + .fold(listOf(), { numbers, conversation -> numbers + conversation.recipients.map { it.address } }) + .filter { number -> client.getAction(number).blockingGet() !is BlockingClient.Action.Block } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt new file mode 100644 index 0000000000000000000000000000000000000000..2663caf067f54e643af3b439f60ff2fe97d3d369 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerState.kt @@ -0,0 +1,7 @@ +package com.moez.QKSMS.feature.blocking.manager + +data class BlockingManagerState( + val blockingManager: Int = 0, + val callControlInstalled: Boolean = false, + val siaInstalled: Boolean = false +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt new file mode 100644 index 0000000000000000000000000000000000000000..7318e53a212b6110a255c761d62ece63e571b82e --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/manager/BlockingManagerView.kt @@ -0,0 +1,16 @@ +package com.moez.QKSMS.feature.blocking.manager + +import com.moez.QKSMS.common.base.QkViewContract +import io.reactivex.Observable +import io.reactivex.Single + +interface BlockingManagerView : QkViewContract { + + fun activityResumed(): Observable<*> + fun qksmsClicked(): Observable<*> + fun callControlClicked(): Observable<*> + fun siaClicked(): Observable<*> + + fun showCopyDialog(manager: String): Single + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..4662c9bf146b692c160635ccc6e302629b94a83e --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesAdapter.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.messages + +import android.content.Context +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkRealmAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.util.Preferences +import io.reactivex.subjects.PublishSubject +import kotlinx.android.synthetic.main.blocked_list_item.view.* +import javax.inject.Inject + +class BlockedMessagesAdapter @Inject constructor( + private val context: Context, + private val dateFormatter: DateFormatter +) : QkRealmAdapter() { + + val clicks: PublishSubject = PublishSubject.create() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_list_item, parent, false) + + if (viewType == 0) { + view.title.setTypeface(view.title.typeface, Typeface.BOLD) + view.date.setTypeface(view.date.typeface, Typeface.BOLD) + view.date.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) + } + + return QkViewHolder(view).apply { + view.setOnClickListener { + val conversation = getItem(adapterPosition) ?: return@setOnClickListener + when (toggleSelection(conversation.id, false)) { + true -> view.isActivated = isSelected(conversation.id) + false -> clicks.onNext(conversation.id) + } + } + view.setOnLongClickListener { + val conversation = getItem(adapterPosition) ?: return@setOnLongClickListener true + toggleSelection(conversation.id) + view.isActivated = isSelected(conversation.id) + true + } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val conversation = getItem(position) ?: return + val view = holder.containerView + + view.isActivated = isSelected(conversation.id) + + view.avatars.contacts = conversation.recipients + view.title.collapseEnabled = conversation.recipients.size > 1 + view.title.text = conversation.getTitle() + view.date.text = dateFormatter.getConversationTimestamp(conversation.date) + + view.blocker.text = when (conversation.blockingClient) { + Preferences.BLOCKING_MANAGER_CC -> context.getString(R.string.blocking_manager_call_control_title) + Preferences.BLOCKING_MANAGER_SIA -> context.getString(R.string.blocking_manager_sia_title) + else -> null + } + + view.reason.text = conversation.blockReason + view.blocker.isVisible = view.blocker.text.isNotEmpty() + view.reason.isVisible = view.blocker.text.isNotEmpty() + } + + override fun getItemViewType(position: Int): Int { + val conversation = getItem(position) + return if (conversation?.read == true) 1 else 0 + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt new file mode 100644 index 0000000000000000000000000000000000000000..92dfcac39f936b83bde2c2d9a6da7a64c4867a39 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesController.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.messages + +import android.app.AlertDialog +import android.content.Context +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.feature.blocking.BlockingDialog +import com.moez.QKSMS.injection.appComponent +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.blocked_messages_controller.* +import kotlinx.android.synthetic.main.container_activity.* +import javax.inject.Inject + +class BlockedMessagesController : QkController(), + BlockedMessagesView { + + override val menuReadyIntent: Subject = PublishSubject.create() + override val optionsItemIntent: Subject = PublishSubject.create() + override val conversationClicks by lazy { blockedMessagesAdapter.clicks } + override val selectionChanges by lazy { blockedMessagesAdapter.selectionChanges } + override val confirmDeleteIntent: Subject> = PublishSubject.create() + override val backClicked: Subject = PublishSubject.create() + + @Inject lateinit var blockedMessagesAdapter: BlockedMessagesAdapter + @Inject lateinit var blockingDialog: BlockingDialog + @Inject lateinit var colors: Colors + @Inject lateinit var context: Context + @Inject override lateinit var presenter: BlockedMessagesPresenter + + init { + appComponent.inject(this) + retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.blocked_messages_controller + } + + override fun onViewCreated() { + super.onViewCreated() + blockedMessagesAdapter.emptyView = empty + conversations.adapter = blockedMessagesAdapter + } + + override fun onAttach(view: View) { + super.onAttach(view) + presenter.bindIntents(this) + setTitle(R.string.blocked_messages_title) + showBackButton(true) + setHasOptionsMenu(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.blocked_messages, menu) + menuReadyIntent.onNext(Unit) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + optionsItemIntent.onNext(item.itemId) + return true + } + + override fun handleBack(): Boolean { + backClicked.onNext(Unit) + return true + } + + override fun render(state: BlockedMessagesState) { + blockedMessagesAdapter.updateData(state.data) + + themedActivity?.toolbar?.menu?.findItem(R.id.block)?.isVisible = state.selected > 0 + themedActivity?.toolbar?.menu?.findItem(R.id.delete)?.isVisible = state.selected > 0 + + setTitle(when (state.selected) { + 0 -> context.getString(R.string.blocked_messages_title) + else -> context.getString(R.string.main_title_selected, state.selected) + }) + } + + override fun clearSelection() = blockedMessagesAdapter.clearSelection() + + override fun showBlockingDialog(conversations: List, block: Boolean) { + blockingDialog.show(activity!!, conversations, block) + } + + override fun showDeleteDialog(conversations: List) { + val count = conversations.size + AlertDialog.Builder(activity) + .setTitle(R.string.dialog_delete_title) + .setMessage(resources?.getQuantityString(R.plurals.dialog_delete_message, count, count)) + .setPositiveButton(R.string.button_delete) { _, _ -> confirmDeleteIntent.onNext(conversations) } + .setNegativeButton(R.string.button_cancel, null) + .show() + } + + override fun goBack() { + router.popCurrentController() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..d1fe9e78265bdf1f1d980fea9f8daed85eda0717 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesPresenter.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.messages + +import com.moez.QKSMS.R +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.common.Navigator +import com.moez.QKSMS.common.base.QkPresenter +import com.moez.QKSMS.interactor.DeleteConversations +import com.moez.QKSMS.repository.ConversationRepository +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.rxkotlin.withLatestFrom +import javax.inject.Inject + +class BlockedMessagesPresenter @Inject constructor( + conversationRepo: ConversationRepository, + private val blockingClient: BlockingClient, + private val deleteConversations: DeleteConversations, + private val navigator: Navigator +) : QkPresenter(BlockedMessagesState( + data = conversationRepo.getBlockedConversationsAsync() +)) { + + override fun bindIntents(view: BlockedMessagesView) { + super.bindIntents(view) + + view.menuReadyIntent + .autoDisposable(view.scope()) + .subscribe { newState { copy() } } + + view.optionsItemIntent + .withLatestFrom(view.selectionChanges) { itemId, conversations -> + when (itemId) { + R.id.block -> { + view.showBlockingDialog(conversations, false) + view.clearSelection() + } + R.id.delete -> { + view.showDeleteDialog(conversations) + } + } + + } + .autoDisposable(view.scope()) + .subscribe() + + view.confirmDeleteIntent + .autoDisposable(view.scope()) + .subscribe { conversations -> + deleteConversations.execute(conversations) + view.clearSelection() + } + + view.conversationClicks + .autoDisposable(view.scope()) + .subscribe { threadId -> navigator.showConversation(threadId) } + + view.selectionChanges + .autoDisposable(view.scope()) + .subscribe { selection -> newState { copy(selected = selection.size) } } + + view.backClicked + .withLatestFrom(state) { _, state -> + when (state.selected) { + 0 -> view.goBack() + else -> view.clearSelection() + } + } + .autoDisposable(view.scope()) + .subscribe() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesState.kt new file mode 100644 index 0000000000000000000000000000000000000000..2fe3201b54d8b1c7ef63653a9842a0b8df1db887 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.messages + +import com.moez.QKSMS.model.Conversation +import io.realm.RealmResults + +data class BlockedMessagesState( + val data: RealmResults? = null, + val selected: Int = 0 +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesView.kt new file mode 100644 index 0000000000000000000000000000000000000000..8de0ad34c70e12abdfe8690683bfd793501981ae --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/messages/BlockedMessagesView.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.messages + +import com.moez.QKSMS.common.base.QkViewContract +import io.reactivex.Observable + +interface BlockedMessagesView : QkViewContract { + + val menuReadyIntent: Observable + val optionsItemIntent: Observable + val conversationClicks: Observable + val selectionChanges: Observable> + val confirmDeleteIntent: Observable> + val backClicked: Observable<*> + + fun clearSelection() + fun showBlockingDialog(conversations: List, block: Boolean) + fun showDeleteDialog(conversations: List) + fun goBack() + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumberTextWatcher.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumberTextWatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..17ceac22ddb6b9ed41098f0b91275d73ee4348ba --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumberTextWatcher.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.numbers + +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText +import com.moez.QKSMS.util.PhoneNumberUtils + +class BlockedNumberTextWatcher( + private val editText: EditText, + private val phoneNumberUtils: PhoneNumberUtils +) : TextWatcher { + + init { + editText.addTextChangedListener(this) + } + + override fun afterTextChanged(s: Editable?) = Unit + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val formatted = s?.let(phoneNumberUtils::formatNumber) + if (s?.toString() != formatted && formatted != null) { + editText.setText(formatted) + editText.setSelection(formatted.length) + } + } + + fun dispose() { + editText.removeTextChangedListener(this) + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersAdapter.kt similarity index 63% rename from presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedAdapter.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersAdapter.kt index f6c1df30905e3956e51d0fd851e527849ad9c07f..9ddf541faa3c3cc4f784e32c01a2091b4129b716 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersAdapter.kt @@ -16,38 +16,37 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.blocked +package com.moez.QKSMS.feature.blocking.numbers import android.view.LayoutInflater import android.view.ViewGroup import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder -import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.BlockedNumber import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.blocked_list_item.view.* -import javax.inject.Inject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.blocked_number_list_item.* +import kotlinx.android.synthetic.main.blocked_number_list_item.view.* -class BlockedAdapter @Inject constructor() : QkRealmAdapter() { +class BlockedNumbersAdapter : QkRealmAdapter() { - val unblock: PublishSubject = PublishSubject.create() + val unblockAddress: Subject = PublishSubject.create() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_list_item, parent, false) + val view = LayoutInflater.from(parent.context).inflate(R.layout.blocked_number_list_item, parent, false) return QkViewHolder(view).apply { - view.setOnClickListener { - val conversation = getItem(adapterPosition) ?: return@setOnClickListener - unblock.onNext(conversation.id) + containerView.unblock.setOnClickListener { + val number = getItem(adapterPosition) ?: return@setOnClickListener + unblockAddress.onNext(number.id) } } } override fun onBindViewHolder(holder: QkViewHolder, position: Int) { - val conversation = getItem(position) ?: return - val view = holder.containerView + val item = getItem(position)!! - view.avatars.contacts = conversation.recipients - view.title.text = conversation.getTitle() + holder.number.text = item.address } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt new file mode 100644 index 0000000000000000000000000000000000000000..8b0927ecac3b11669ae2415dd3f5584b797dafc3 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersController.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.numbers + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AlertDialog +import com.jakewharton.rxbinding2.view.clicks +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkController +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.util.PhoneNumberUtils +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.blocked_numbers_add_dialog.view.* +import kotlinx.android.synthetic.main.blocked_numbers_controller.* +import javax.inject.Inject + +class BlockedNumbersController : QkController(), + BlockedNumbersView { + + @Inject override lateinit var presenter: BlockedNumbersPresenter + @Inject lateinit var colors: Colors + @Inject lateinit var phoneNumberUtils: PhoneNumberUtils + + private val adapter = BlockedNumbersAdapter() + private val saveAddressSubject: Subject = PublishSubject.create() + + init { + appComponent.inject(this) + retainViewMode = RetainViewMode.RETAIN_DETACH + layoutRes = R.layout.blocked_numbers_controller + } + + override fun onAttach(view: View) { + super.onAttach(view) + presenter.bindIntents(this) + setTitle(R.string.blocked_numbers_title) + showBackButton(true) + } + + override fun onViewCreated() { + super.onViewCreated() + add.setBackgroundTint(colors.theme().theme) + add.setTint(colors.theme().textPrimary) + adapter.emptyView = empty + numbers.adapter = adapter + } + + override fun render(state: BlockedNumbersState) { + adapter.updateData(state.numbers) + } + + override fun unblockAddress(): Observable = adapter.unblockAddress + override fun addAddress(): Observable<*> = add.clicks() + override fun saveAddress(): Observable = saveAddressSubject + + override fun showAddDialog() { + val layout = LayoutInflater.from(activity).inflate(R.layout.blocked_numbers_add_dialog, null) + val textWatcher = BlockedNumberTextWatcher(layout.input, phoneNumberUtils) + val dialog = AlertDialog.Builder(activity!!) + .setView(layout) + .setPositiveButton(R.string.blocked_numbers_dialog_block) { _, _ -> + saveAddressSubject.onNext(layout.input.text.toString()) + } + .setNegativeButton(R.string.button_cancel) { _, _ -> } + .setOnDismissListener { textWatcher.dispose() } + dialog.show() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f9c4decd83bc17d3162d4350339918b489b93760 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersPresenter.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.blocking.numbers + +import com.moez.QKSMS.common.base.QkPresenter +import com.moez.QKSMS.interactor.MarkUnblocked +import com.moez.QKSMS.repository.BlockingRepository +import com.moez.QKSMS.repository.ConversationRepository +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +class BlockedNumbersPresenter @Inject constructor( + private val blockingRepo: BlockingRepository, + private val conversationRepo: ConversationRepository, + private val markUnblocked: MarkUnblocked +) : QkPresenter( + BlockedNumbersState(numbers = blockingRepo.getBlockedNumbers()) +) { + + override fun bindIntents(view: BlockedNumbersView) { + super.bindIntents(view) + + view.unblockAddress() + .doOnNext { id -> + blockingRepo.getBlockedNumber(id)?.address + ?.let(conversationRepo::getThreadId) + ?.let { threadId -> markUnblocked.execute(listOf(threadId)) } + } + .doOnNext(blockingRepo::unblockNumber) + .subscribeOn(Schedulers.io()) + .autoDisposable(view.scope()) + .subscribe() + + view.addAddress() + .autoDisposable(view.scope()) + .subscribe { view.showAddDialog() } + + view.saveAddress() + .subscribeOn(Schedulers.io()) + .autoDisposable(view.scope()) + .subscribe { address -> blockingRepo.blockNumber(address) } + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersState.kt similarity index 78% rename from presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedState.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersState.kt index 5a83fd4fe0870b7f71627002edaff1d685035714..abc206006a3c476b5c0de5ae7f488e4cd72c4784 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersState.kt @@ -16,12 +16,12 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.blocked +package com.moez.QKSMS.feature.blocking.numbers -import com.moez.QKSMS.model.Conversation +import com.moez.QKSMS.model.BlockedNumber +import io.realm.RealmList import io.realm.RealmResults -data class BlockedState( - val siaEnabled: Boolean = false, - val data: RealmResults? = null -) \ No newline at end of file +data class BlockedNumbersState( + val numbers: RealmResults? = null +) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersView.kt similarity index 70% rename from presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedView.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersView.kt index 3b5d5216f6f58744feafb36d820ad6c7c1477f05..b537c9446a8679576cb68c481b33e3ec94c82e13 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/blocking/numbers/BlockedNumbersView.kt @@ -16,17 +16,17 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.blocked +package com.moez.QKSMS.feature.blocking.numbers -import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.common.base.QkViewContract import io.reactivex.Observable -interface BlockedView : QkView { +interface BlockedNumbersView : QkViewContract { - val siaClickedIntent: Observable<*> - val unblockIntent: Observable - val confirmUnblockIntent: Observable + fun unblockAddress(): Observable + fun addAddress(): Observable<*> + fun saveAddress(): Observable - fun showUnblockDialog(threadId: Long) + fun showAddDialog() -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..43de03391cd80d15bd54968639908b2920cfbe4e --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogAdapter.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.changelog + +import android.content.Context +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.ViewGroup +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.manager.ChangelogManager +import kotlinx.android.synthetic.main.changelog_list_item.* + +class ChangelogAdapter(private val context: Context) : QkAdapter() { + + data class ChangelogItem(val type: Int, val label: String) + + fun setChangelog(changelog: ChangelogManager.Changelog) { + val changes = mutableListOf() + if (changelog.added.isNotEmpty()) { + changes += ChangelogItem(0, context.getString(R.string.changelog_added)) + changes += changelog.added.map { change -> ChangelogItem(1, change) } + changes += ChangelogItem(0, "") + } + if (changelog.improved.isNotEmpty()) { + changes += ChangelogItem(0, context.getString(R.string.changelog_improved)) + changes += changelog.improved.map { change -> ChangelogItem(1, change) } + changes += ChangelogItem(0, "") + } + if (changelog.fixed.isNotEmpty()) { + changes += ChangelogItem(0, context.getString(R.string.changelog_fixed)) + changes += changelog.fixed.map { change -> ChangelogItem(1, change) } + changes += ChangelogItem(0, "") + } + data = changes + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.changelog_list_item, parent, false) + return QkViewHolder(view).apply { + if (viewType == 0) { + changelogItem.setTypeface(changelogItem.typeface, Typeface.BOLD) + } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val item = getItem(position) + + holder.changelogItem.text = item.label + } + + override fun getItemViewType(position: Int): Int { + return getItem(position).type + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..04dc1d4ce00391204325a827e69483e51b76d976 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/changelog/ChangelogDialog.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.changelog + +import android.view.LayoutInflater +import androidx.appcompat.app.AlertDialog +import com.moez.QKSMS.BuildConfig +import com.moez.QKSMS.R +import com.moez.QKSMS.feature.main.MainActivity +import com.moez.QKSMS.manager.ChangelogManager +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.changelog_dialog.view.* + +class ChangelogDialog(activity: MainActivity) { + + val moreClicks: Subject = PublishSubject.create() + + private val dialog: AlertDialog + private val adapter = ChangelogAdapter(activity) + + init { + val layout = LayoutInflater.from(activity).inflate(R.layout.changelog_dialog, null) + + dialog = AlertDialog.Builder(activity) + .setCancelable(true) + .setView(layout) + .create() + + layout.version.text = activity.getString(R.string.changelog_version, BuildConfig.VERSION_NAME) + layout.changelog.adapter = adapter + layout.more.setOnClickListener { dialog.dismiss(); moreClicks.onNext(Unit) } + layout.dismiss.setOnClickListener { dialog.dismiss() } + } + + fun show(changelog: ChangelogManager.Changelog) { + adapter.setChangelog(changelog) + dialog.show() + } + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/BubbleUtils.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/BubbleUtils.kt index afcde9240bfc4039102659e992621bf2353b32c1..cb7dad0eced570bb73387a4a5a9d50195bed7a31 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/BubbleUtils.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/BubbleUtils.kt @@ -32,8 +32,9 @@ object BubbleUtils { return message.compareSender(other) && diff < TIMESTAMP_THRESHOLD } - fun getBubble(canGroupWithPrevious: Boolean, canGroupWithNext: Boolean, isMe: Boolean): Int { + fun getBubble(emojiOnly: Boolean, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean, isMe: Boolean): Int { return when { + emojiOnly -> R.drawable.message_emoji !canGroupWithPrevious && canGroupWithNext -> if (isMe) R.drawable.message_out_first else R.drawable.message_in_first canGroupWithPrevious && canGroupWithNext -> if (isMe) R.drawable.message_out_middle else R.drawable.message_in_middle canGroupWithPrevious && !canGroupWithNext -> if (isMe) R.drawable.message_out_last else R.drawable.message_in_last diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt index 20307ba6d31938762c337a084e786463feb4df0a..a0c38ad08faff1ed89658ccaca67bcaa9a06f8c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt @@ -43,7 +43,7 @@ import com.google.android.material.snackbar.Snackbar import com.jakewharton.rxbinding2.view.clicks import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R -import com.moez.QKSMS.common.androidxcompat.scope +import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.autoScrollToStart @@ -55,8 +55,8 @@ import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.util.extensions.showKeyboard import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Contact -import com.moez.QKSMS.model.Message -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject @@ -79,6 +79,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { @Inject lateinit var contactsAdapter: ContactAdapter @Inject lateinit var dateFormatter: DateFormatter @Inject lateinit var messageAdapter: MessagesAdapter + @Inject lateinit var navigator: Navigator @Inject lateinit var viewModelFactory: ViewModelProvider.Factory override val activityVisibleIntent: Subject = PublishSubject.create() @@ -90,9 +91,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override val menuReadyIntent: Observable = menu.map { Unit } override val optionsItemIntent: Subject = PublishSubject.create() override val sendAsGroupIntent by lazy { sendAsGroupBackground.clicks() } - override val messageClickIntent: Subject by lazy { messageAdapter.clicks } + override val messageClickIntent: Subject by lazy { messageAdapter.clicks } + override val messagePartClickIntent: Subject by lazy { messageAdapter.partClicks } override val messagesSelectedIntent by lazy { messageAdapter.selectionChanges } - override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } + override val cancelSendingIntent: Subject by lazy { messageAdapter.cancelSending } override val attachmentDeletedIntent: Subject by lazy { attachmentAdapter.attachmentDeleted } override val textChangedIntent by lazy { message.textChanges() } override val attachIntent by lazy { Observable.merge(attach.clicks(), attachingBackground.clicks()) } @@ -250,6 +252,10 @@ class ComposeActivity : QkThemedActivity(), ComposeView { .show() } + override fun requestDefaultSms() { + navigator.showDefaultSmsDialog(this) + } + override fun requestStoragePermission() { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 0) } @@ -313,6 +319,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun showQksmsPlusSnackbar(message: Int) { Snackbar.make(contentView, message, Snackbar.LENGTH_LONG).run { setAction(R.string.button_more) { viewQksmsPlusIntent.onNext(Unit) } + setActionTextColor(colors.theme().theme) show() } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 51ea25cdde9683954f7c4162c53db5dc68dba32a..d9b0504a2e23af97f8cda72f2e7420f2ee0280b8 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -49,10 +49,10 @@ class ComposeActivityModule { activity.intent.data?.let { val data = it.toString() address = when { - it.scheme.startsWith("smsto") -> data.replace("smsto:", "") - it.scheme.startsWith("mmsto") -> data.replace("mmsto:", "") - it.scheme.startsWith("sms") -> data.replace("sms:", "") - it.scheme.startsWith("mms") -> data.replace("mms:", "") + it.scheme?.startsWith("smsto") == true -> data.replace("smsto:", "") + it.scheme?.startsWith("mmsto") == true -> data.replace("mmsto:", "") + it.scheme?.startsWith("sms") == true -> data.replace("sms:", "") + it.scheme?.startsWith("mms") == true -> data.replace("mms:", "") else -> "" } @@ -66,7 +66,9 @@ class ComposeActivityModule { @Provides @Named("text") fun provideSharedText(activity: ComposeActivity): String { - return activity.intent.extras?.getString(Intent.EXTRA_TEXT) ?: "" + return activity.intent.extras?.getString(Intent.EXTRA_TEXT) + ?: activity.intent.extras?.getString("sms_body") + ?: "" } @Provides diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt index e15b9d00235614aa3b5a47086118dcca5671e2d2..137a2aae5e4122b5fcda9e0f85369d91ba457c83 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeView.kt @@ -24,7 +24,6 @@ import androidx.core.view.inputmethod.InputContentInfoCompat import com.moez.QKSMS.common.base.QkView import com.moez.QKSMS.model.Attachment import com.moez.QKSMS.model.Contact -import com.moez.QKSMS.model.Message import io.reactivex.Observable import io.reactivex.subjects.Subject @@ -39,9 +38,10 @@ interface ComposeView : QkView { val menuReadyIntent: Observable val optionsItemIntent: Observable val sendAsGroupIntent: Observable<*> - val messageClickIntent: Subject + val messageClickIntent: Subject + val messagePartClickIntent: Subject val messagesSelectedIntent: Observable> - val cancelSendingIntent: Subject + val cancelSendingIntent: Subject val attachmentDeletedIntent: Subject val textChangedIntent: Observable val attachIntent: Observable @@ -61,6 +61,7 @@ interface ComposeView : QkView { fun clearSelection() fun showDetails(details: String) + fun requestDefaultSms() fun requestStoragePermission() fun requestSmsPermission() fun requestCamera() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index da4026c14be5b6688b62f38243db66455beb490a..7ad5989cfaff0134de87b2f2283b84506222d613 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -21,12 +21,10 @@ package com.moez.QKSMS.feature.compose import android.content.Context import android.net.Uri import android.provider.ContactsContract -import android.telephony.PhoneNumberUtils import android.telephony.SmsMessage import android.view.inputmethod.EditorInfo import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.common.util.ClipboardUtils import com.moez.QKSMS.common.util.MessageDetailsFormatter @@ -35,6 +33,7 @@ import com.moez.QKSMS.compat.SubscriptionManagerCompat import com.moez.QKSMS.compat.TelephonyCompat import com.moez.QKSMS.extensions.asObservable import com.moez.QKSMS.extensions.isImage +import com.moez.QKSMS.extensions.isVideo import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.filter.ContactFilter @@ -46,9 +45,11 @@ import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.util.ActiveSubscriptionObservable +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import com.moez.QKSMS.util.tryOrNull -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.Observables @@ -83,6 +84,7 @@ class ComposeViewModel @Inject constructor( private val messageRepo: MessageRepository, private val navigator: Navigator, private val permissionManager: PermissionManager, + private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, private val retrySending: RetrySending, private val sendMessage: SendMessage, @@ -119,8 +121,7 @@ class ComposeViewModel @Inject constructor( .observeOn(AndroidSchedulers.mainThread()) .doOnNext { newState { copy(loading = false) } } .switchMap { (threadId, addresses) -> - - // If we already have this thread in realm, or were able to obtain it from the + // If we already have this thread in realm, or we're able to obtain it from the // system, just return that. threadId.takeIf { it > 0 }?.let { return@switchMap conversationRepo.getConversationAsync(threadId).asObservable() @@ -236,7 +237,8 @@ class ComposeViewModel @Inject constructor( // Update the list of contact suggestions based on the query input, while also filtering out any contacts // that have already been selected Observables - .combineLatest(view.queryChangedIntent, contacts, selectedContacts) { query, contacts, selectedContacts -> + .combineLatest(view.queryChangedIntent, contacts, selectedContacts) { query, contacts, + selectedContacts -> // Strip the accents from the query. This can be an expensive operation, so // cache the result instead of doing it for each contact @@ -247,10 +249,9 @@ class ComposeViewModel @Inject constructor( .filter { contact -> contactFilter.filter(contact, normalizedQuery) } // If the entry is a valid destination, allow it as a recipient - if (PhoneNumberUtils.isWellFormedSmsAddress(query.toString())) { - val newAddress = PhoneNumberUtils.formatNumber(query.toString(), Locale.getDefault().country) - val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress - ?: query.toString()))) + if (phoneNumberUtils.isPossibleNumber(query.toString())) { + val newAddress = phoneNumberUtils.formatNumber(query) + val newContact = Contact(numbers = RealmList(PhoneNumber(address = newAddress))) filteredContacts = listOf(newContact) + filteredContacts } @@ -343,6 +344,7 @@ class ComposeViewModel @Inject constructor( // Delete the messages view.optionsItemIntent .filter { it == R.id.delete } + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } .withLatestFrom(view.messagesSelectedIntent, conversation) { _, messages, conversation -> deleteMessages.execute(DeleteMessages.Params(messages, conversation.id)) } @@ -355,13 +357,12 @@ class ComposeViewModel @Inject constructor( .withLatestFrom(view.messagesSelectedIntent) { _, messages -> messages?.firstOrNull()?.let { messageRepo.getMessage(it) }?.let { message -> val images = message.parts.filter { it.isImage() }.mapNotNull { it.getUri() } - navigator.showCompose(message.body, images) + navigator.showCompose(message.getText(), images) } } .autoDisposable(view.scope()) .subscribe { view.clearSelection() } - // Show the previous search result view.optionsItemIntent .filter { it == R.id.previous } @@ -374,7 +375,6 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe(searchSelection) - // Show the next search result view.optionsItemIntent .filter { it == R.id.next } @@ -387,20 +387,17 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe(searchSelection) - // Clear the search view.optionsItemIntent .filter { it == R.id.clear } .autoDisposable(view.scope()) .subscribe { newState { copy(query = "", searchSelectionId = -1) } } - // Toggle the group sending mode view.sendAsGroupIntent .autoDisposable(view.scope()) .subscribe { newState { copy(sendAsGroup = !sendAsGroup) } } - // Scroll to search position searchSelection .filter { id -> id != -1L } @@ -408,14 +405,34 @@ class ComposeViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe(view::scrollToMessage) - // Retry sending view.messageClickIntent + .mapNotNull(messageRepo::getMessage) .filter { message -> message.isFailedMessage() } .doOnNext { message -> retrySending.execute(message) } .autoDisposable(view.scope()) .subscribe() + // Media attachment clicks + view.messagePartClickIntent + .mapNotNull(messageRepo::getPart) + .filter { part -> part.isImage() || part.isVideo() } + .autoDisposable(view.scope()) + .subscribe { part -> navigator.showMedia(part.id) } + + // Non-media attachment clicks + view.messagePartClickIntent + .mapNotNull(messageRepo::getPart) + .filter { part -> !part.isImage() && !part.isVideo() } + .autoDisposable(view.scope()) + .subscribe { part -> + if (permissionManager.hasStorage()) { + messageRepo.savePart(part.id)?.let(navigator::viewFile) + } else { + view.requestStoragePermission() + } + } + // Update the State when the message selected count changes view.messagesSelectedIntent .map { selection -> selection.size } @@ -424,25 +441,27 @@ class ComposeViewModel @Inject constructor( // Cancel sending a message view.cancelSendingIntent + .mapNotNull(messageRepo::getMessage) .doOnNext { message -> view.setDraft(message.getText()) } .autoDisposable(view.scope()) .subscribe { message -> cancelMessage.execute(message.id) } // Set the current conversation - Observables - .combineLatest( - view.activityVisibleIntent.distinctUntilChanged(), - conversation.mapNotNull { conversation -> conversation.takeIf { it.isValid }?.id }.distinctUntilChanged()) - { visible, threadId -> - when (visible) { - true -> { - activeConversationManager.setActiveConversation(threadId) - markRead.execute(listOf(threadId)) - } - - false -> activeConversationManager.setActiveConversation(null) - } + Observables.combineLatest( + view.activityVisibleIntent.distinctUntilChanged(), + conversation.mapNotNull { conversation -> + conversation.takeIf { it.isValid }?.id + }.distinctUntilChanged()) + { visible, threadId -> + when (visible) { + true -> { + activeConversationManager.setActiveConversation(threadId) + markRead.execute(listOf(threadId)) } + + false -> activeConversationManager.setActiveConversation(null) + } + } .autoDisposable(view.scope()) .subscribe() @@ -515,7 +534,7 @@ class ComposeViewModel @Inject constructor( // Contact was selected for attachment view.contactSelectedIntent .map { uri -> Attachment.Contact(getVCard(uri)!!) } - .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment} + .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment } .subscribeOn(Schedulers.io()) .autoDisposable(view.scope()) .subscribe(attachments::onNext) { error -> @@ -595,14 +614,12 @@ class ComposeViewModel @Inject constructor( // Send a message when the send button is clicked, and disable editing mode if it's enabled view.sendIntent - .filter { - val hasPermission = permissionManager.hasSendSms() - if (!hasPermission) view.requestSmsPermission() - hasPermission - } + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } + .filter { permissionManager.hasSendSms().also { if (!it) view.requestSmsPermission() } } .withLatestFrom(view.textChangedIntent) { _, body -> body } .map { body -> body.toString() } - .withLatestFrom(state, attachments, conversation, selectedContacts) { body, state, attachments, conversation, contacts -> + .withLatestFrom(state, attachments, conversation, selectedContacts) { body, state, attachments, + conversation, contacts -> val subId = state.subscription?.subscriptionId ?: -1 val addresses = when (conversation.recipients.isNotEmpty()) { true -> conversation.recipients.map { it.address } @@ -619,15 +636,20 @@ class ComposeViewModel @Inject constructor( // Scheduling a message state.scheduled != 0L -> { newState { copy(scheduled = 0) } - val uris = attachments.mapNotNull { it as? Attachment.Image }.map { it.getUri() }.map { it.toString() } - val params = AddScheduledMessage.Params(state.scheduled, subId, addresses, state.sendAsGroup, body, uris) + val uris = attachments + .mapNotNull { it as? Attachment.Image } + .map { it.getUri() } + .map { it.toString() } + val params = AddScheduledMessage + .Params(state.scheduled, subId, addresses, state.sendAsGroup, body, uris) addScheduledMessage.execute(params) context.makeToast(R.string.compose_scheduled_toast) } // Sending a group message state.sendAsGroup -> { - sendMessage.execute(SendMessage.Params(subId, conversation.id, addresses, body, attachments, delay)) + sendMessage.execute(SendMessage + .Params(subId, conversation.id, addresses, body, attachments, delay)) } // Sending a message to an existing conversation with one recipient @@ -638,15 +660,20 @@ class ComposeViewModel @Inject constructor( // Create a new conversation with one address addresses.size == 1 -> { - sendMessage.execute(SendMessage.Params(subId, threadId, addresses, body, attachments, delay)) + sendMessage.execute(SendMessage + .Params(subId, threadId, addresses, body, attachments, delay)) } // Send a message to multiple addresses else -> { addresses.forEach { addr -> - val threadId = tryOrNull(false) { TelephonyCompat.getOrCreateThreadId(context, addr) } ?: 0 - val address = listOf(conversationRepo.getConversation(threadId)?.recipients?.firstOrNull()?.address ?: addr) - sendMessage.execute(SendMessage.Params(subId, threadId, address, body, attachments, delay)) + val threadId = tryOrNull(false) { + TelephonyCompat.getOrCreateThreadId(context, addr) + } ?: 0 + val address = listOf(conversationRepo + .getConversation(threadId)?.recipients?.firstOrNull()?.address ?: addr) + sendMessage.execute(SendMessage + .Params(subId, threadId, address, body, attachments, delay)) } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt index cdceeda050891f97b80593f632ba6ac16e8e1506..524bb783c19258c4dffe8d5f272f27faf6330c7c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ContactAdapter.kt @@ -62,8 +62,8 @@ class ContactAdapter @Inject constructor() : QkAdapter() { view.avatar.setContact(contact) view.name.text = contact.name view.name.setVisible(view.name.text.isNotEmpty()) - view.address.text = contact.numbers.first()?.address ?: "" - view.type.text = contact.numbers.first()?.type ?: "" + view.address.text = contact.numbers.firstOrNull()?.address ?: "" + view.type.text = contact.numbers.firstOrNull()?.type ?: "" val adapter = view.addresses.adapter as PhoneNumberAdapter adapter.contact = contact diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt index df60bbee512ceff78f4d030cd53949b84b2cc596..9443fb340e29196663157d7d34279180c31ee070 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/MessagesAdapter.kt @@ -22,7 +22,6 @@ import android.animation.ObjectAnimator import android.content.Context import android.graphics.Typeface import android.os.Build -import android.telephony.PhoneNumberUtils import android.text.Layout import android.text.Spannable import android.text.SpannableString @@ -35,11 +34,11 @@ import android.widget.ProgressBar import androidx.recyclerview.widget.RecyclerView import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R -import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.common.util.TextViewStyler import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.common.util.extensions.forwardTouches import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -47,15 +46,15 @@ import com.moez.QKSMS.common.util.extensions.setPadding import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.compat.SubscriptionManagerCompat -import com.moez.QKSMS.extensions.isImage -import com.moez.QKSMS.extensions.isVCard -import com.moez.QKSMS.extensions.isVideo +import com.moez.QKSMS.extensions.isSmil +import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup import com.moez.QKSMS.feature.compose.BubbleUtils.getBubble import com.moez.QKSMS.feature.compose.part.PartsAdapter import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.Recipient +import com.moez.QKSMS.util.PhoneNumberUtils import com.moez.QKSMS.util.Preferences import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject @@ -64,23 +63,33 @@ import kotlinx.android.synthetic.main.message_list_item_in.view.* import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject +import javax.inject.Provider class MessagesAdapter @Inject constructor( + subscriptionManager: SubscriptionManagerCompat, private val context: Context, private val colors: Colors, private val dateFormatter: DateFormatter, - private val navigator: Navigator, + private val partsAdapterProvider: Provider, + private val phoneNumberUtils: PhoneNumberUtils, private val prefs: Preferences, - private val subscriptionManager: SubscriptionManagerCompat + private val textViewStyler: TextViewStyler ) : QkRealmAdapter() { companion object { private const val VIEW_TYPE_MESSAGE_IN = 0 private const val VIEW_TYPE_MESSAGE_OUT = 1 + + // Thanks to Cory Kilger for this regex + // https://gist.github.com/cmkilger/b8f7dba3e76244a84e7e + private val EMOJI_REGEX = Regex( + "^[\\s\n\r]*(?:(?:[\u00a9\u00ae\u203c\u2049\u2122\u2139\u2194-\u2199\u21a9-\u21aa\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa\u24c2\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614-\u2615\u2618\u261d\u2620\u2622-\u2623\u2626\u262a\u262e-\u262f\u2638-\u263a\u2648-\u2653\u2660\u2663\u2665-\u2666\u2668\u267b\u267f\u2692-\u2694\u2696-\u2697\u2699\u269b-\u269c\u26a0-\u26a1\u26aa-\u26ab\u26b0-\u26b1\u26bd-\u26be\u26c4-\u26c5\u26c8\u26ce-\u26cf\u26d1\u26d3-\u26d4\u26e9-\u26ea\u26f0-\u26f5\u26f7-\u26fa\u26fd\u2702\u2705\u2708-\u270d\u270f\u2712\u2714\u2716\u271d\u2721\u2728\u2733-\u2734\u2744\u2747\u274c\u274e\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797\u27a1\u27b0\u27bf\u2934-\u2935\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\ud83c\udc04\ud83c\udccf\ud83c\udd70-\ud83c\udd71\ud83c\udd7e-\ud83c\udd7f\ud83c\udd8e\ud83c\udd91-\ud83c\udd9a\ud83c\ude01-\ud83c\ude02\ud83c\ude1a\ud83c\ude2f\ud83c\ude32-\ud83c\ude3a\ud83c\ude50-\ud83c\ude51\u200d\ud83c\udf00-\ud83d\uddff\ud83d\ude00-\ud83d\ude4f\ud83d\ude80-\ud83d\udeff\ud83e\udd00-\ud83e\uddff\udb40\udc20-\udb40\udc7f]|\u200d[\u2640\u2642]|[\ud83c\udde6-\ud83c\uddff]{2}|.[\u20e0\u20e3\ufe0f]+)+[\\s\n\r]*)+$") + } - val clicks: Subject = PublishSubject.create() - val cancelSending: Subject = PublishSubject.create() + val clicks: Subject = PublishSubject.create() + val partClicks: Subject = PublishSubject.create() + val cancelSending: Subject = PublishSubject.create() var data: Pair>? = null set(value) { @@ -145,7 +154,9 @@ class MessagesAdapter @Inject constructor( view.body.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE } - view.attachments.adapter = PartsAdapter(context, navigator, theme) + val partsAdapter = partsAdapterProvider.get() + partsAdapter.clicks.subscribe(partClicks) + view.attachments.adapter = partsAdapter view.attachments.setRecycledViewPool(partsViewPool) view.body.forwardTouches(view) @@ -155,7 +166,7 @@ class MessagesAdapter @Inject constructor( when (toggleSelection(message.id, false)) { true -> view.isActivated = isSelected(message.id) false -> { - clicks.onNext(message) + clicks.onNext(message.id) expanded[message.id] = view.status.visibility != View.VISIBLE notifyItemChanged(adapterPosition) } @@ -176,16 +187,14 @@ class MessagesAdapter @Inject constructor( val next = if (position == itemCount - 1) null else getItem(position + 1) val view = viewHolder.containerView - // Update the selected state view.isActivated = isSelected(message.id) || highlight == message.id - // Bind the cancel view view.findViewById(R.id.cancel)?.let { cancel -> val isCancellable = message.isSending() && message.date > System.currentTimeMillis() cancel.setVisible(isCancellable) - cancel.clicks().subscribe { cancelSending.onNext(message) } + cancel.clicks().subscribe { cancelSending.onNext(message.id) } cancel.progress = 2 if (isCancellable) { @@ -203,11 +212,9 @@ class MessagesAdapter @Inject constructor( } } - // Bind the message status bindStatus(viewHolder, message, next) - // Bind the timestamp val timeSincePrevious = TimeUnit.MILLISECONDS.toMinutes(message.date - (previous?.date ?: 0)) val simIndex = subs.takeIf { it.size > 1 }?.indexOfFirst { it.subscriptionId == message.subId } ?: -1 @@ -215,16 +222,15 @@ class MessagesAdapter @Inject constructor( view.timestamp.text = dateFormatter.getMessageTimestamp(message.date) view.simIndex.text = "${simIndex + 1}" - view.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD || message.subId != previous?.subId && simIndex != -1) + view.timestamp.setVisible(timeSincePrevious >= BubbleUtils.TIMESTAMP_THRESHOLD + || message.subId != previous?.subId && simIndex != -1) view.sim.setVisible(message.subId != previous?.subId && simIndex != -1) view.simIndex.setVisible(message.subId != previous?.subId && simIndex != -1) - // Bind the grouping - val media = message.parts.filter { it.isImage() || it.isVideo() || it.isVCard() } + val media = message.parts.filter { !it.isSmil() && !it.isText() } view.setPadding(bottom = if (canGroup(message, next)) 0 else 16.dpToPx(context)) - // Bind the avatar if (!message.isMe()) { view.avatar.threadId = conversation?.id ?: 0 @@ -232,37 +238,45 @@ class MessagesAdapter @Inject constructor( view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE) } - // Bind the body text - view.body.text = when (message.isSms()) { + val messageText = when (message.isSms()) { true -> message.body false -> { val subject = message.getCleansedSubject() val body = message.parts - .filter { part -> !part.isVCard() } + .filter { part -> part.isText() } .mapNotNull { part -> part.text } - .filter { part -> part.isNotBlank() } + .filter { text -> text.isNotBlank() } .joinToString("\n") when { subject.isNotBlank() -> { val spannable = SpannableString(if (body.isNotBlank()) "$subject\n$body" else subject) - spannable.setSpan(StyleSpan(Typeface.BOLD), 0, subject.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan(StyleSpan(Typeface.BOLD), 0, subject.length, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE) spannable } else -> body } } } - view.body.setVisible(message.isSms() || view.body.text.isNotBlank()) + val emojiOnly = messageText.isNotBlank() && messageText.matches(EMOJI_REGEX) + textViewStyler.setTextSize(view.body, when (emojiOnly) { + true -> TextViewStyler.SIZE_EMOJI + false -> TextViewStyler.SIZE_PRIMARY + }) + + view.body.text = messageText + view.body.setVisible(message.isSms() || messageText.isNotBlank()) view.body.setBackgroundResource(getBubble( + emojiOnly = emojiOnly, canGroupWithPrevious = canGroup(message, previous) || media.isNotEmpty(), canGroupWithNext = canGroup(message, next), isMe = message.isMe())) - // Bind the attachments val partsAdapter = view.attachments.adapter as PartsAdapter + partsAdapter.theme = theme partsAdapter.setData(message, previous, next, view) } @@ -273,7 +287,8 @@ class MessagesAdapter @Inject constructor( view.status.text = when { message.isSending() -> context.getString(R.string.message_status_sending) - message.isDelivered() -> context.getString(R.string.message_status_delivered, dateFormatter.getTimestamp(message.dateSent)) + message.isDelivered() -> context.getString(R.string.message_status_delivered, + dateFormatter.getTimestamp(message.dateSent)) message.isFailedMessage() -> context.getString(R.string.message_status_failed) // Incoming group message @@ -311,7 +326,7 @@ class MessagesAdapter @Inject constructor( override fun get(key: String): Recipient? { if (super.get(key)?.isValid != true) { - set(key, conversation?.recipients?.firstOrNull { PhoneNumberUtils.compare(it.address, key) }) + set(key, conversation?.recipients?.firstOrNull { phoneNumberUtils.compare(it.address, key) }) } return super.get(key)?.takeIf { it.isValid } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..73d9ab8362909423e4718839fc24d29a482a1916 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/FileBinder.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.compose.part + +import android.annotation.SuppressLint +import android.content.Context +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import com.moez.QKSMS.R +import com.moez.QKSMS.common.util.Colors +import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.feature.compose.BubbleUtils +import com.moez.QKSMS.model.Message +import com.moez.QKSMS.model.MmsPart +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.mms_file_list_item.view.* +import javax.inject.Inject + +class FileBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { + + override val partLayout = R.layout.mms_file_list_item + override var theme = colors.theme() + + // This is the last binder we check. If we're here, we can bind the part + override fun canBindPart(part: MmsPart) = true + + @SuppressLint("CheckResult") + override fun bindPart( + view: View, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean + ) { + BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) + .let(view.fileBackground::setBackgroundResource) + + view.setOnClickListener { clicks.onNext(part.id) } + + Observable.just(part.getUri()) + .map(context.contentResolver::openInputStream) + .map { inputStream -> inputStream.use { it.available() } } + .map { bytes -> + when (bytes) { + in 0..999 -> "$bytes B" + in 1000..999999 -> "${"%.1f".format(bytes / 1000f)} KB" + in 1000000..9999999 -> "${"%.1f".format(bytes / 1000000f)} MB" + else -> "${"%.1f".format(bytes / 1000000000f)} GB" + } + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { size -> view.size.text = size } + + view.filename.text = part.name + + val params = view.fileBackground.layoutParams as FrameLayout.LayoutParams + if (!message.isMe()) { + view.fileBackground.layoutParams = params.apply { gravity = Gravity.START } + view.fileBackground.setBackgroundTint(theme.theme) + view.icon.setTint(theme.textPrimary) + view.filename.setTextColor(theme.textPrimary) + view.size.setTextColor(theme.textTertiary) + } else { + view.fileBackground.layoutParams = params.apply { gravity = Gravity.END } + view.fileBackground.setBackgroundTint(view.context.resolveThemeColor(R.attr.bubbleColor)) + view.icon.setTint(view.context.resolveThemeColor(android.R.attr.textColorSecondary)) + view.filename.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorPrimary)) + view.size.setTextColor(view.context.resolveThemeColor(android.R.attr.textColorTertiary)) + } + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt index 62a23fa5ec6a802d5b4b9c226f98f1f656bd8eb6..dafece1c6225f4d8e2c33694a54e80d4fdf5bc68 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/MediaBinder.kt @@ -21,7 +21,7 @@ package com.moez.QKSMS.feature.compose.part import android.content.Context import android.view.View import com.moez.QKSMS.R -import com.moez.QKSMS.common.Navigator +import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.BubbleImageView import com.moez.QKSMS.extensions.isImage @@ -30,16 +30,24 @@ import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import com.moez.QKSMS.util.GlideApp import kotlinx.android.synthetic.main.mms_preview_list_item.view.* +import javax.inject.Inject -class MediaBinder(private val context: Context, private val navigator: Navigator) : PartBinder { +class MediaBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { override val partLayout = R.layout.mms_preview_list_item + override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isImage() || part.isVideo() - override fun bindPart(view: View, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean) { + override fun bindPart( + view: View, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean + ) { view.video.setVisible(part.isVideo()) - view.setOnClickListener { navigator.showMedia(part.id) } + view.setOnClickListener { clicks.onNext(part.id) } view.thumbnail.bubbleStyle = when { !canGroupWithPrevious && canGroupWithNext -> if (message.isMe()) BubbleImageView.Style.OUT_FIRST else BubbleImageView.Style.IN_FIRST diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt index 53f220fe0969cc05932d9493db75f48508e102c9..fc3fb7fc4733fbefd73f9b6b19cacb5e05d6fd43 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartBinder.kt @@ -19,15 +19,28 @@ package com.moez.QKSMS.feature.compose.part import android.view.View +import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject -interface PartBinder { +abstract class PartBinder { - val partLayout: Int + val clicks: Subject = PublishSubject.create() - fun canBindPart(part: MmsPart): Boolean + abstract val partLayout: Int - fun bindPart(view: View, part: MmsPart, message: Message, canGroupWithPrevious: Boolean, canGroupWithNext: Boolean) + abstract var theme: Colors.Theme -} \ No newline at end of file + abstract fun canBindPart(part: MmsPart): Boolean + + abstract fun bindPart( + view: View, + part: MmsPart, + message: Message, + canGroupWithPrevious: Boolean, + canGroupWithNext: Boolean + ) + +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt index a2e4a047611f805dc95b1ed10478d795cb24d08b..31c67aee5bdd8e760c84a0b26a24d442ab84f1b3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/PartsAdapter.kt @@ -18,29 +18,38 @@ */ package com.moez.QKSMS.feature.compose.part -import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.forwardTouches -import com.moez.QKSMS.extensions.isImage -import com.moez.QKSMS.extensions.isVCard -import com.moez.QKSMS.extensions.isVideo +import com.moez.QKSMS.extensions.isSmil +import com.moez.QKSMS.extensions.isText import com.moez.QKSMS.feature.compose.BubbleUtils.canGroup import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart +import io.reactivex.Observable import kotlinx.android.synthetic.main.message_list_item_in.view.* +import javax.inject.Inject -class PartsAdapter(context: Context, navigator: Navigator, theme: Colors.Theme) : QkAdapter() { +class PartsAdapter @Inject constructor( + colors: Colors, + fileBinder: FileBinder, + mediaBinder: MediaBinder, + vCardBinder: VCardBinder +) : QkAdapter() { - private val partBinders = listOf( - MediaBinder(context, navigator), - VCardBinder(context, navigator, theme) - ) + private val partBinders = listOf(mediaBinder, vCardBinder, fileBinder) + + var theme: Colors.Theme = colors.theme() + set(value) { + field = value + partBinders.forEach { binder -> binder.theme = value } + } + + val clicks: Observable = Observable.merge(partBinders.map { it.clicks }) private lateinit var message: Message private var previous: Message? = null @@ -54,7 +63,7 @@ class PartsAdapter(context: Context, navigator: Navigator, theme: Colors.Theme) this.next = next this.messageView = messageView this.bodyVisible = messageView.body.visibility == View.VISIBLE - this.data = message.parts.filter { it.isImage() || it.isVideo() || it.isVCard() } + this.data = message.parts.filter { !it.isSmil() && !it.isText() } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt index 3cafd05a2fd0827ed8fa5a49df711ea0e3858a9e..205437d4e4971f44cf26d4bf535d780150604c36 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/part/VCardBinder.kt @@ -18,13 +18,11 @@ */ package com.moez.QKSMS.feature.compose.part -import android.content.ContentUris import android.content.Context import android.view.Gravity import android.view.View import android.widget.FrameLayout import com.moez.QKSMS.R -import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.setBackgroundTint @@ -32,7 +30,6 @@ import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.extensions.isVCard import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.feature.compose.BubbleUtils -import com.moez.QKSMS.mapper.CursorToPartImpl import com.moez.QKSMS.model.Message import com.moez.QKSMS.model.MmsPart import ezvcard.Ezvcard @@ -40,14 +37,12 @@ import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.mms_vcard_list_item.view.* +import javax.inject.Inject -class VCardBinder( - private val context: Context, - private val navigator: Navigator, - private val theme: Colors.Theme -) : PartBinder { +class VCardBinder @Inject constructor(colors: Colors, private val context: Context) : PartBinder() { override val partLayout = R.layout.mms_vcard_list_item + override var theme = colors.theme() override fun canBindPart(part: MmsPart) = part.isVCard() @@ -58,13 +53,12 @@ class VCardBinder( canGroupWithPrevious: Boolean, canGroupWithNext: Boolean ) { - val uri = ContentUris.withAppendedId(CursorToPartImpl.CONTENT_URI, part.id) - val bubble = BubbleUtils.getBubble(canGroupWithPrevious, canGroupWithNext, message.isMe()) + BubbleUtils.getBubble(false, canGroupWithPrevious, canGroupWithNext, message.isMe()) + .let(view.vCardBackground::setBackgroundResource) - view.setOnClickListener { navigator.saveVcard(uri) } - view.vCardBackground.setBackgroundResource(bubble) + view.setOnClickListener { clicks.onNext(part.id) } - Observable.just(uri) + Observable.just(part.getUri()) .map(context.contentResolver::openInputStream) .mapNotNull { inputStream -> inputStream.use { Ezvcard.parse(it).first() } } .subscribeOn(Schedulers.computation()) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt index caabfebb755836f26053611b35e4899d5885a457..45a010e315a605070890dfaeb48022d4c58949c3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoController.kt @@ -18,7 +18,6 @@ */ package com.moez.QKSMS.feature.conversationinfo -import android.text.InputFilter import android.view.View import androidx.appcompat.app.AlertDialog import com.bluelinelabs.conductor.RouterTransaction @@ -28,29 +27,36 @@ import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.extensions.animateLayoutChanges -import com.moez.QKSMS.common.util.extensions.dpToPx -import com.moez.QKSMS.common.util.extensions.resolveThemeColor import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.util.extensions.setVisible -import com.moez.QKSMS.common.widget.QkEditText +import com.moez.QKSMS.common.widget.FieldDialog +import com.moez.QKSMS.feature.blocking.BlockingDialog import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoModule import com.moez.QKSMS.feature.themepicker.ThemePickerController import com.moez.QKSMS.injection.appComponent -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.conversation_info_controller.* import javax.inject.Inject -class ConversationInfoController(val threadId: Long = 0) : QkController(), ConversationInfoView { +class ConversationInfoController( + val threadId: Long = 0 +) : QkController(), ConversationInfoView { @Inject override lateinit var presenter: ConversationInfoPresenter + @Inject lateinit var blockingDialog: BlockingDialog @Inject lateinit var navigator: Navigator @Inject lateinit var recipientAdapter: ConversationRecipientAdapter @Inject lateinit var mediaAdapter: ConversationMediaAdapter @Inject lateinit var itemDecoration: GridSpacingItemDecoration + private val nameDialog: FieldDialog by lazy { + FieldDialog(activity!!, activity!!.getString(R.string.info_name), nameChangeSubject::onNext) + } + private val nameChangeSubject: Subject = PublishSubject.create() private val confirmDeleteSubject: Subject = PublishSubject.create() @@ -85,6 +91,8 @@ class ConversationInfoController(val threadId: Long = 0) : QkController = recipientAdapter.clicks + override fun nameClicks(): Observable<*> = name.clicks() override fun nameChanges(): Observable = nameChangeSubject @@ -114,9 +122,11 @@ class ConversationInfoController(val threadId: Long = 0) : QkController= 2) name.summary = state.name - notifications.setVisible(!state.blocked) + notifications.isEnabled = !state.blocked + + themePrefs.isEnabled = !state.blocked - archive.setVisible(!state.blocked) + archive.isEnabled = !state.blocked archive.title = activity?.getString(when (state.archived) { true -> R.string.info_unarchive false -> R.string.info_archive @@ -130,26 +140,7 @@ class ConversationInfoController(val threadId: Long = 0) : QkController nameChangeSubject.onNext(editText.text.toString()) } - .setNegativeButton(R.string.button_cancel, null) - .show() - } + override fun showNameDialog(name: String) = nameDialog.setText(name).show() override fun showThemePicker(threadId: Long) { router.pushController(RouterTransaction.with(ThemePickerController(threadId)) @@ -157,6 +148,14 @@ class ConversationInfoController(val threadId: Long = 0) : QkController, block: Boolean) { + blockingDialog.show(activity!!, conversations, block) + } + + override fun requestDefaultSms() { + navigator.showDefaultSmsDialog(activity!!) + } + override fun showDeleteDialog() { AlertDialog.Builder(activity!!) .setTitle(R.string.dialog_delete_title) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt index 90bd9317639026a7b2cf8a5f0194bdc280a90f0a..4f6029021e530e5450a604dd3e45625528c03803 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoPresenter.kt @@ -18,18 +18,22 @@ */ package com.moez.QKSMS.feature.conversationinfo +import androidx.lifecycle.Lifecycle import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter import com.moez.QKSMS.extensions.asObservable +import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.interactor.DeleteConversations import com.moez.QKSMS.interactor.MarkArchived -import com.moez.QKSMS.interactor.MarkBlocked import com.moez.QKSMS.interactor.MarkUnarchived -import com.moez.QKSMS.interactor.MarkUnblocked +import com.moez.QKSMS.listener.ContactAddedListener +import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.Conversation import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.Observable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.subjects.BehaviorSubject @@ -40,13 +44,13 @@ import javax.inject.Named class ConversationInfoPresenter @Inject constructor( @Named("threadId") threadId: Long, messageRepo: MessageRepository, + private val contactAddedListener: ContactAddedListener, private val conversationRepo: ConversationRepository, private val deleteConversations: DeleteConversations, private val markArchived: MarkArchived, private val markUnarchived: MarkUnarchived, - private val markBlocked: MarkBlocked, - private val markUnblocked: MarkUnblocked, - private val navigator: Navigator + private val navigator: Navigator, + private val permissionManager: PermissionManager ) : QkPresenter( ConversationInfoState(threadId = threadId, media = messageRepo.getPartsForConversation(threadId)) ) { @@ -68,8 +72,6 @@ class ConversationInfoPresenter @Inject constructor( disposables += markArchived disposables += markUnarchived - disposables += markBlocked - disposables += markUnblocked disposables += deleteConversations // Update the recipients whenever they change @@ -100,6 +102,23 @@ class ConversationInfoPresenter @Inject constructor( override fun bindIntents(view: ConversationInfoView) { super.bindIntents(view) + // Add or display the contact + view.recipientClicks() + .mapNotNull(conversationRepo::getRecipient) + .flatMap { recipient -> + val lookupKey = recipient.contact?.lookupKey + if (lookupKey != null) { + navigator.showContact(lookupKey) + Observable.empty() + } else { + // Allow the user to add the contact, then listen for changes + navigator.addContact(recipient.address) + contactAddedListener.listen(recipient.address) + } + } + .autoDisposable(view.scope(Lifecycle.Event.ON_DESTROY)) // ... this should be the default + .subscribe() + // Show the conversation title dialog view.nameClicks() .withLatestFrom(conversation) { _, conversation -> conversation } @@ -142,15 +161,11 @@ class ConversationInfoPresenter @Inject constructor( view.blockClicks() .withLatestFrom(conversation) { _, conversation -> conversation } .autoDisposable(view.scope()) - .subscribe { conversation -> - when (conversation.blocked) { - true -> markUnblocked.execute(listOf(conversation.id)) - false -> markBlocked.execute(listOf(conversation.id)) - } - } + .subscribe { conversation -> view.showBlockingDialog(listOf(conversation.id), !conversation.blocked) } // Show the delete confirmation dialog view.deleteClicks() + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } .autoDisposable(view.scope()) .subscribe { view.showDeleteDialog() } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt index eeeb0e911e78482696ab78e8006f4ff27aab3e50..e2c1d97136abc760168681f6c22e0e144ef05935 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationInfoView.kt @@ -23,6 +23,7 @@ import io.reactivex.Observable interface ConversationInfoView : QkViewContract { + fun recipientClicks(): Observable fun nameClicks(): Observable<*> fun nameChanges(): Observable fun notificationClicks(): Observable<*> @@ -34,6 +35,8 @@ interface ConversationInfoView : QkViewContract { fun showNameDialog(name: String) fun showThemePicker(threadId: Long) + fun showBlockingDialog(conversations: List, block: Boolean) + fun requestDefaultSms() fun showDeleteDialog() -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt index 83c6701380844176d6e9ae671191aec843fe988c..f98f51bd24f4552849bae3d6d30100957ad56e56 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversationinfo/ConversationRecipientAdapter.kt @@ -22,20 +22,20 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.R -import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.model.Recipient import io.reactivex.disposables.CompositeDisposable +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.conversation_recipient_list_item.view.* import javax.inject.Inject -class ConversationRecipientAdapter @Inject constructor( - private val navigator: Navigator -) : QkRealmAdapter() { +class ConversationRecipientAdapter @Inject constructor() : QkRealmAdapter() { var threadId: Long = 0L + val clicks: Subject = PublishSubject.create() private val disposables = CompositeDisposable() @@ -45,11 +45,7 @@ class ConversationRecipientAdapter @Inject constructor( return QkViewHolder(view).apply { view.setOnClickListener { val recipient = getItem(adapterPosition) ?: return@setOnClickListener - if (recipient.contact == null) { - navigator.addContact(recipient.address) - } else { - view.avatar.callOnClick() - } + clicks.onNext(recipient.id) } } } @@ -74,4 +70,4 @@ class ConversationRecipientAdapter @Inject constructor( disposables.clear() } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt index bb46fa9003db2d151da2539fe5638de3b8e00848..7b834c3c8643cbcab1f800ddfd9341e0008abca4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationItemTouchCallback.kt @@ -22,16 +22,14 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint +import android.graphics.Rect import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.R -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.dpToPx import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign @@ -39,7 +37,8 @@ import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import javax.inject.Inject - +import kotlin.math.max +import kotlin.math.min class ConversationItemTouchCallback @Inject constructor( colors: Colors, @@ -55,26 +54,27 @@ class ConversationItemTouchCallback @Inject constructor( */ var adapter: RecyclerView.Adapter<*>? = null - private val paint = Paint() + private val backgroundPaint = Paint() private var rightAction = 0 - private var rightIcon: Bitmap? = null + private var swipeRightIcon: Bitmap? = null private var leftAction = 0 - private var leftIcon: Bitmap? = null + private var swipeLeftIcon: Bitmap? = null private val iconLength = 24.dpToPx(context) init { disposables += colors.themeObservable() - .doOnNext { theme -> paint.color = theme.theme } + .doOnNext { theme -> backgroundPaint.color = theme.theme } .subscribeOn(Schedulers.io()) .subscribe() disposables += Observables - .combineLatest(prefs.swipeRight.asObservable(), prefs.swipeLeft.asObservable(), colors.themeObservable()) { right, left, theme -> + .combineLatest(prefs.swipeRight.asObservable(), prefs.swipeLeft.asObservable(), colors.themeObservable() + ) { right, left, theme -> rightAction = right - rightIcon = iconForAction(right, theme.textPrimary) + swipeRightIcon = iconForAction(right, theme.textPrimary) leftAction = left - leftIcon = iconForAction(left, theme.textPrimary) + swipeLeftIcon = iconForAction(left, theme.textPrimary) setDefaultSwipeDirs((if (right == Preferences.SWIPE_ACTION_NONE) 0 else ItemTouchHelper.RIGHT) or (if (left == Preferences.SWIPE_ACTION_NONE) 0 else ItemTouchHelper.LEFT)) } @@ -82,24 +82,53 @@ class ConversationItemTouchCallback @Inject constructor( .subscribe() } - override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { return false } - override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { val itemView = viewHolder.itemView if (dX > 0) { - c.drawRect(itemView.left.toFloat(), itemView.top.toFloat(), dX, itemView.bottom.toFloat(), paint) - c.drawBitmap(rightIcon, itemView.left.toFloat() + iconLength, - itemView.top.toFloat() + (itemView.bottom.toFloat() - itemView.top.toFloat() - (rightIcon?.height - ?: 0)) / 2, paint) + c.drawRect(itemView.left.toFloat(), itemView.top.toFloat(), + dX, itemView.bottom.toFloat(), backgroundPaint) + + swipeRightIcon?.let { icon -> + val availablePx = dX.toInt() - iconLength + if (availablePx > 0) { + val src = Rect(0, 0, min(availablePx, icon.width), icon.height) + val dstTop = itemView.top + (itemView.bottom - itemView.top - icon.height) / 2 + val dst = Rect(iconLength, dstTop, iconLength + src.width(), dstTop + src.height()) + c.drawBitmap(icon, src, dst, null) + } + } } else if (dX < 0) { - c.drawRect(itemView.right.toFloat() + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat(), paint) - c.drawBitmap(leftIcon, itemView.right.toFloat() - iconLength - (leftIcon?.width ?: 0), - itemView.top.toFloat() + (itemView.bottom.toFloat() - itemView.top.toFloat() - (leftIcon?.height - ?: 0)) / 2, paint) + c.drawRect(itemView.right.toFloat() + dX, itemView.top.toFloat(), + itemView.right.toFloat(), itemView.bottom.toFloat(), backgroundPaint) + + swipeLeftIcon?.let { icon -> + val availablePx = -dX.toInt() - iconLength + if (availablePx > 0) { + val src = Rect(max(0, icon.width - availablePx), 0, icon.width, icon.height) + val dstTop = itemView.top + (itemView.bottom - itemView.top - icon.height) / 2 + val dst = Rect(itemView.right - iconLength - src.width(), dstTop, + itemView.right - iconLength, dstTop + src.height()) + c.drawBitmap(icon, src, dst, null) + } + } } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) @@ -122,6 +151,7 @@ class ConversationItemTouchCallback @Inject constructor( Preferences.SWIPE_ACTION_DELETE -> R.drawable.ic_delete_white_24dp Preferences.SWIPE_ACTION_CALL -> R.drawable.ic_call_white_24dp Preferences.SWIPE_ACTION_READ -> R.drawable.ic_check_white_24dp + Preferences.SWIPE_ACTION_UNREAD -> R.drawable.ic_markunread_black_24dp else -> null } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt index 5413f23197b31c7530d0649160d1581c3130d932..387cd1eb3780e14cbb3b8cf49fdac5cf325da060 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/conversations/ConversationsAdapter.kt @@ -27,13 +27,16 @@ import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkRealmAdapter import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.model.Conversation import kotlinx.android.synthetic.main.conversation_list_item.view.* import javax.inject.Inject class ConversationsAdapter @Inject constructor( + private val colors: Colors, private val context: Context, private val dateFormatter: DateFormatter, private val navigator: Navigator @@ -56,6 +59,9 @@ class ConversationsAdapter @Inject constructor( view.snippet.setTextColor(textColorPrimary) view.snippet.maxLines = 5 + view.unread.isVisible = true + view.unread.setTint(colors.theme().theme) + view.date.setTypeface(view.date.typeface, Typeface.BOLD) view.date.setTextColor(textColorPrimary) } @@ -84,11 +90,13 @@ class ConversationsAdapter @Inject constructor( view.isActivated = isSelected(conversation.id) view.avatars.contacts = conversation.recipients + view.title.collapseEnabled = conversation.recipients.size > 1 view.title.text = conversation.getTitle() view.date.text = dateFormatter.getConversationTimestamp(conversation.date) - view.snippet.text = when (conversation.me) { - true -> context.getString(R.string.main_sender_you, conversation.snippet) - false -> conversation.snippet + view.snippet.text = when { + conversation.draft.isNotEmpty() -> context.getString(R.string.main_draft, conversation.draft) + conversation.me -> context.getString(R.string.main_sender_you, conversation.snippet) + else -> conversation.snippet } view.pinned.isVisible = conversation.pinned } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt index 2b121b1f232a9738821af9b3f355be4752cec00f..868fd3c2dd56a567f0dce253d12e1936abb6edc7 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryActivity.kt @@ -23,6 +23,7 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.annotation.NonNull +import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider @@ -32,7 +33,6 @@ import androidx.viewpager2.widget.ViewPager2 import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkActivity import com.moez.QKSMS.common.util.DateFormatter -import com.moez.QKSMS.common.util.extensions.addOnPageChangeListener import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.model.MmsPart import dagger.android.AndroidInjection @@ -56,6 +56,7 @@ class GalleryActivity : QkActivity(), GalleryView { private val permissionResultSubject: Subject = PublishSubject.create() override fun onCreate(savedInstanceState: Bundle?) { + delegate.setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES) AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.gallery_activity) @@ -103,6 +104,8 @@ class GalleryActivity : QkActivity(), GalleryView { override fun pageChanged(): Observable = pageChangedSubject + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.gallery, menu) return super.onCreateOptionsMenu(menu) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt index fea79fed68c04f7d3dbf759fc544c16734017507..0e487a6b025841c8d1fa45e41d566e380b9c9456 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/gallery/GalleryViewModel.kt @@ -20,7 +20,6 @@ package com.moez.QKSMS.feature.gallery import android.content.Context import com.moez.QKSMS.R -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.extensions.mapNotNull @@ -28,7 +27,8 @@ import com.moez.QKSMS.interactor.SaveImage import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Flowable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom @@ -49,7 +49,11 @@ class GalleryViewModel @Inject constructor( .mapNotNull(messageRepo::getMessageForPart) .mapNotNull { message -> message.threadId } .doOnNext { threadId -> newState { copy(parts = messageRepo.getPartsForConversation(threadId)) } } - .doOnNext { threadId -> newState { copy(title = conversationRepo.getConversation(threadId)?.getTitle()) } } + .doOnNext { threadId -> + newState { + copy(title = conversationRepo.getConversation(threadId)?.getTitle()) + } + } .subscribe() } @@ -72,6 +76,7 @@ class GalleryViewModel @Inject constructor( // Save image to device view.optionsItemSelected() .filter { itemId -> itemId == R.id.save } + .filter { permissionManager.hasStorage().also { if (!it) view.requestStoragePermission() } } .withLatestFrom(view.pageChanged()) { _, part -> part.id } .autoDisposable(view.scope()) .subscribe { diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt index f5e0f6e2eaf0089c59e1f9b28eaa57ba0ebad5bd..39c333845bac6d868d0f28ba4d7a4ce13e8a49e4 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainActivity.kt @@ -21,6 +21,7 @@ package com.moez.QKSMS.feature.main import android.Manifest import android.animation.ObjectAnimator import android.app.AlertDialog +import android.content.Intent import android.content.res.ColorStateList import android.os.Bundle import android.view.Gravity @@ -31,7 +32,6 @@ import android.view.ViewStub import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.app.ActivityCompat import androidx.core.view.GravityCompat -import androidx.core.view.accessibility.AccessibilityEventCompat.setAction import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders @@ -43,7 +43,6 @@ import com.jakewharton.rxbinding2.widget.textChanges import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.androidxcompat.drawerOpen -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.extensions.autoScrollToStart import com.moez.QKSMS.common.util.extensions.dismissKeyboard @@ -52,10 +51,14 @@ import com.moez.QKSMS.common.util.extensions.scrapViews import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.feature.blocking.BlockingDialog +import com.moez.QKSMS.feature.changelog.ChangelogDialog import com.moez.QKSMS.feature.conversations.ConversationItemTouchCallback import com.moez.QKSMS.feature.conversations.ConversationsAdapter +import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.repository.SyncRepository -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable @@ -69,6 +72,7 @@ import javax.inject.Inject class MainActivity : QkThemedActivity(), MainView { + @Inject lateinit var blockingDialog: BlockingDialog @Inject lateinit var disposables: CompositeDisposable @Inject lateinit var navigator: Navigator @Inject lateinit var conversationsAdapter: ConversationsAdapter @@ -77,6 +81,7 @@ class MainActivity : QkThemedActivity(), MainView { @Inject lateinit var itemTouchCallback: ConversationItemTouchCallback @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + override val onNewIntentIntent: Subject = PublishSubject.create() override val activityResumedIntent: Subject = PublishSubject.create() override val queryChangedIntent by lazy { toolbarSearch.textChanges() } override val composeIntent by lazy { compose.clicks() } @@ -86,42 +91,45 @@ class MainActivity : QkThemedActivity(), MainView { .doOnNext { dismissKeyboard() } } override val homeIntent: Subject = PublishSubject.create() - override val drawerItemIntent: Observable by lazy { + override val navigationIntent: Observable by lazy { Observable.merge(listOf( - inbox.clicks().map { DrawerItem.INBOX }, - archived.clicks().map { DrawerItem.ARCHIVED }, - scheduled.clicks().map { DrawerItem.SCHEDULED }, - blocking.clicks().map { DrawerItem.BLOCKING }, - settings.clicks().map { DrawerItem.SETTINGS })) + backPressedSubject, + inbox.clicks().map { NavItem.INBOX }, + archived.clicks().map { NavItem.ARCHIVED }, + backup.clicks().map { NavItem.BACKUP }, + scheduled.clicks().map { NavItem.SCHEDULED }, + blocking.clicks().map { NavItem.BLOCKING }, + settings.clicks().map { NavItem.SETTINGS }, + plus.clicks().map { NavItem.PLUS }, + help.clicks().map { NavItem.HELP }, + invite.clicks().map { NavItem.INVITE })) } override val optionsItemIntent: Subject = PublishSubject.create() override val conversationsSelectedIntent by lazy { conversationsAdapter.selectionChanges } override val confirmDeleteIntent: Subject> = PublishSubject.create() override val swipeConversationIntent by lazy { itemTouchCallback.swipes } + override val changelogMoreIntent by lazy { changelogDialog.moreClicks } override val undoArchiveIntent: Subject = PublishSubject.create() override val snackbarButtonIntent: Subject = PublishSubject.create() - override val backPressedIntent: Subject = PublishSubject.create() private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[MainViewModel::class.java] } private val toggle by lazy { ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.main_drawer_open_cd, 0) } private val itemTouchHelper by lazy { ItemTouchHelper(itemTouchCallback) } private val progressAnimator by lazy { ObjectAnimator.ofInt(syncingProgress, "progress", 0, 0) } - private val archiveSnackbar by lazy { - Snackbar.make(drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { - setAction(R.string.button_undo) { undoArchiveIntent.onNext(Unit) } - } - } + private val changelogDialog by lazy { ChangelogDialog(this) } private val snackbar by lazy { findViewById(R.id.snackbar) } private val syncing by lazy { findViewById(R.id.syncing) } + private val backPressedSubject: Subject = PublishSubject.create() override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) viewModel.bindView(this) + onNewIntentIntent.onNext(intent) (snackbar as? ViewStub)?.setOnInflateListener { _, _ -> - snackbarButton.clicks().subscribe(snackbarButtonIntent) + snackbarButton.clicks().autoDisposable(scope()).subscribe(snackbarButtonIntent) } (syncing as? ViewStub)?.setOnInflateListener { _, _ -> @@ -139,7 +147,7 @@ class MainActivity : QkThemedActivity(), MainView { recyclerView.layoutManager = LinearLayoutManager(this) // Don't allow clicks to pass through the drawer layout - drawer.clicks().subscribe() + drawer.clicks().autoDisposable(scope()).subscribe() // Set the theme color tint to the recyclerView, progressbar, and FAB theme @@ -147,7 +155,8 @@ class MainActivity : QkThemedActivity(), MainView { .autoDisposable(scope()) .subscribe { theme -> // Set the color for the drawer icons - val states = arrayOf(intArrayOf(android.R.attr.state_activated), intArrayOf(-android.R.attr.state_activated)) + val states = arrayOf(intArrayOf(android.R.attr.state_activated), + intArrayOf(-android.R.attr.state_activated)) resolveThemeColor(android.R.attr.textColorSecondary) .let { textSecondary -> ColorStateList(states, intArrayOf(theme.theme, textSecondary)) } .let { tintList -> @@ -168,12 +177,23 @@ class MainActivity : QkThemedActivity(), MainView { conversationsAdapter.autoScrollToStart(recyclerView) } + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.run(onNewIntentIntent::onNext) + } + override fun render(state: MainState) { if (state.hasError) { finish() return } + val addContact = when (state.page) { + is Inbox -> state.page.addContact + is Archived -> state.page.addContact + else -> false + } + val markPinned = when (state.page) { is Inbox -> state.page.markPinned is Archived -> state.page.markPinned @@ -198,6 +218,7 @@ class MainActivity : QkThemedActivity(), MainView { toolbar.menu.findItem(R.id.archive)?.isVisible = state.page is Inbox && selectedConversations != 0 toolbar.menu.findItem(R.id.unarchive)?.isVisible = state.page is Archived && selectedConversations != 0 toolbar.menu.findItem(R.id.delete)?.isVisible = selectedConversations != 0 + toolbar.menu.findItem(R.id.add)?.isVisible = addContact && selectedConversations != 0 toolbar.menu.findItem(R.id.pin)?.isVisible = markPinned && selectedConversations != 0 toolbar.menu.findItem(R.id.unpin)?.isVisible = !markPinned && selectedConversations != 0 toolbar.menu.findItem(R.id.read)?.isVisible = markRead && selectedConversations != 0 @@ -241,8 +262,10 @@ class MainActivity : QkThemedActivity(), MainView { inbox.isActivated = state.page is Inbox archived.isActivated = state.page is Archived - if (drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) drawerLayout.closeDrawer(GravityCompat.START) - else if (!drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) drawerLayout.openDrawer(GravityCompat.START) + if (drawerLayout.isDrawerOpen(GravityCompat.START) && !state.drawerOpen) drawerLayout.closeDrawer( + GravityCompat.START) + else if (!drawerLayout.isDrawerVisible(GravityCompat.START) && state.drawerOpen) drawerLayout.openDrawer( + GravityCompat.START) when (state.syncing) { is SyncRepository.SyncProgress.Idle -> { @@ -298,6 +321,10 @@ class MainActivity : QkThemedActivity(), MainView { } } + override fun requestDefaultSms() { + navigator.showDefaultSmsDialog(this) + } + override fun requestPermissions() { ActivityCompat.requestPermissions(this, arrayOf( Manifest.permission.READ_SMS, @@ -314,6 +341,10 @@ class MainActivity : QkThemedActivity(), MainView { conversationsAdapter.clearSelection() } + override fun showBlockingDialog(conversations: List, block: Boolean) { + blockingDialog.show(this, conversations, block) + } + override fun showDeleteDialog(conversations: List) { val count = conversations.size AlertDialog.Builder(this) @@ -324,8 +355,16 @@ class MainActivity : QkThemedActivity(), MainView { .show() } + override fun showChangelog(changelog: ChangelogManager.Changelog) { + changelogDialog.show(changelog) + } + override fun showArchivedSnackbar() { - archiveSnackbar.show() + Snackbar.make(drawerLayout, R.string.toast_archived, Snackbar.LENGTH_LONG).apply { + setAction(R.string.button_undo) { undoArchiveIntent.onNext(Unit) } + setActionTextColor(colors.theme().theme) + show() + } } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -339,7 +378,7 @@ class MainActivity : QkThemedActivity(), MainView { } override fun onBackPressed() { - backPressedIntent.onNext(Unit) + backPressedSubject.onNext(NavItem.BACK) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt index a56ea63d0297c464e2780bdfd67eccba114d5a6b..39cf39aebddd2f39f3f04a5d30a3964602aa8b40 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainState.kt @@ -37,6 +37,7 @@ data class MainState( sealed class MainPage data class Inbox( + val addContact: Boolean = false, val markPinned: Boolean = true, val markRead: Boolean = false, val data: RealmResults? = null, @@ -49,6 +50,7 @@ data class Searching( ) : MainPage() data class Archived( + val addContact: Boolean = false, val markPinned: Boolean = true, val markRead: Boolean = false, val data: RealmResults? = null, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt index 8e213a5802e9dd8ba62cb0d8dab8e8a50ea80ed2..79ad8aef56e69b3bd84d5f36b197d4daa1e8e4c9 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainView.kt @@ -18,31 +18,37 @@ */ package com.moez.QKSMS.feature.main +import android.content.Intent import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.manager.ChangelogManager import io.reactivex.Observable interface MainView : QkView { + val onNewIntentIntent: Observable val activityResumedIntent: Observable<*> val queryChangedIntent: Observable val composeIntent: Observable val drawerOpenIntent: Observable val homeIntent: Observable<*> - val drawerItemIntent: Observable + val navigationIntent: Observable val optionsItemIntent: Observable val conversationsSelectedIntent: Observable> val confirmDeleteIntent: Observable> val swipeConversationIntent: Observable> + val changelogMoreIntent: Observable<*> val undoArchiveIntent: Observable val snackbarButtonIntent: Observable - val backPressedIntent: Observable + fun requestDefaultSms() fun requestPermissions() fun clearSearch() fun clearSelection() + fun showBlockingDialog(conversations: List, block: Boolean) fun showDeleteDialog(conversations: List) + fun showChangelog(changelog: ChangelogManager.Changelog) fun showArchivedSnackbar() } -enum class DrawerItem { INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, PLUS } \ No newline at end of file +enum class NavItem { BACK, INBOX, ARCHIVED, BACKUP, SCHEDULED, BLOCKING, SETTINGS, PLUS, HELP, INVITE } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt index 802cfdaca3593049753191407823480fbd92da3b..7ffd1292931d0071a270b61efba7eb52f14293c3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/MainViewModel.kt @@ -21,22 +21,34 @@ package com.moez.QKSMS.feature.main import androidx.recyclerview.widget.ItemTouchHelper import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel -import com.moez.QKSMS.extensions.removeAccents -import com.moez.QKSMS.interactor.* +import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.extensions.mapNotNull +import com.moez.QKSMS.interactor.DeleteConversations +import com.moez.QKSMS.interactor.MarkAllSeen +import com.moez.QKSMS.interactor.MarkArchived +import com.moez.QKSMS.interactor.MarkPinned +import com.moez.QKSMS.interactor.MarkRead +import com.moez.QKSMS.interactor.MarkUnarchived +import com.moez.QKSMS.interactor.MarkUnpinned +import com.moez.QKSMS.interactor.MarkUnread +import com.moez.QKSMS.interactor.MigratePreferences +import com.moez.QKSMS.interactor.SyncMessages +import com.moez.QKSMS.listener.ContactAddedListener +import com.moez.QKSMS.manager.ChangelogManager import com.moez.QKSMS.manager.PermissionManager import com.moez.QKSMS.model.SyncLog import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable -import io.reactivex.Observable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import io.reactivex.schedulers.Schedulers import io.realm.Realm +import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -44,10 +56,11 @@ class MainViewModel @Inject constructor( markAllSeen: MarkAllSeen, migratePreferences: MigratePreferences, syncRepository: SyncRepository, + private val contactAddedListener: ContactAddedListener, + private val changelogManager: ChangelogManager, private val conversationRepo: ConversationRepository, private val deleteConversations: DeleteConversations, private val markArchived: MarkArchived, - private val markBlocked: MarkBlocked, private val markPinned: MarkPinned, private val markRead: MarkRead, private val markUnarchived: MarkUnarchived, @@ -91,7 +104,7 @@ class MainViewModel @Inject constructor( super.bindView(view) when { - !permissionManager.isDefaultSms() -> navigator.showDefaultSmsDialog() + !permissionManager.isDefaultSms() -> view.requestDefaultSms() !permissionManager.hasReadSms() || !permissionManager.hasContacts() -> view.requestPermissions() } @@ -117,10 +130,38 @@ class MainViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { syncMessages.execute(Unit) } + // Launch screen from intent + view.onNewIntentIntent + .autoDisposable(view.scope()) + .subscribe { intent -> + when (intent.getStringExtra("screen")) { + "blocking" -> navigator.showBlockedConversations() + } + } + + // Show changelog + if (changelogManager.didUpdate()) { + if (Locale.getDefault().language.startsWith("en")) { + disposables += changelogManager.getChangelog() + .timeout(3, TimeUnit.SECONDS) // If it takes long than 3s, we'll just try again next time + .subscribe({ changelog -> + changelogManager.markChangelogSeen() + view.showChangelog(changelog) + }, {}) // Ignore error + } else { + changelogManager.markChangelogSeen() + } + } else { + changelogManager.markChangelogSeen() + } + + view.changelogMoreIntent + .autoDisposable(view.scope()) + .subscribe { navigator.showChangelog() } + view.queryChangedIntent .debounce(200, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .map { query -> query.removeAccents() } .withLatestFrom(state) { query, state -> if (query.isEmpty() && state.page is Searching) { newState { copy(page = Inbox(data = conversationRepo.getConversations())) } @@ -135,7 +176,7 @@ class MainViewModel @Inject constructor( } } .observeOn(Schedulers.io()) - .switchMap { query -> Observable.just(query).map { conversationRepo.searchConversations(it) } } + .map(conversationRepo::searchConversations) .autoDisposable(view.scope()) .subscribe { data -> newState { copy(page = Searching(loading = false, data = data)) } } @@ -160,86 +201,150 @@ class MainViewModel @Inject constructor( .autoDisposable(view.scope()) .subscribe { open -> newState { copy(drawerOpen = open) } } - view.drawerItemIntent - .doOnNext { newState { copy(drawerOpen = false) } } - .doOnNext { if (it == DrawerItem.BACKUP) navigator.showBackup() } - .doOnNext { if (it == DrawerItem.SCHEDULED) navigator.showScheduled() } - .doOnNext { if (it == DrawerItem.BLOCKING) navigator.showBlockedConversations() } - .doOnNext { if (it == DrawerItem.SETTINGS) navigator.showSettings() } + view.navigationIntent + .withLatestFrom(state) { drawerItem, state -> + newState { copy(drawerOpen = false) } + when (drawerItem) { + NavItem.BACK -> when { + state.drawerOpen -> Unit + state.page is Searching -> view.clearSearch() + state.page is Inbox && state.page.selected > 0 -> view.clearSelection() + state.page is Archived && state.page.selected > 0 -> view.clearSelection() + state.page !is Inbox -> { + newState { copy(page = Inbox(data = conversationRepo.getConversations())) } + } + else -> newState { copy(hasError = true) } + } + NavItem.BACKUP -> navigator.showBackup() + NavItem.SCHEDULED -> navigator.showScheduled() + NavItem.BLOCKING -> navigator.showBlockedConversations() + NavItem.SETTINGS -> navigator.showSettings() + NavItem.PLUS -> navigator.showQksmsPlusActivity("main_menu") + NavItem.HELP -> navigator.showSupport() + NavItem.INVITE -> navigator.showInvite() + else -> Unit + } + drawerItem + } .distinctUntilChanged() - .doOnNext { - when (it) { - DrawerItem.INBOX -> newState { copy(page = Inbox(data = conversationRepo.getConversations())) } - DrawerItem.ARCHIVED -> newState { copy(page = Archived(data = conversationRepo.getConversations(true))) } - else -> { - } // Do nothing + .doOnNext { drawerItem -> + when (drawerItem) { + NavItem.INBOX -> newState { copy(page = Inbox(data = conversationRepo.getConversations())) } + NavItem.ARCHIVED -> newState { copy(page = Archived(data = conversationRepo.getConversations(true))) } + else -> Unit } } .autoDisposable(view.scope()) .subscribe() view.optionsItemIntent - .withLatestFrom(view.conversationsSelectedIntent) { itemId, conversations -> - when (itemId) { - R.id.archive -> { - markArchived.execute(conversations) - view.clearSelection() - } + .filter { itemId -> itemId == R.id.archive } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markArchived.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() - R.id.unarchive -> { - markUnarchived.execute(conversations) - view.clearSelection() - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.unarchive } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markUnarchived.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() - R.id.delete -> view.showDeleteDialog(conversations) + view.optionsItemIntent + .filter { itemId -> itemId == R.id.delete } + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + view.showDeleteDialog(conversations) + } + .autoDisposable(view.scope()) + .subscribe() - R.id.pin -> { - markPinned.execute(conversations) - view.clearSelection() - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.add } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> conversations } + .doOnNext { view.clearSelection() } + .filter { conversations -> conversations.size == 1 } + .map { conversations -> conversations.first() } + .mapNotNull(conversationRepo::getConversation) + .map { conversation -> conversation.recipients } + .mapNotNull { recipients -> recipients[0]?.address?.takeIf { recipients.size == 1 } } + .doOnNext(navigator::addContact) + .flatMap(contactAddedListener::listen) + .autoDisposable(view.scope()) + .subscribe() - R.id.unpin -> { - markUnpinned.execute(conversations) - view.clearSelection() - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.pin } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markPinned.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() - R.id.read -> { - markRead.execute(conversations) - view.clearSelection() - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.unpin } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markUnpinned.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() - R.id.unread -> { - markUnread.execute(conversations) - view.clearSelection() - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.read } + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markRead.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() - R.id.block -> { - markBlocked.execute(conversations) - view.clearSelection() - } - } + view.optionsItemIntent + .filter { itemId -> itemId == R.id.unread } + .filter { permissionManager.isDefaultSms().also { if (!it) view.requestDefaultSms() } } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + markUnread.execute(conversations) + view.clearSelection() + } + .autoDisposable(view.scope()) + .subscribe() + + view.optionsItemIntent + .filter { itemId -> itemId == R.id.block } + .withLatestFrom(view.conversationsSelectedIntent) { _, conversations -> + view.showBlockingDialog(conversations, true) + view.clearSelection() } .autoDisposable(view.scope()) .subscribe() view.conversationsSelectedIntent .withLatestFrom(state) { selection, state -> - val pin = selection - .mapNotNull(conversationRepo::getConversation) - .sumBy { if (it.pinned) -1 else 1 } >= 0 - val read = selection - .mapNotNull(conversationRepo::getConversation) - .sumBy { if (it.read) -1 else 1 } >= 0 + val conversations = selection.mapNotNull(conversationRepo::getConversation) + val add = conversations.firstOrNull() + ?.takeIf { conversations.size == 1 } + ?.takeIf { conversation -> conversation.recipients.size == 1 } + ?.recipients?.first() + ?.takeIf { recipient -> recipient.contact == null } != null + val pin = conversations.sumBy { if (it.pinned) -1 else 1 } >= 0 + val read = conversations.sumBy { if (it.read) -1 else 1 } >= 0 val selected = selection.size when (state.page) { is Inbox -> { - val page = state.page.copy(markPinned = pin, markRead = read, selected = selected) - newState { copy(page = page.copy(markRead = read, selected = selected)) } + val page = state.page.copy(addContact = add, markPinned = pin, markRead = read, selected = selected) + newState { copy(page = page) } } is Archived -> { - val page = state.page.copy(markPinned = pin, markRead = read, selected = selected) + val page = state.page.copy(addContact = add, markPinned = pin, markRead = read, selected = selected) newState { copy(page = page) } } } @@ -264,6 +369,7 @@ class MainViewModel @Inject constructor( Preferences.SWIPE_ACTION_DELETE -> view.showDeleteDialog(listOf(threadId)) Preferences.SWIPE_ACTION_CALL -> conversationRepo.getConversation(threadId)?.recipients?.firstOrNull()?.address?.let(navigator::makePhoneCall) Preferences.SWIPE_ACTION_READ -> markRead.execute(listOf(threadId)) + Preferences.SWIPE_ACTION_UNREAD -> markUnread.execute(listOf(threadId)) } } @@ -275,32 +381,13 @@ class MainViewModel @Inject constructor( view.snackbarButtonIntent .withLatestFrom(state) { _, state -> when { - !state.defaultSms -> navigator.showDefaultSmsDialog() + !state.defaultSms -> view.requestDefaultSms() !state.smsPermission -> view.requestPermissions() !state.contactPermission -> view.requestPermissions() } } .autoDisposable(view.scope()) .subscribe() - - view.backPressedIntent - .withLatestFrom(state) { _, state -> - when { - state.drawerOpen -> newState { copy(drawerOpen = false) } - - state.page is Searching -> view.clearSearch() - - state.page is Inbox && state.page.selected > 0 -> view.clearSelection() - - state.page is Archived && state.page.selected > 0 -> view.clearSelection() - - state.page !is Inbox -> newState { copy(page = Inbox(data = conversationRepo.getConversations())) } - - else -> newState { copy(hasError = true) } - } - } - .autoDisposable(view.scope()) - .subscribe() } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt index 7f32e69108163397a20a3d0be2ff68c48c49e5bd..6e429aa595b85d2f5dd1aef79df9e841ab6eef85 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/main/SearchAdapter.kt @@ -31,6 +31,7 @@ import com.moez.QKSMS.common.base.QkViewHolder import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.extensions.removeAccents import com.moez.QKSMS.model.SearchResult import kotlinx.android.synthetic.main.search_list_item.view.* import javax.inject.Inject @@ -64,7 +65,7 @@ class SearchAdapter @Inject constructor( val query = result.query val title = SpannableString(result.conversation.getTitle()) - var index = title.indexOf(query, ignoreCase = true) + var index = title.removeAccents().indexOf(query, ignoreCase = true) while (index >= 0) { title.setSpan(BackgroundColorSpan(highlightColor), index, index + query.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt index 4131b87b196716abcbc7d621f6740bef2ecabf53..b998c1d97a5f5c3c10cb3f68d7c5d375367af15f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsActivity.kt @@ -30,12 +30,12 @@ import androidx.lifecycle.ViewModelProviders import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R import com.moez.QKSMS.common.QkDialog -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkThemedActivity import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setVisible import com.moez.QKSMS.common.widget.PreferenceView -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import dagger.android.AndroidInjection import io.reactivex.Observable import io.reactivex.subjects.PublishSubject @@ -55,7 +55,9 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { override val ringtoneSelectedIntent: Subject = PublishSubject.create() override val actionsSelectedIntent by lazy { actionsDialog.adapter.menuItemClicks } - private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[NotificationPrefsViewModel::class.java] } + private val viewModel by lazy { + ViewModelProviders.of(this, viewModelFactory)[NotificationPrefsViewModel::class.java] + } override fun onCreate(savedInstanceState: Bundle?) { AndroidInjection.inject(this) @@ -112,7 +114,8 @@ class NotificationPrefsActivity : QkThemedActivity(), NotificationPrefsView { qkreplyTitle.isVisible = state.threadId == 0L qkreply.checkbox.isChecked = state.qkReplyEnabled qkreply.isVisible = state.threadId == 0L - qkreplyTapDismiss.setVisible(state.threadId == 0L && state.qkReplyEnabled) + qkreplyTapDismiss.isVisible = state.threadId == 0L + qkreplyTapDismiss.isEnabled = state.qkReplyEnabled qkreplyTapDismiss.checkbox.isChecked = state.qkReplyTapDismiss } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt index 4bfbeb44fe8ebb036feec57151baef64415bcc15..6639d4f709829cd53cb55aa87acdb7e3fb609bcc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/notificationprefs/NotificationPrefsViewModel.kt @@ -23,12 +23,12 @@ import android.media.RingtoneManager import android.net.Uri import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.extensions.mapNotNull import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Flowable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..1df78e0456445de9141c2fdf546500e4e0f904e4 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivity.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.plus + +import android.graphics.Typeface +import android.os.Bundle +import androidx.core.view.children +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import com.jakewharton.rxbinding2.view.clicks +import com.jakewharton.rxbinding2.view.enabled +import com.moez.QKSMS.BuildConfig +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.QkThemedActivity +import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.common.util.FontProvider +import com.moez.QKSMS.common.util.extensions.resolveThemeColor +import com.moez.QKSMS.common.util.extensions.setBackgroundTint +import com.moez.QKSMS.common.util.extensions.setTint +import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.widget.PreferenceView +import com.moez.QKSMS.feature.plus.experiment.UpgradeButtonExperiment +import dagger.android.AndroidInjection +import io.reactivex.Observable +import kotlinx.android.synthetic.main.collapsing_toolbar.* +import kotlinx.android.synthetic.main.preference_view.view.* +import kotlinx.android.synthetic.main.qksms_plus_activity.* +import javax.inject.Inject + +class PlusActivity : QkThemedActivity(), PlusView { + + @Inject lateinit var fontProvider: FontProvider + @Inject lateinit var upgradeButtonExperiment: UpgradeButtonExperiment + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[PlusViewModel::class.java] } + + override val upgradeIntent by lazy { upgrade.clicks() } + override val upgradeDonateIntent by lazy { upgradeDonate.clicks() } + override val donateIntent by lazy { donate.clicks() } + override val themeClicks by lazy { themes.clicks() } + override val scheduleClicks by lazy { schedule.clicks() } + override val backupClicks by lazy { backup.clicks() } + override val delayedClicks by lazy { delayed.clicks() } + override val nightClicks by lazy { night.clicks() } + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + setContentView(R.layout.qksms_plus_activity) + setTitle(R.string.title_qksms_plus) + showBackButton(true) + viewModel.bindView(this) + + free.setVisible(false) + + if (!prefs.systemFont.get()) { + fontProvider.getLato { lato -> + val typeface = Typeface.create(lato, Typeface.BOLD) + collapsingToolbar.setCollapsedTitleTypeface(typeface) + collapsingToolbar.setExpandedTitleTypeface(typeface) + } + } + + // Make the list titles bold + linearLayout.children + .mapNotNull { it as? PreferenceView } + .map { it.titleView } + .forEach { it.setTypeface(it.typeface, Typeface.BOLD) } + + val textPrimary = resolveThemeColor(android.R.attr.textColorPrimary) + collapsingToolbar.setCollapsedTitleTextColor(textPrimary) + collapsingToolbar.setExpandedTitleColor(textPrimary) + + val theme = colors.theme().theme + donate.setBackgroundTint(theme) + upgrade.setBackgroundTint(theme) + thanksIcon.setTint(theme) + } + + override fun render(state: PlusState) { + description.text = getString(R.string.qksms_plus_description_summary, state.upgradePrice) + upgrade.text = getString(upgradeButtonExperiment.variant, state.upgradePrice, state.currency) + upgradeDonate.text = getString(R.string.qksms_plus_upgrade_donate, state.upgradeDonatePrice, state.currency) + + val fdroid = BuildConfig.FLAVOR == "noAnalytics" + + free.setVisible(fdroid) + toUpgrade.setVisible(!fdroid && !state.upgraded) + upgraded.setVisible(!fdroid && state.upgraded) + + themes.isEnabled = state.upgraded + schedule.isEnabled = state.upgraded + backup.isEnabled = state.upgraded + delayed.isEnabled = state.upgraded + night.isEnabled = state.upgraded + } + + override fun initiatePurchaseFlow(billingManager: BillingManager, sku: String) { + billingManager.initiatePurchaseFlow(this, sku) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivityModule.kt similarity index 82% rename from presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivityModule.kt rename to presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivityModule.kt index 19d92193144d2886af0a662193f3a72f5e7d2581..26a3fe3d2b7cc0b1e160b180015764e828f86ab1 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/blocked/BlockedActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusActivityModule.kt @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with QKSMS. If not, see . */ -package com.moez.QKSMS.feature.blocked +package com.moez.QKSMS.feature.plus import androidx.lifecycle.ViewModel import com.moez.QKSMS.injection.ViewModelKey @@ -25,11 +25,11 @@ import dagger.Provides import dagger.multibindings.IntoMap @Module -class BlockedActivityModule { +class PlusActivityModule { @Provides @IntoMap - @ViewModelKey(BlockedViewModel::class) - fun provideBlockedViewModel(viewModel: BlockedViewModel): ViewModel = viewModel + @ViewModelKey(PlusViewModel::class) + fun providePlusViewModel(viewModel: PlusViewModel): ViewModel = viewModel } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusState.kt new file mode 100644 index 0000000000000000000000000000000000000000..b3d3276064664c76a8f70c0e019c367bec0be992 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.plus + +data class PlusState( + val upgraded: Boolean = false, + val upgradePrice: String = "", + val upgradeDonatePrice: String = "", + val currency: String = "" +) \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt new file mode 100644 index 0000000000000000000000000000000000000000..8d7c67e84bc470bb3148e6e80b7fc11974821402 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusView.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.plus + +import com.moez.QKSMS.common.base.QkView +import com.moez.QKSMS.common.util.BillingManager +import io.reactivex.Observable + +interface PlusView : QkView { + + val upgradeIntent: Observable + val upgradeDonateIntent: Observable + val donateIntent: Observable<*> + val themeClicks: Observable<*> + val scheduleClicks: Observable<*> + val backupClicks: Observable<*> + val delayedClicks: Observable<*> + val nightClicks: Observable<*> + + fun initiatePurchaseFlow(billingManager: BillingManager, sku: String) + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..a3475822d169e41b2080a944ed34a596f855ea06 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/PlusViewModel.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.plus + +import com.moez.QKSMS.common.Navigator +import com.moez.QKSMS.common.base.QkViewModel +import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.manager.AnalyticsManager +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.Observable +import io.reactivex.rxkotlin.plusAssign +import javax.inject.Inject + +class PlusViewModel @Inject constructor( + private val analyticsManager: AnalyticsManager, + private val billingManager: BillingManager, + private val navigator: Navigator +) : QkViewModel(PlusState()) { + + init { + disposables += billingManager.upgradeStatus + .subscribe { upgraded -> newState { copy(upgraded = upgraded) } } + + disposables += billingManager.products + .subscribe { products -> + newState { + val upgrade = products.firstOrNull { it.sku == BillingManager.SKU_PLUS } + val upgradeDonate = products.firstOrNull { it.sku == BillingManager.SKU_PLUS_DONATE } + copy(upgradePrice = upgrade?.price ?: "", upgradeDonatePrice = upgradeDonate?.price ?: "", + currency = upgrade?.priceCurrencyCode ?: upgradeDonate?.priceCurrencyCode ?: "") + } + } + } + + override fun bindView(view: PlusView) { + super.bindView(view) + + Observable.merge( + view.upgradeIntent.map { BillingManager.SKU_PLUS }, + view.upgradeDonateIntent.map { BillingManager.SKU_PLUS_DONATE }) + .doOnNext { sku -> analyticsManager.track("Clicked Upgrade", Pair("sku", sku)) } + .autoDisposable(view.scope()) + .subscribe { sku -> view.initiatePurchaseFlow(billingManager, sku) } + + view.donateIntent + .autoDisposable(view.scope()) + .subscribe { navigator.showDonation() } + + view.themeClicks + .autoDisposable(view.scope()) + .subscribe { navigator.showSettings() } + + view.scheduleClicks + .autoDisposable(view.scope()) + .subscribe { navigator.showScheduled() } + + view.backupClicks + .autoDisposable(view.scope()) + .subscribe { navigator.showBackup() } + + view.delayedClicks + .autoDisposable(view.scope()) + .subscribe { navigator.showSettings() } + + view.nightClicks + .autoDisposable(view.scope()) + .subscribe { navigator.showSettings() } + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/plus/experiment/UpgradeButtonExperiment.kt b/presentation/src/main/java/com/moez/QKSMS/feature/plus/experiment/UpgradeButtonExperiment.kt new file mode 100644 index 0000000000000000000000000000000000000000..c954a5592e9002dc061cdf362cf4c14acaf87182 --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/plus/experiment/UpgradeButtonExperiment.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.plus.experiment + +import android.content.Context +import androidx.annotation.StringRes +import com.moez.QKSMS.R +import com.moez.QKSMS.experiment.Experiment +import com.moez.QKSMS.experiment.Variant +import com.moez.QKSMS.manager.AnalyticsManager +import javax.inject.Inject + +class UpgradeButtonExperiment @Inject constructor( + context: Context, + analytics: AnalyticsManager +) : Experiment<@StringRes Int>(context, analytics) { + + override val key: String = "Upgrade Button" + + override val variants: List> = listOf( + Variant("variant_a", R.string.qksms_plus_upgrade), + Variant("variant_b", R.string.qksms_plus_upgrade_b), + Variant("variant_c", R.string.qksms_plus_upgrade_c), + Variant("variant_d", R.string.qksms_plus_upgrade_d)) + + override val default: Int = R.string.qksms_plus_upgrade + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt index f141efcef3384a7f9824ca554c6adf848bcb997c..ead2fe26e95b5654f0a74d321f21d0f76fee9f73 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyActivity.kt @@ -123,10 +123,9 @@ class QkReplyActivity : QkThemedActivity(), QkReplyView { return true } - override fun getActivityThemeRes(night: Boolean, black: Boolean) = when { - night && black -> R.style.AppThemeBlackDialog - night && !black -> R.style.AppThemeDarkDialog - else -> R.style.AppThemeLightDialog + override fun getActivityThemeRes(black: Boolean) = when { + black -> R.style.AppThemeDialog_Black + else -> R.style.AppThemeDialog } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt index f3dadd6c460ae9b36dcad1a7ce07476209be6414..d29c2068cf12fdda937c11f9d5f61fcaadcb6c12 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/qkreply/QkReplyViewModel.kt @@ -21,7 +21,6 @@ package com.moez.QKSMS.feature.qkreply import android.telephony.SmsMessage import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel import com.moez.QKSMS.compat.SubscriptionManagerCompat import com.moez.QKSMS.extensions.asObservable @@ -33,7 +32,8 @@ import com.moez.QKSMS.model.Message import com.moez.QKSMS.repository.ConversationRepository import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.util.ActiveSubscriptionObservable -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt index 0ac1abc42bef3924f88d392347a844034408c703..43dfcab719596e596dc5d30fe9f21cf6c09eaf9a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledMessageAdapter.kt @@ -19,7 +19,6 @@ package com.moez.QKSMS.feature.scheduled import android.net.Uri -import android.telephony.PhoneNumberUtils import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible @@ -32,6 +31,7 @@ import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.model.ScheduledMessage import com.moez.QKSMS.repository.ContactRepository +import com.moez.QKSMS.util.PhoneNumberUtils import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.scheduled_message_list_item.view.* @@ -39,7 +39,8 @@ import javax.inject.Inject class ScheduledMessageAdapter @Inject constructor( private val contactRepo: ContactRepository, - private val dateFormatter: DateFormatter + private val dateFormatter: DateFormatter, + private val phoneNumberUtils: PhoneNumberUtils ) : QkRealmAdapter() { private val contacts by lazy { contactRepo.getContacts() } @@ -91,7 +92,7 @@ class ScheduledMessageAdapter @Inject constructor( if (super.get(key)?.isValid != true) { set(key, contacts.firstOrNull { contact -> contact.numbers.any { - PhoneNumberUtils.compare(it.address, key) + phoneNumberUtils.compare(it.address, key) } }) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt index 0f13882fd9ae52d1ebc46e55057fc4f999b9476e..00883e82708c524788c1d2c77dbb4256a1680085 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/scheduled/ScheduledViewModel.kt @@ -18,16 +18,26 @@ */ package com.moez.QKSMS.feature.scheduled +import android.content.Context +import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator -import com.moez.QKSMS.common.androidxcompat.scope import com.moez.QKSMS.common.base.QkViewModel +import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.common.util.ClipboardUtils +import com.moez.QKSMS.common.util.extensions.makeToast import com.moez.QKSMS.interactor.SendScheduledMessage +import com.moez.QKSMS.repository.MessageRepository import com.moez.QKSMS.repository.ScheduledMessageRepository -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable +import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import javax.inject.Inject class ScheduledViewModel @Inject constructor( + billingManager: BillingManager, + private val context: Context, + private val messageRepo: MessageRepository, private val navigator: Navigator, private val scheduledMessageRepo: ScheduledMessageRepository, private val sendScheduledMessage: SendScheduledMessage @@ -46,8 +56,13 @@ class ScheduledViewModel @Inject constructor( .withLatestFrom(view.messageClickIntent) { itemId, messageId -> when (itemId) { 0 -> sendScheduledMessage.execute(messageId) - 1 -> scheduledMessageRepo.deleteScheduledMessage(messageId) + 1 -> scheduledMessageRepo.getScheduledMessage(messageId)?.let { message -> + ClipboardUtils.copy(context, message.body) + context.makeToast(R.string.toast_copied) + } + 2 -> scheduledMessageRepo.deleteScheduledMessage(messageId) } + Unit } .autoDisposable(view.scope()) .subscribe() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt index 2f764b874aa2a0cb59d0f468c31ae49aec06fb6c..99a3b7aa0e4d573065b611944bff81dbbdb7520f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsController.kt @@ -21,6 +21,7 @@ package com.moez.QKSMS.feature.settings import android.animation.ObjectAnimator import android.app.TimePickerDialog import android.content.Context +import android.os.Build import android.text.format.DateFormat import android.view.View import androidx.core.view.isVisible @@ -30,6 +31,7 @@ import com.jakewharton.rxbinding2.view.clicks import com.jakewharton.rxbinding2.view.longClicks import com.moez.QKSMS.BuildConfig import com.moez.QKSMS.R +import com.moez.QKSMS.common.MenuItem import com.moez.QKSMS.common.QkChangeHandler import com.moez.QKSMS.common.QkDialog import com.moez.QKSMS.common.base.QkController @@ -37,6 +39,7 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setVisible +import com.moez.QKSMS.common.widget.FieldDialog import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.feature.settings.about.AboutController import com.moez.QKSMS.feature.settings.swipe.SwipeActionsController @@ -44,7 +47,8 @@ import com.moez.QKSMS.feature.themepicker.ThemePickerController import com.moez.QKSMS.injection.appComponent import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject @@ -65,9 +69,14 @@ class SettingsController : QkController = PublishSubject.create() private val startTimeSelectedSubject: Subject> = PublishSubject.create() private val endTimeSelectedSubject: Subject> = PublishSubject.create() + private val signatureSubject: Subject = PublishSubject.create() private val progressAnimator by lazy { ObjectAnimator.ofInt(syncingProgress, "progress", 0, 0) } @@ -84,7 +93,12 @@ class SettingsController : QkController= 29) { + true -> nightModeDialog.adapter.setData(R.array.night_modes) + false -> nightModeDialog.adapter.data = context.resources.getStringArray(R.array.night_modes) + .mapIndexed { index, title -> MenuItem(title, index) } + .drop(1) + } textSizeDialog.adapter.setData(R.array.text_sizes) sendDelayDialog.adapter.setData(R.array.delayed_sending_labels) mmsSizeDialog.adapter.setData(R.array.mms_sizes, R.array.mms_sizes_ids) @@ -118,6 +132,8 @@ class SettingsController : QkController = sendDelayDialog.adapter.menuItemClicks + override fun signatureSet(): Observable = signatureSubject + override fun mmsSizeSelected(): Observable = mmsSizeDialog.adapter.menuItemClicks override fun render(state: SettingsState) { @@ -139,6 +155,8 @@ class SettingsController : QkController(SettingsState(theme = colors.theme().theme)) { + private val syncMessages: SyncMessages +) : QkPresenter(SettingsState( + nightModeId = prefs.nightMode.get() +)) { init { - newState { copy(theme = colors.theme().theme) } + disposables += colors.themeObservable() + .subscribe { theme -> newState { copy(theme = theme.theme) } } disposables += prefs.theme().asObservable() .subscribe { color -> newState { copy(theme = color) } } @@ -93,6 +99,9 @@ class SettingsPresenter @Inject constructor( disposables += prefs.delivery.asObservable() .subscribe { enabled -> newState { copy(deliveryEnabled = enabled) } } + disposables += prefs.signature.asObservable() + .subscribe { signature -> newState { copy(signature = signature) } } + val textSizeLabels = context.resources.getStringArray(R.array.text_sizes) disposables += prefs.textSize.asObservable() .subscribe { textSize -> @@ -165,6 +174,8 @@ class SettingsPresenter @Inject constructor( R.id.delivery -> prefs.delivery.set(!prefs.delivery.get()) + R.id.signature -> view.showSignatureDialog(prefs.signature.get()) + R.id.textSize -> view.showTextSizePicker() R.id.systemFont -> prefs.systemFont.set(!prefs.systemFont.get()) @@ -206,15 +217,20 @@ class SettingsPresenter @Inject constructor( view.textSizeSelected() .autoDisposable(view.scope()) - .subscribe { prefs.textSize.set(it) } + .subscribe(prefs.textSize::set) view.sendDelaySelected() .autoDisposable(view.scope()) - .subscribe { duration -> prefs.sendDelay.set(duration) } + .subscribe() + + view.signatureSet() + .doOnNext(prefs.signature::set) + .autoDisposable(view.scope()) + .subscribe() view.mmsSizeSelected() .autoDisposable(view.scope()) - .subscribe { prefs.mmsSize.set(it) } + .subscribe(prefs.mmsSize::set) } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt index 18285627ba4d11afdfe4f10618f7a183d96c06cb..d2a35995782029a6beb807924aaff0b495ad1411 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsState.kt @@ -33,6 +33,7 @@ data class SettingsState( val sendDelaySummary: String = "", val sendDelayId: Int = 0, val deliveryEnabled: Boolean = false, + val signature: String = "", val textSizeSummary: String = "", val textSizeId: Int = Preferences.TEXT_SIZE_NORMAL, val systemFontEnabled: Boolean = false, diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt index 9df4163c61156cff82560e3811d93cd741a35fc4..81b73019dc6dd398622e14955cf8cf5bc92be445 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/SettingsView.kt @@ -31,13 +31,15 @@ interface SettingsView : QkViewContract { fun nightEndSelected(): Observable> fun textSizeSelected(): Observable fun sendDelaySelected(): Observable + fun signatureSet(): Observable fun mmsSizeSelected(): Observable - + fun showQksmsPlusSnackbar() fun showNightModeDialog() fun showStartTimePicker(hour: Int, minute: Int) fun showEndTimePicker(hour: Int, minute: Int) fun showTextSizePicker() fun showDelayDurationDialog() + fun showSignatureDialog(signature: String) fun showMmsSizePicker() fun showSwipeActions() fun showThemePicker() diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt index b20cb4f5ad8d63ae3ac42f7c867a7068c11044e9..8b000e356ebf58532c79ef6430878ff65e589a0d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/about/AboutPresenter.kt @@ -21,7 +21,8 @@ package com.moez.QKSMS.feature.settings.about import com.moez.QKSMS.R import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import javax.inject.Inject class AboutPresenter @Inject constructor( diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt index a93174cbbfde61f5f1db34c94e0506c6d02a74c6..8121ad443de30d1e99ff4e16d4e0c5395af8858d 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsController.kt @@ -29,7 +29,8 @@ import com.moez.QKSMS.common.util.extensions.animateLayoutChanges import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.Subject diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt index dbc01d2e00d0dbfb810c3c530bd40ffc25915e44..f895b5ae5c607694d824690783e06f2780ce378a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/settings/swipe/SwipeActionsPresenter.kt @@ -23,7 +23,8 @@ import androidx.annotation.DrawableRes import com.moez.QKSMS.R import com.moez.QKSMS.common.base.QkPresenter import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import javax.inject.Inject @@ -73,6 +74,7 @@ class SwipeActionsPresenter @Inject constructor( Preferences.SWIPE_ACTION_DELETE -> R.drawable.ic_delete_white_24dp Preferences.SWIPE_ACTION_CALL -> R.drawable.ic_call_white_24dp Preferences.SWIPE_ACTION_READ -> R.drawable.ic_check_white_24dp + Preferences.SWIPE_ACTION_UNREAD -> R.drawable.ic_markunread_black_24dp else -> 0 } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt index bf1b61a65887a759aabc01fa9b52471ac6cc757e..1ef80a3b5b40f30e8de3ef5e86cd12dd4a7d9b3c 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerController.kt @@ -88,6 +88,14 @@ class ThemePickerController(val threadId: Long = 0L) : QkController = themeAdapter.colorSelected override fun hsvThemeSelected(): Observable = picker.selectedColor diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt index d57b3551f640c796f9c44d06ee4acf77a02cd9aa..20c7e358b878d0b6e971e3d58bec0977eca6f1ab 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerPresenter.kt @@ -24,7 +24,8 @@ import com.moez.QKSMS.common.base.QkPresenter import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.manager.WidgetManager import com.moez.QKSMS.util.Preferences -import com.uber.autodispose.kotlin.autoDisposable +import com.uber.autodispose.android.lifecycle.scope +import com.uber.autodispose.autoDisposable import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.withLatestFrom import javax.inject.Inject diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt index 5480740a7e1a4994fe852cb7f6f2e4fdf9c94229..5a27ca896e99baab803b228793976f228b7f1a7a 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/themepicker/ThemePickerView.kt @@ -28,6 +28,6 @@ interface ThemePickerView : QkViewContract { fun clearHsvThemeClicks(): Observable<*> fun applyHsvThemeClicks(): Observable<*> fun viewQksmsPlusClicks(): Observable<*> - + fun showQksmsPlusSnackbar() fun setCurrentTheme(color: Int) } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt index e3af1e40ec64860d436eb201e6025727f474d59d..c2f6124b55331bc98377fe63cb1302fb956559c3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/widget/WidgetAdapter.kt @@ -22,14 +22,11 @@ import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent -import android.graphics.Typeface -import android.telephony.PhoneNumberUtils -import android.text.Spannable -import android.text.SpannableString -import android.text.style.StyleSpan +import android.text.SpannableStringBuilder import android.view.View import android.widget.RemoteViews import android.widget.RemoteViewsService +import androidx.core.text.bold import com.moez.QKSMS.R import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.DateFormatter @@ -133,7 +130,7 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { contact?.numbers?.firstOrNull()?.address?.let { address -> val futureGet = GlideApp.with(context) .asBitmap() - .load(PhoneNumberUtils.stripSeparators(address)) + .load("tel:$address") .submit(48.dpToPx(context), 48.dpToPx(context)) try { @@ -171,14 +168,10 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory { return view } - private fun boldText(text: String, shouldBold: Boolean): CharSequence { - return if (shouldBold) { - SpannableString(text).apply { - setSpan(StyleSpan(Typeface.BOLD), 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE) - } - } else { - text - } + private fun boldText(text: String, shouldBold: Boolean): CharSequence = when { + shouldBold -> SpannableStringBuilder() + .bold { append(text) } + else -> text } override fun getLoadingView(): RemoteViews { diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt index e91d5ed7d5d043ee77dccfa89059defc253dcf53..755152147d2f297ebab09128c28ecf2b020af48f 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppComponent.kt @@ -27,7 +27,12 @@ import com.moez.QKSMS.common.widget.PreferenceView import com.moez.QKSMS.common.widget.QkEditText import com.moez.QKSMS.common.widget.QkSwitch import com.moez.QKSMS.common.widget.QkTextView +import com.moez.QKSMS.common.widget.RadioPreferenceView import com.moez.QKSMS.feature.backup.BackupController +import com.moez.QKSMS.feature.blocking.BlockingController +import com.moez.QKSMS.feature.blocking.manager.BlockingManagerController +import com.moez.QKSMS.feature.blocking.messages.BlockedMessagesController +import com.moez.QKSMS.feature.blocking.numbers.BlockedNumbersController import com.moez.QKSMS.feature.compose.DetailedChipView import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoComponent import com.moez.QKSMS.feature.settings.SettingsController @@ -59,6 +64,10 @@ interface AppComponent { fun inject(controller: AboutController) fun inject(controller: BackupController) + fun inject(controller: BlockedMessagesController) + fun inject(controller: BlockedNumbersController) + fun inject(controller: BlockingController) + fun inject(controller: BlockingManagerController) fun inject(controller: SettingsController) fun inject(controller: SwipeActionsController) @@ -77,6 +86,7 @@ interface AppComponent { fun inject(view: DetailedChipView) fun inject(view: PagerTitleView) fun inject(view: PreferenceView) + fun inject(view: RadioPreferenceView) fun inject(view: QkEditText) fun inject(view: QkSwitch) fun inject(view: QkTextView) diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index c59472da05018b805ce04f5f4a4a463fce2eedb5..9a7386b062270725133c40c199a7b828da8c1925 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -24,6 +24,8 @@ import android.content.Context import android.preference.PreferenceManager import androidx.lifecycle.ViewModelProvider import com.f2prateek.rx.preferences2.RxSharedPreferences +import com.moez.QKSMS.blocking.BlockingClient +import com.moez.QKSMS.blocking.BlockingManager import com.moez.QKSMS.common.ViewModelFactory import com.moez.QKSMS.common.util.NotificationManagerImpl import com.moez.QKSMS.common.util.ShortcutManagerImpl @@ -37,8 +39,8 @@ import com.moez.QKSMS.manager.AlarmManager import com.moez.QKSMS.manager.AlarmManagerImpl import com.moez.QKSMS.manager.AnalyticsManager import com.moez.QKSMS.manager.AnalyticsManagerImpl -import com.moez.QKSMS.manager.ExternalBlockingManager -import com.moez.QKSMS.manager.ExternalBlockingManagerImpl +import com.moez.QKSMS.manager.ChangelogManager +import com.moez.QKSMS.manager.ChangelogManagerImpl import com.moez.QKSMS.manager.KeyManager import com.moez.QKSMS.manager.KeyManagerImpl import com.moez.QKSMS.manager.NotificationManager @@ -59,6 +61,8 @@ import com.moez.QKSMS.mapper.CursorToRecipient import com.moez.QKSMS.mapper.CursorToRecipientImpl import com.moez.QKSMS.repository.BackupRepository import com.moez.QKSMS.repository.BackupRepositoryImpl +import com.moez.QKSMS.repository.BlockingRepository +import com.moez.QKSMS.repository.BlockingRepositoryImpl import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ContactRepositoryImpl import com.moez.QKSMS.repository.ConversationRepository @@ -72,6 +76,7 @@ import com.moez.QKSMS.repository.ScheduledMessageRepositoryImpl import com.moez.QKSMS.repository.SyncRepository import com.moez.QKSMS.repository.SyncRepositoryImpl import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -98,7 +103,9 @@ class AppModule(private var application: Application) { @Provides @Singleton fun provideMoshi(): Moshi { - return Moshi.Builder().build() + return Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() } @Provides @@ -121,7 +128,10 @@ class AppModule(private var application: Application) { fun provideAnalyticsManager(manager: AnalyticsManagerImpl): AnalyticsManager = manager @Provides - fun externalBlockingManager(manager: ExternalBlockingManagerImpl): ExternalBlockingManager = manager + fun blockingClient(manager: BlockingManager): BlockingClient = manager + + @Provides + fun changelogManager(manager: ChangelogManagerImpl): ChangelogManager = manager @Provides fun provideKeyManager(manager: KeyManagerImpl): KeyManager = manager @@ -138,7 +148,6 @@ class AppModule(private var application: Application) { @Provides fun provideWidgetManager(manager: WidgetManagerImpl): WidgetManager = manager - // Mapper @Provides @@ -156,12 +165,14 @@ class AppModule(private var application: Application) { @Provides fun provideCursorToRecipient(mapper: CursorToRecipientImpl): CursorToRecipient = mapper - // Repository @Provides fun provideBackupRepository(repository: BackupRepositoryImpl): BackupRepository = repository + @Provides + fun provideBlockingRepository(repository: BlockingRepositoryImpl): BlockingRepository = repository + @Provides fun provideContactRepository(repository: ContactRepositoryImpl): ContactRepository = repository diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt index c415b02e5c679c05494e790ea4953141bc3768b6..eb38d2f3a49067e537b0bf82eb2873ee88db0a49 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/android/ActivityBuilderModule.kt @@ -19,8 +19,7 @@ package com.moez.QKSMS.injection.android import com.moez.QKSMS.feature.backup.BackupActivity -import com.moez.QKSMS.feature.blocked.BlockedActivity -import com.moez.QKSMS.feature.blocked.BlockedActivityModule +import com.moez.QKSMS.feature.blocking.BlockingActivity import com.moez.QKSMS.feature.compose.ComposeActivity import com.moez.QKSMS.feature.compose.ComposeActivityModule import com.moez.QKSMS.feature.conversationinfo.ConversationInfoActivity @@ -30,6 +29,8 @@ import com.moez.QKSMS.feature.main.MainActivity import com.moez.QKSMS.feature.main.MainActivityModule import com.moez.QKSMS.feature.notificationprefs.NotificationPrefsActivity import com.moez.QKSMS.feature.notificationprefs.NotificationPrefsActivityModule +import com.moez.QKSMS.feature.plus.PlusActivity +import com.moez.QKSMS.feature.plus.PlusActivityModule import com.moez.QKSMS.feature.qkreply.QkReplyActivity import com.moez.QKSMS.feature.qkreply.QkReplyActivityModule import com.moez.QKSMS.feature.scheduled.ScheduledActivity @@ -79,7 +80,11 @@ abstract class ActivityBuilderModule { abstract fun bindSettingsActivity(): SettingsActivity @ActivityScope - @ContributesAndroidInjector(modules = [BlockedActivityModule::class]) - abstract fun bindBlockedActivity(): BlockedActivity + @ContributesAndroidInjector(modules = []) + abstract fun bindBlockingActivity(): BlockingActivity + + @ActivityScope + @ContributesAndroidInjector(modules = [PlusActivityModule::class]) + abstract fun bindPlusActivity(): PlusActivity -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt index 22012af1de9f38aecc6e310b05c6e39b6f4be88e..ddc701bdb96fa495e9cdc1bc08f82640abf66039 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/android/ServiceBuilderModule.kt @@ -21,7 +21,7 @@ package com.moez.QKSMS.injection.android import com.moez.QKSMS.feature.backup.RestoreBackupService import com.moez.QKSMS.injection.scope.ActivityScope import com.moez.QKSMS.service.HeadlessSmsSendService -import com.moez.QKSMS.service.SendSmsService +import com.moez.QKSMS.receiver.SendSmsReceiver import dagger.Module import dagger.android.ContributesAndroidInjector @@ -38,6 +38,6 @@ abstract class ServiceBuilderModule { @ActivityScope @ContributesAndroidInjector() - abstract fun bindSendSmsReceiver(): SendSmsService + abstract fun bindSendSmsReceiver(): SendSmsReceiver } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/scope/ActivityScope.kt b/presentation/src/main/java/com/moez/QKSMS/injection/scope/ActivityScope.kt index 762f16f752489d1aa1cc577caf15e19f6ed9b6d7..007e63f458eac8262ea4e27ce68c92139e75cac3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/scope/ActivityScope.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/scope/ActivityScope.kt @@ -21,4 +21,4 @@ package com.moez.QKSMS.injection.scope import javax.inject.Scope @Scope -annotation class ActivityScope \ No newline at end of file +annotation class ActivityScope diff --git a/presentation/src/main/res/color-night/text_primary.xml b/presentation/src/main/res/color-night/text_primary.xml new file mode 100644 index 0000000000000000000000000000000000000000..c97dc9b257d3bb71064225affc21a4c99949affd --- /dev/null +++ b/presentation/src/main/res/color-night/text_primary.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/presentation/src/main/res/color-night/text_secondary.xml b/presentation/src/main/res/color-night/text_secondary.xml new file mode 100644 index 0000000000000000000000000000000000000000..653e10ea5437af6ee616bd1b7de4ce9cd378082d --- /dev/null +++ b/presentation/src/main/res/color-night/text_secondary.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/presentation/src/main/res/color-night/text_tertiary.xml b/presentation/src/main/res/color-night/text_tertiary.xml new file mode 100644 index 0000000000000000000000000000000000000000..195ffca830fc73c6788f69b690263158324996db --- /dev/null +++ b/presentation/src/main/res/color-night/text_tertiary.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/presentation/src/main/res/color/text_primary.xml b/presentation/src/main/res/color/text_primary.xml new file mode 100644 index 0000000000000000000000000000000000000000..2e8e52498f69a8f0d40c4cc0cdd18c7fd4224764 --- /dev/null +++ b/presentation/src/main/res/color/text_primary.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/presentation/src/main/res/color/text_secondary.xml b/presentation/src/main/res/color/text_secondary.xml new file mode 100644 index 0000000000000000000000000000000000000000..8e32a5ff8190eea311234e05be53279067c1a4af --- /dev/null +++ b/presentation/src/main/res/color/text_secondary.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/presentation/src/main/res/color/text_tertiary.xml b/presentation/src/main/res/color/text_tertiary.xml new file mode 100644 index 0000000000000000000000000000000000000000..89d232e2da537a43fcd251f7d25bc7c12386d58c --- /dev/null +++ b/presentation/src/main/res/color/text_tertiary.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/presentation/src/main/res/drawable/ab_shadow.xml b/presentation/src/main/res/drawable/ab_shadow.xml index cdea74a093909f701d305a831a447398ed18c94c..e4100d9398db26da093946b4c72f52ae819f255e 100644 --- a/presentation/src/main/res/drawable/ab_shadow.xml +++ b/presentation/src/main/res/drawable/ab_shadow.xml @@ -1,6 +1,6 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_add_black_24dp.xml b/presentation/src/main/res/drawable/ic_add_black_24dp.xml index 788ad4b32a9822c384981958207f7a13bc53cee1..8b7299f418e57c5259b8d9be9b3d1b0c9a4a4858 100644 --- a/presentation/src/main/res/drawable/ic_add_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_add_black_24dp.xml @@ -1,5 +1,5 @@ - - + + diff --git a/presentation/src/main/res/drawable/ic_archive_black_24dp.xml b/presentation/src/main/res/drawable/ic_archive_black_24dp.xml index c5e78c4549b374924803a13ac067e449fe29147f..53346afd07e56a62b11adf967f0ff717bea3cb71 100644 --- a/presentation/src/main/res/drawable/ic_archive_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_archive_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z" /> diff --git a/presentation/src/main/res/drawable/ic_attachment_black_24dp.xml b/presentation/src/main/res/drawable/ic_attachment_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..d9940a26b354542bc8ca8c364da019763eac4e95 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_attachment_black_24dp.xml @@ -0,0 +1,27 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_av_timer_black_24dp.xml b/presentation/src/main/res/drawable/ic_av_timer_black_24dp.xml index 59f37793580ef7bbd56bc85c72744990e611ade6..e1787230e2eb1fdc67c10e684fe902e443160db1 100644 --- a/presentation/src/main/res/drawable/ic_av_timer_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_av_timer_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M11,17c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1zM11,3v4h2L13,5.08c3.39,0.49 6,3.39 6,6.92 0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-1.68 0.59,-3.22 1.58,-4.42L12,13l1.41,-1.41 -6.8,-6.8v0.02C4.42,6.45 3,9.05 3,12c0,4.97 4.02,9 9,9 4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9h-1zM18,12c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1 0.45,1 1,1 1,-0.45 1,-1zM6,12c0,0.55 0.45,1 1,1s1,-0.45 1,-1 -0.45,-1 -1,-1 -1,0.45 -1,1z" /> diff --git a/presentation/src/main/res/drawable/ic_backup_black_24dp.xml b/presentation/src/main/res/drawable/ic_backup_black_24dp.xml index 2ad4a6dd5f39dc9d304fcd37d2e47957a7db0666..6ed84e44860a0001440df2b22b7bdd4d9574bdd7 100644 --- a/presentation/src/main/res/drawable/ic_backup_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_backup_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z" /> diff --git a/presentation/src/main/res/drawable/ic_block_black_24dp.xml b/presentation/src/main/res/drawable/ic_block_black_24dp.xml index ce43c792b97dfbb5b2eefe8267e5b02caa558eb1..c3f491ef781a10295e022fe221822feecf6ed345 100644 --- a/presentation/src/main/res/drawable/ic_block_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_block_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z" /> diff --git a/presentation/src/main/res/drawable/ic_call_white_24dp.xml b/presentation/src/main/res/drawable/ic_call_white_24dp.xml index aa7bbb8ee6f417d8c985db790c64b14335a20671..d37e9f4e571ec5736511eeb82642e660a9a8e7e4 100644 --- a/presentation/src/main/res/drawable/ic_call_white_24dp.xml +++ b/presentation/src/main/res/drawable/ic_call_white_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z" /> diff --git a/presentation/src/main/res/drawable/ic_camera_alt_black_24dp.xml b/presentation/src/main/res/drawable/ic_camera_alt_black_24dp.xml index 5809569a8129316143c1876c9456c83fb5d794fe..4f84c3c74b0b9223a538605124a4817f221fe9f0 100644 --- a/presentation/src/main/res/drawable/ic_camera_alt_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_camera_alt_black_24dp.xml @@ -1,5 +1,5 @@ - - - + + + diff --git a/presentation/src/main/res/drawable/ic_cancel_black_24dp.xml b/presentation/src/main/res/drawable/ic_cancel_black_24dp.xml index ef873d4bfcbb65cd7a3e463fa5a076d32be1deec..b05db58ddd3f667afb8f52b43941943a166ba913 100755 --- a/presentation/src/main/res/drawable/ic_cancel_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_cancel_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" /> diff --git a/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml b/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml index d01ba1042cca451c9124f892d913663d67401e8f..2891855b5d5b82b7a56079ec72af4a0458c83c0a 100644 --- a/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_chevron_right_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" /> diff --git a/presentation/src/main/res/drawable/ic_clear_all_black_24dp.xml b/presentation/src/main/res/drawable/ic_clear_all_black_24dp.xml index 341c6cb8a81e76254a9fa720929b41785ff72a8f..a9c72ce6f09290cfe31f13f8d089ff326e45800a 100644 --- a/presentation/src/main/res/drawable/ic_clear_all_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_clear_all_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z" /> diff --git a/presentation/src/main/res/drawable/ic_clear_black_24dp.xml b/presentation/src/main/res/drawable/ic_clear_black_24dp.xml index 68809bc27126552bfdb7b217009f6852f8e76933..2ec0059a6d030d2752a693691044e8072d6f86b1 100644 --- a/presentation/src/main/res/drawable/ic_clear_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_clear_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" /> diff --git a/presentation/src/main/res/drawable/ic_close_black_24dp.xml b/presentation/src/main/res/drawable/ic_close_black_24dp.xml index 68809bc27126552bfdb7b217009f6852f8e76933..2ec0059a6d030d2752a693691044e8072d6f86b1 100644 --- a/presentation/src/main/res/drawable/ic_close_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_close_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" /> diff --git a/presentation/src/main/res/drawable/ic_color_lens_black_24dp.xml b/presentation/src/main/res/drawable/ic_color_lens_black_24dp.xml index 88343a0c11c56ef997aecf7d3027395dec28fec2..2979b3a06266b6e988df19db95666f4b077fbaa4 100644 --- a/presentation/src/main/res/drawable/ic_color_lens_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_color_lens_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" /> diff --git a/presentation/src/main/res/drawable/ic_content_copy_black_24dp.xml b/presentation/src/main/res/drawable/ic_content_copy_black_24dp.xml index 5cb4570e724ec52a47f2a34dcaff27305f2b2b1f..c4fa4e0c21b77c5a0fc963b92ae952a959249375 100644 --- a/presentation/src/main/res/drawable/ic_content_copy_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_content_copy_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" /> diff --git a/presentation/src/main/res/drawable/ic_delete_white_24dp.xml b/presentation/src/main/res/drawable/ic_delete_white_24dp.xml index 1ffbab9fd9b057eed649997aa912ab6278921ead..ca0a7d5940933cbd77669b5fed61b247aae067f9 100644 --- a/presentation/src/main/res/drawable/ic_delete_white_24dp.xml +++ b/presentation/src/main/res/drawable/ic_delete_white_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" /> diff --git a/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml b/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml index 7751006e4c9756a1b8204c4706ba7bbbda358329..c945e2f24e5dd97b03cfc4a7c4392cdab310a6c4 100644 --- a/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_drafts_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21.99,8c0,-0.72 -0.37,-1.35 -0.94,-1.7L12,1 2.95,6.3C2.38,6.65 2,7.28 2,8v10c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2l-0.01,-10zM12,13L3.74,7.84 12,3l8.26,4.84L12,13z" /> diff --git a/presentation/src/main/res/drawable/ic_event_black_24dp.xml b/presentation/src/main/res/drawable/ic_event_black_24dp.xml index 59a77a5a137d91f0c7b1aa9f0b1b26d4f3a93072..b39e1754200deeb51b95f4fc19ff221b06d6f714 100644 --- a/presentation/src/main/res/drawable/ic_event_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_event_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z" /> diff --git a/presentation/src/main/res/drawable/ic_favorite_black_24dp.xml b/presentation/src/main/res/drawable/ic_favorite_black_24dp.xml index d26c91de6ab425a0a1f33a4fe32becb20bab0c0c..f66dbb9f9c09f628903ffd8f7c44a2a51f362294 100644 --- a/presentation/src/main/res/drawable/ic_favorite_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_favorite_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" /> diff --git a/presentation/src/main/res/drawable/ic_file_download_black_24dp.xml b/presentation/src/main/res/drawable/ic_file_download_black_24dp.xml index 7e5961d34efe1cbd20ca00c9c2c580526e218a8c..edc7894e01e81e2c977a916a10062ab6f6d1b40e 100644 --- a/presentation/src/main/res/drawable/ic_file_download_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_file_download_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" /> diff --git a/presentation/src/main/res/drawable/ic_file_upload_black_24dp.xml b/presentation/src/main/res/drawable/ic_file_upload_black_24dp.xml index cd7e004b3cfa36c8ec3238b37b2c84257872986d..1e7ce2fa3c1fbe1cbfd30b11c5572f2bfd66897b 100644 --- a/presentation/src/main/res/drawable/ic_file_upload_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_file_upload_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" /> diff --git a/presentation/src/main/res/drawable/ic_format_size_black_24dp.xml b/presentation/src/main/res/drawable/ic_format_size_black_24dp.xml index 0d3f3ffabfd61f041fe421bdf043526e6adfb808..82c4792aca8455745aa2c68a12ca357c94c99d12 100644 --- a/presentation/src/main/res/drawable/ic_format_size_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_format_size_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M9,4v3h5v12h3L17,7h5L22,4L9,4zM3,12h3v7h3v-7h3L12,9L3,9v3z" /> diff --git a/presentation/src/main/res/drawable/ic_help_black_24dp.xml b/presentation/src/main/res/drawable/ic_help_black_24dp.xml index 37600c05f41d20373c7a7346b9a70cbeabe4a2c9..e01b214f37d2fbd479250b222e5074172ccb036a 100644 --- a/presentation/src/main/res/drawable/ic_help_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_help_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z" /> diff --git a/presentation/src/main/res/drawable/ic_history_black_24dp.xml b/presentation/src/main/res/drawable/ic_history_black_24dp.xml index b69690ca1945592a4d500d454e79d24d0b576e18..82a97894455a348193847e2500139029bf5929df 100644 --- a/presentation/src/main/res/drawable/ic_history_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_history_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" /> diff --git a/presentation/src/main/res/drawable/ic_import_export_black_24dp.xml b/presentation/src/main/res/drawable/ic_import_export_black_24dp.xml index 881a526b8286390de9a9ad23c1e2857dd5081fe4..820ba7bc3efc696a6892c85ccdaf0761ecd9ee43 100644 --- a/presentation/src/main/res/drawable/ic_import_export_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_import_export_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" /> diff --git a/presentation/src/main/res/drawable/ic_inbox_black_24dp.xml b/presentation/src/main/res/drawable/ic_inbox_black_24dp.xml index 9b18469a1914b45d52dc1e85871f9d6e2b6d6973..f0272cdb4683acfe6348ba5a4e29a8da25ae988d 100644 --- a/presentation/src/main/res/drawable/ic_inbox_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_inbox_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.89 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.11 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10z" /> diff --git a/presentation/src/main/res/drawable/ic_info_black_24dp.xml b/presentation/src/main/res/drawable/ic_info_black_24dp.xml index 14cd2474df79fc927b6d68a8ce767cd698c573a1..36ed4eaf40eeab94fa8d839a4472472b167787a5 100644 --- a/presentation/src/main/res/drawable/ic_info_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_info_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" /> diff --git a/presentation/src/main/res/drawable/ic_insert_photo_black_24dp.xml b/presentation/src/main/res/drawable/ic_insert_photo_black_24dp.xml index 001cb2428f488ceb05b81f7a26a11ab1cfe3db8e..c2a21b5ddafe5af2dc9d89d1c973461681106ab9 100644 --- a/presentation/src/main/res/drawable/ic_insert_photo_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_insert_photo_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" /> diff --git a/presentation/src/main/res/drawable/ic_invert_colors_black_24dp.xml b/presentation/src/main/res/drawable/ic_invert_colors_black_24dp.xml index b64ef5778d81809dbbe000143504f4010943f5b6..c3bc64e3a9c1b52fdd213edf8e3339953b89fcce 100644 --- a/presentation/src/main/res/drawable/ic_invert_colors_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_invert_colors_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M17.66,7.93L12,2.27 6.34,7.93c-3.12,3.12 -3.12,8.19 0,11.31C7.9,20.8 9.95,21.58 12,21.58c2.05,0 4.1,-0.78 5.66,-2.34 3.12,-3.12 3.12,-8.19 0,-11.31zM12,19.59c-1.6,0 -3.11,-0.62 -4.24,-1.76C6.62,16.69 6,15.19 6,13.59s0.62,-3.11 1.76,-4.24L12,5.1v14.49z" /> diff --git a/presentation/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml b/presentation/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml index 0ac71ea8332838b57e1dec11b3ed9190445aa29e..7a6065d77a614377e61a72e33f3d28ae11975aa5 100644 --- a/presentation/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z" /> diff --git a/presentation/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml b/presentation/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml index 6103456254106c15fda70398dd0c34c856f82316..7a625c27fbd0a20593defef78870f19aed676a0a 100644 --- a/presentation/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_keyboard_arrow_up_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" /> diff --git a/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml b/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml index dfccf44ab4dec510a51817d2133e63af7940ff88..783b4ee317a1f66be8badf9b4457f9ed553ac63d 100644 --- a/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_markunread_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z" /> diff --git a/presentation/src/main/res/drawable/ic_more_horiz_black_24dp.xml b/presentation/src/main/res/drawable/ic_more_horiz_black_24dp.xml index 090f162f16a7ff9d3e07005d0b7b4848f7b1e293..797ea991d044c39cc2079ad3993654258c5929ac 100644 --- a/presentation/src/main/res/drawable/ic_more_horiz_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_more_horiz_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" /> diff --git a/presentation/src/main/res/drawable/ic_more_vert_black_24dp.xml b/presentation/src/main/res/drawable/ic_more_vert_black_24dp.xml index f0b30a85447e375f0d78f823ed042350ac66a791..f738148ffd5f0f052c8a74c75c18f14195a58ca8 100644 --- a/presentation/src/main/res/drawable/ic_more_vert_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_more_vert_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" /> diff --git a/presentation/src/main/res/drawable/ic_notifications_black_24dp.xml b/presentation/src/main/res/drawable/ic_notifications_black_24dp.xml index 123ac29f026f4be66c9f61c46437de0a12daaa77..fcc373d068aae5f7a20bace9ab770565af3d35dd 100644 --- a/presentation/src/main/res/drawable/ic_notifications_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" /> diff --git a/presentation/src/main/res/drawable/ic_palette_black_24dp.xml b/presentation/src/main/res/drawable/ic_palette_black_24dp.xml index 88343a0c11c56ef997aecf7d3027395dec28fec2..2979b3a06266b6e988df19db95666f4b077fbaa4 100644 --- a/presentation/src/main/res/drawable/ic_palette_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_palette_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" /> diff --git a/presentation/src/main/res/drawable/ic_people_black_24dp.xml b/presentation/src/main/res/drawable/ic_people_black_24dp.xml index 0f4e3b15a264d7c8ca3230f5833d808690d73926..91306539f7c2ad5ab82feec33a65a3924fdf0df7 100644 --- a/presentation/src/main/res/drawable/ic_people_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_people_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z" /> diff --git a/presentation/src/main/res/drawable/ic_person_add_black_24dp.xml b/presentation/src/main/res/drawable/ic_person_add_black_24dp.xml index 3aaf1bfde41904def9111032a789159e37e77881..78aea774599d9902a79779ccd5a9ae48720d4cf5 100644 --- a/presentation/src/main/res/drawable/ic_person_add_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_person_add_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" /> diff --git a/presentation/src/main/res/drawable/ic_person_black_24dp.xml b/presentation/src/main/res/drawable/ic_person_black_24dp.xml index 9d8f5fbbc566d3ec68d10f7880dff1f9f66c23fe..87586bebca2d57afceb664c4f91928427b16a539 100644 --- a/presentation/src/main/res/drawable/ic_person_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_person_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" /> diff --git a/presentation/src/main/res/drawable/ic_photo_size_select_actual_black_24dp.xml b/presentation/src/main/res/drawable/ic_photo_size_select_actual_black_24dp.xml index 9ab525056de5c2b9a988a3370e8b5775260f9440..d7c900379d5db14897c7c560675af3a596c09248 100644 --- a/presentation/src/main/res/drawable/ic_photo_size_select_actual_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_photo_size_select_actual_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M21,3H3C2,3 1,4 1,5v14c0,1.1 0.9,2 2,2h18c1,0 2,-1 2,-2V5c0,-1 -1,-2 -2,-2zM5,17l3.5,-4.5 2.5,3.01L14.5,11l4.5,6H5z" /> diff --git a/presentation/src/main/res/drawable/ic_pin_black_24dp.xml b/presentation/src/main/res/drawable/ic_pin_black_24dp.xml index 15a783ae30803cc2d3edae3d6a9d8cb8cd71ba74..3602d9d2a0e4280cc8dc58a6b256ace9e321f627 100644 --- a/presentation/src/main/res/drawable/ic_pin_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_pin_black_24dp.xml @@ -1,5 +1,5 @@ - - + + diff --git a/presentation/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml b/presentation/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml index 16a334e992036e8559ab9c725daa0fb6a93ee62e..d4692685ed149527669d869d5844b04796779e6b 100644 --- a/presentation/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_radio_button_checked_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" /> diff --git a/presentation/src/main/res/drawable/ic_radio_button_unchecked_black_24dp.xml b/presentation/src/main/res/drawable/ic_radio_button_unchecked_black_24dp.xml index bd4622d73f6973876a7ffcf504635413c86e26b7..4225a079ccbb4f4b0b699c273156f73b0527caa2 100644 --- a/presentation/src/main/res/drawable/ic_radio_button_unchecked_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_radio_button_unchecked_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" /> diff --git a/presentation/src/main/res/drawable/ic_reply_white_24dp.xml b/presentation/src/main/res/drawable/ic_reply_white_24dp.xml index c5527cfc73bf8dbe4be1e612ceedeee1613e5b55..649cc2b081fd636408d69da6709deb7ae103d240 100644 --- a/presentation/src/main/res/drawable/ic_reply_white_24dp.xml +++ b/presentation/src/main/res/drawable/ic_reply_white_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" /> diff --git a/presentation/src/main/res/drawable/ic_send_black_24dp.xml b/presentation/src/main/res/drawable/ic_send_black_24dp.xml index 2ac692601cb87cbc835e9f780b8de5910c1b3982..7cbdd66bfadece39fcb4a0610b845c8e3f37f5a6 100644 --- a/presentation/src/main/res/drawable/ic_send_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_send_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" /> diff --git a/presentation/src/main/res/drawable/ic_short_text_black_24dp.xml b/presentation/src/main/res/drawable/ic_short_text_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..cc092d48076f51b08440a0b637b0f87608b07235 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_short_text_black_24dp.xml @@ -0,0 +1,27 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_sim_card_black_24dp.xml b/presentation/src/main/res/drawable/ic_sim_card_black_24dp.xml index 20daeffd185acdbfc3a89946cefc4b7325f2840c..862ac6a965ab358d43ae9dd9a194e8b6cc1fcc36 100644 --- a/presentation/src/main/res/drawable/ic_sim_card_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_sim_card_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM14,8h" /> diff --git a/presentation/src/main/res/drawable/ic_smartphone_black_24dp.xml b/presentation/src/main/res/drawable/ic_smartphone_black_24dp.xml index ceb1e5ccd6720ca76d39bd104aa106a3d3ab522d..821a72449b7f597f00af1e5a06a93c0e1a613642 100644 --- a/presentation/src/main/res/drawable/ic_smartphone_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_smartphone_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z" /> diff --git a/presentation/src/main/res/drawable/ic_star_black_24dp.xml b/presentation/src/main/res/drawable/ic_star_black_24dp.xml index 32d8ee49b0da8912df783a66fa6c87c965ba756c..a1b975f41903eec8fec217f9190b910eb4a4e988 100644 --- a/presentation/src/main/res/drawable/ic_star_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_star_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" /> diff --git a/presentation/src/main/res/drawable/ic_sync_black_24dp.xml b/presentation/src/main/res/drawable/ic_sync_black_24dp.xml index f1cef21a2c4e6fb02e924307a3325c7d55379de5..b6347f01d92a00d4363f82c96c34f2e022b4400f 100644 --- a/presentation/src/main/res/drawable/ic_sync_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_sync_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" /> diff --git a/presentation/src/main/res/drawable/ic_title_black_24dp.xml b/presentation/src/main/res/drawable/ic_title_black_24dp.xml index dc7ec5d3dd651645a422f38336b563b01bc9232e..6db01c458d0b494e6ed93f038717432e365e6180 100644 --- a/presentation/src/main/res/drawable/ic_title_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_title_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M5,4v3h5.5v12h3V7H19V4z" /> diff --git a/presentation/src/main/res/drawable/ic_unfold_less_black_24dp.xml b/presentation/src/main/res/drawable/ic_unfold_less_black_24dp.xml index f65d57f36619760b12b03b7a57677933d8770c06..92ab6b508c8e5d0416a287122a2852ac8ee4a499 100644 --- a/presentation/src/main/res/drawable/ic_unfold_less_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_unfold_less_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z" /> diff --git a/presentation/src/main/res/drawable/ic_unfold_more_black_24dp.xml b/presentation/src/main/res/drawable/ic_unfold_more_black_24dp.xml index d540e6c6d44776bb40ad64b3e3c37cfad40e4976..8aa8c0cd3f3afb8c43799466fe93890c8f57f69c 100644 --- a/presentation/src/main/res/drawable/ic_unfold_more_black_24dp.xml +++ b/presentation/src/main/res/drawable/ic_unfold_more_black_24dp.xml @@ -1,5 +1,5 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z" /> diff --git a/presentation/src/main/res/drawable/message_emoji.xml b/presentation/src/main/res/drawable/message_emoji.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d41280a6117b4b90194f0f5376e148f61f85a26 --- /dev/null +++ b/presentation/src/main/res/drawable/message_emoji.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/presentation/src/main/res/drawable/message_in_first.xml b/presentation/src/main/res/drawable/message_in_first.xml index b3f83bf2511e724c7ee7cb78f784376f17a34c74..782bf98a701a58505b0b13547112a652e382c065 100644 --- a/presentation/src/main/res/drawable/message_in_first.xml +++ b/presentation/src/main/res/drawable/message_in_first.xml @@ -1,6 +1,6 @@ - + app:fontProviderAuthority="com.google.android.gms.fonts" + app:fontProviderCerts="@array/com_google_android_gms_fonts_certs" + app:fontProviderPackage="com.google.android.gms" + app:fontProviderQuery="Lato"> diff --git a/presentation/src/main/res/layout/about_controller.xml b/presentation/src/main/res/layout/about_controller.xml index e3bc14a41e6bc3bf713ad983136f44e096ec82ef..3d15a1b8c44430d80e028baa340682bef97046ac 100644 --- a/presentation/src/main/res/layout/about_controller.xml +++ b/presentation/src/main/res/layout/about_controller.xml @@ -1,5 +1,6 @@ - - + android:scaleType="centerCrop" + tools:src="@tools:sample/avatars" /> \ No newline at end of file diff --git a/presentation/src/main/res/layout/backup_controller.xml b/presentation/src/main/res/layout/backup_controller.xml index 70b23034f488b22a52548d1deb53f06b3b6cd4b2..8f787231a3bbb8587b9e9c35912c9151d2979556 100644 --- a/presentation/src/main/res/layout/backup_controller.xml +++ b/presentation/src/main/res/layout/backup_controller.xml @@ -1,6 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/presentation/src/main/res/layout/blocked_list_item.xml b/presentation/src/main/res/layout/blocked_list_item.xml index e5177cc1887f0ec48d2d580ed73bb83786824fa4..5c41be73ce0b6ca64cbd956f555301c9ac4d32bf 100644 --- a/presentation/src/main/res/layout/blocked_list_item.xml +++ b/presentation/src/main/res/layout/blocked_list_item.xml @@ -1,6 +1,6 @@ + + + + + + + diff --git a/presentation/src/main/res/layout/blocked_number_list_item.xml b/presentation/src/main/res/layout/blocked_number_list_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..93bcb89348a56b1bdda62d92f0b787287e693e71 --- /dev/null +++ b/presentation/src/main/res/layout/blocked_number_list_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/blocked_numbers_add_dialog.xml b/presentation/src/main/res/layout/blocked_numbers_add_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..42d22860b182d2e9229b107faf9c7f1f42b59b1e --- /dev/null +++ b/presentation/src/main/res/layout/blocked_numbers_add_dialog.xml @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/presentation/src/main/res/layout/blocked_numbers_controller.xml b/presentation/src/main/res/layout/blocked_numbers_controller.xml new file mode 100644 index 0000000000000000000000000000000000000000..9534dce174a2a7782a1d34e9e292f3933343c5fe --- /dev/null +++ b/presentation/src/main/res/layout/blocked_numbers_controller.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/blocking_controller.xml b/presentation/src/main/res/layout/blocking_controller.xml new file mode 100644 index 0000000000000000000000000000000000000000..5602fc9dfc9eb83f79a331891e973e7576fe4ff9 --- /dev/null +++ b/presentation/src/main/res/layout/blocking_controller.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/blocking_manager_controller.xml b/presentation/src/main/res/layout/blocking_manager_controller.xml new file mode 100644 index 0000000000000000000000000000000000000000..03575c7b3bfcf24c69ab54cf9174e4406fadda71 --- /dev/null +++ b/presentation/src/main/res/layout/blocking_manager_controller.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/blocking_manager_list_option.xml b/presentation/src/main/res/layout/blocking_manager_list_option.xml new file mode 100644 index 0000000000000000000000000000000000000000..06d4f8274e926877c47058c259145a714d44d316 --- /dev/null +++ b/presentation/src/main/res/layout/blocking_manager_list_option.xml @@ -0,0 +1,28 @@ + + + diff --git a/presentation/src/main/res/layout/changelog_dialog.xml b/presentation/src/main/res/layout/changelog_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..2d98cad2d912858b93e990dad7bf1595aa958338 --- /dev/null +++ b/presentation/src/main/res/layout/changelog_dialog.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/changelog_list_item.xml b/presentation/src/main/res/layout/changelog_list_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..9046a0cacd5db7c2ae701b28567e4f1358446c95 --- /dev/null +++ b/presentation/src/main/res/layout/changelog_list_item.xml @@ -0,0 +1,30 @@ + + + diff --git a/presentation/src/main/res/layout/chip_input_list_item.xml b/presentation/src/main/res/layout/chip_input_list_item.xml index eca0bf5d71e4295ca776a2377ce19922e57756d1..4d3969591a2e0cb5c8d7637204c1771ccc3ea6b1 100644 --- a/presentation/src/main/res/layout/chip_input_list_item.xml +++ b/presentation/src/main/res/layout/chip_input_list_item.xml @@ -1,6 +1,6 @@ - diff --git a/presentation/src/main/res/layout/compose_activity.xml b/presentation/src/main/res/layout/compose_activity.xml index cdbb3f75976d7b5d52a60282f3cd13d337e52beb..274da5dc8814b98ca680e40c693494ca9851c903 100644 --- a/presentation/src/main/res/layout/compose_activity.xml +++ b/presentation/src/main/res/layout/compose_activity.xml @@ -1,6 +1,6 @@ - @@ -314,10 +315,10 @@ android:layout_marginBottom="4dp" android:elevation="4dp" android:gravity="center" + android:textColor="?android:attr/textColorSecondary" app:layout_constraintBottom_toTopOf="@id/send" app:layout_constraintEnd_toEndOf="@id/send" app:layout_constraintStart_toStartOf="@id/send" - app:textColor="secondary" app:textSize="tertiary" tools:text="108 / 2" /> @@ -361,8 +362,8 @@ @@ -370,8 +371,8 @@ android:id="@+id/chips" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="4dp" android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" android:scrollbars="vertical" tools:visibility="gone" /> @@ -436,8 +437,8 @@ android:backgroundTint="?attr/bubbleColor" android:elevation="4dp" android:gravity="center_vertical" - android:paddingEnd="8dp" android:paddingStart="8dp" + android:paddingEnd="8dp" android:text="@string/compose_contact_cd" android:textColor="?android:attr/textColorPrimary" app:layout_constraintBottom_toBottomOf="@id/contact" @@ -470,8 +471,8 @@ android:backgroundTint="?attr/bubbleColor" android:elevation="4dp" android:gravity="center_vertical" - android:paddingEnd="8dp" android:paddingStart="8dp" + android:paddingEnd="8dp" android:text="@string/compose_schedule_cd" android:textColor="?android:attr/textColorPrimary" app:layout_constraintBottom_toBottomOf="@id/schedule" @@ -504,8 +505,8 @@ android:backgroundTint="?attr/bubbleColor" android:elevation="4dp" android:gravity="center_vertical" - android:paddingEnd="8dp" android:paddingStart="8dp" + android:paddingEnd="8dp" android:text="@string/compose_gallery_cd" android:textColor="?android:attr/textColorPrimary" app:layout_constraintBottom_toBottomOf="@id/gallery" @@ -538,8 +539,8 @@ android:backgroundTint="?attr/bubbleColor" android:elevation="4dp" android:gravity="center_vertical" - android:paddingEnd="8dp" android:paddingStart="8dp" + android:paddingEnd="8dp" android:text="@string/compose_camera_cd" android:textColor="?android:attr/textColorPrimary" app:layout_constraintBottom_toBottomOf="@id/camera" @@ -550,8 +551,8 @@ android:id="@+id/attach" android:layout_width="44dp" android:layout_height="44dp" - android:layout_marginBottom="8dp" android:layout_marginStart="8dp" + android:layout_marginBottom="8dp" android:background="@drawable/circle" android:contentDescription="@string/compose_attach_cd" android:elevation="4dp" diff --git a/presentation/src/main/res/layout/contact_chip.xml b/presentation/src/main/res/layout/contact_chip.xml index 9bb86f80672ac028377eaa07c9bafdf7f81fd31b..76805b05dc31d870f6f7722c8d89a3f02e878307 100755 --- a/presentation/src/main/res/layout/contact_chip.xml +++ b/presentation/src/main/res/layout/contact_chip.xml @@ -1,6 +1,6 @@ - @@ -45,8 +45,8 @@ android:layout_gravity="center_vertical" android:layout_marginLeft="8dp" android:layout_marginRight="12dp" + android:textColor="?android:attr/textColorPrimary" android:textStyle="bold" - app:textColor="primary" app:textSize="secondary" tools:text="Moez Bhatti" /> diff --git a/presentation/src/main/res/layout/contact_chip_detailed.xml b/presentation/src/main/res/layout/contact_chip_detailed.xml index 9ad7237384f6dcae94a68433c7cb88b388480cc0..786e160fd80b60454f27063aa56db78e3fcb637f 100755 --- a/presentation/src/main/res/layout/contact_chip_detailed.xml +++ b/presentation/src/main/res/layout/contact_chip_detailed.xml @@ -1,6 +1,6 @@ - - @@ -97,8 +96,8 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/field_dialog.xml b/presentation/src/main/res/layout/field_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..5170aeb3fed99aa2c52550b26b13bd7067de51a6 --- /dev/null +++ b/presentation/src/main/res/layout/field_dialog.xml @@ -0,0 +1,33 @@ + + + diff --git a/presentation/src/main/res/layout/gallery_activity.xml b/presentation/src/main/res/layout/gallery_activity.xml index ca8c3b899918f9488dfded3e1f657eab06311d14..fe2a4f1da100e340da3ab6b15c5ba76dcd08cf1b 100644 --- a/presentation/src/main/res/layout/gallery_activity.xml +++ b/presentation/src/main/res/layout/gallery_activity.xml @@ -1,6 +1,6 @@ - diff --git a/presentation/src/main/res/layout/gallery_image_page.xml b/presentation/src/main/res/layout/gallery_image_page.xml index 625f92a21ec7481d928e16c87e755196a23c5bd1..a6360c21f94a0b606d5b6f62a128ed5a23d7be2b 100644 --- a/presentation/src/main/res/layout/gallery_image_page.xml +++ b/presentation/src/main/res/layout/gallery_image_page.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/presentation/src/main/res/layout/gallery_invalid_page.xml b/presentation/src/main/res/layout/gallery_invalid_page.xml index 47f5461a57a833e274411b1dcca2bfdb70af6fbf..dd09cddee2959ffef1ae374192bee5e1cd421237 100644 --- a/presentation/src/main/res/layout/gallery_invalid_page.xml +++ b/presentation/src/main/res/layout/gallery_invalid_page.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/presentation/src/main/res/layout/group_avatar_view.xml b/presentation/src/main/res/layout/group_avatar_view.xml index ecf6d70d2f786199aeee942d43781e95a069b28b..c752aec43553069d376fe44d5be755e432ff3dff 100644 --- a/presentation/src/main/res/layout/group_avatar_view.xml +++ b/presentation/src/main/res/layout/group_avatar_view.xml @@ -1,6 +1,6 @@ - @@ -43,7 +43,8 @@ + android:layout_marginStart="16dp" + android:visibility="gone" /> diff --git a/presentation/src/main/res/layout/main_permission_hint.xml b/presentation/src/main/res/layout/main_permission_hint.xml index c9d92d7942fb0ac4edd9039280f9c9544e100c95..93213445647b2bc3bf7a326e360d81c4c528ee64 100644 --- a/presentation/src/main/res/layout/main_permission_hint.xml +++ b/presentation/src/main/res/layout/main_permission_hint.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/mms_preview_list_item.xml b/presentation/src/main/res/layout/mms_preview_list_item.xml index 4b1f0455c271d56aa3266110dad24644bbadcbc4..7b7628455402f60474dc9ee43012df59b47e9951 100644 --- a/presentation/src/main/res/layout/mms_preview_list_item.xml +++ b/presentation/src/main/res/layout/mms_preview_list_item.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/radio_preference_view.xml b/presentation/src/main/res/layout/radio_preference_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..8a1d2a67ee5b6304de5d53291b5813af0d82a8f2 --- /dev/null +++ b/presentation/src/main/res/layout/radio_preference_view.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + diff --git a/presentation/src/main/res/layout/scheduled_activity.xml b/presentation/src/main/res/layout/scheduled_activity.xml index 8c8f900bd26509af4b913aeda5bbfbc1778586a8..6cf71ceaa60c09d7cb404b60738a2798846e17a9 100644 --- a/presentation/src/main/res/layout/scheduled_activity.xml +++ b/presentation/src/main/res/layout/scheduled_activity.xml @@ -1,6 +1,6 @@ - @@ -30,8 +30,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:paddingBottom="8dp" - android:paddingTop="8dp"> + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + @@ -206,4 +213,4 @@ - \ No newline at end of file + diff --git a/presentation/src/main/res/layout/settings_switch_widget.xml b/presentation/src/main/res/layout/settings_switch_widget.xml index 67d21372b65be60bbf35648bac89868424d270a6..4958dcdb758ea70fe9d1b951e73d0e1d1ac0f82e 100644 --- a/presentation/src/main/res/layout/settings_switch_widget.xml +++ b/presentation/src/main/res/layout/settings_switch_widget.xml @@ -1,6 +1,6 @@ - + android:clickable="false" + android:duplicateParentState="true" /> diff --git a/presentation/src/main/res/layout/settings_theme_widget.xml b/presentation/src/main/res/layout/settings_theme_widget.xml index 9f2171770e9516116218c52007eb870bdb641aef..249d4c00c100519c479fb2fbd0aee9abd76aa919 100644 --- a/presentation/src/main/res/layout/settings_theme_widget.xml +++ b/presentation/src/main/res/layout/settings_theme_widget.xml @@ -1,6 +1,6 @@ - @@ -41,9 +40,9 @@ android:id="@+id/rightTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" android:layout_marginStart="24dp" android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" android:text="@string/settings_swipe_actions_right" android:textColor="?android:attr/textColorPrimary" app:layout_constraintEnd_toStartOf="@id/rightChange" @@ -80,10 +79,10 @@ android:id="@+id/rightBackground" android:layout_width="match_parent" android:layout_height="84dp" - android:layout_marginBottom="16dp" - android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" android:alpha="0.1" android:background="@drawable/rounded_rectangle_outline_4dp" android:backgroundTint="?android:attr/textColorPrimary" @@ -97,10 +96,10 @@ android:layout_width="96dp" android:layout_height="84dp" android:background="@drawable/rounded_rectangle_left_4dp" - android:paddingBottom="30dp" - android:paddingEnd="48dp" android:paddingStart="24dp" android:paddingTop="30dp" + android:paddingEnd="48dp" + android:paddingBottom="30dp" app:layout_constraintBottom_toBottomOf="@id/rightBackground" app:layout_constraintStart_toStartOf="@id/rightBackground" app:layout_constraintTop_toTopOf="@id/rightBackground" @@ -162,9 +161,9 @@ android:id="@+id/leftTitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" android:layout_marginStart="24dp" android:layout_marginTop="24dp" + android:layout_marginEnd="16dp" android:text="@string/settings_swipe_actions_left" android:textColor="?android:attr/textColorPrimary" app:layout_constraintEnd_toStartOf="@id/leftChange" @@ -201,10 +200,10 @@ android:id="@+id/leftBackground" android:layout_width="match_parent" android:layout_height="84dp" - android:layout_marginBottom="16dp" - android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" android:alpha="0.1" android:background="@drawable/rounded_rectangle_outline_4dp" android:backgroundTint="?android:attr/textColorPrimary" @@ -218,10 +217,10 @@ android:layout_width="96dp" android:layout_height="84dp" android:background="@drawable/rounded_rectangle_right_4dp" - android:paddingBottom="30dp" - android:paddingEnd="24dp" android:paddingStart="48dp" android:paddingTop="30dp" + android:paddingEnd="24dp" + android:paddingBottom="30dp" app:layout_constraintBottom_toBottomOf="@id/leftBackground" app:layout_constraintEnd_toEndOf="@id/leftBackground" app:layout_constraintTop_toTopOf="@id/leftBackground" diff --git a/presentation/src/main/res/layout/tab_view.xml b/presentation/src/main/res/layout/tab_view.xml index 66c546d4026d8404a049b4e5bc1ac98c5b1c02bb..a4a019c77fb5c887aa27cbd0cd5766f42d4a0f92 100644 --- a/presentation/src/main/res/layout/tab_view.xml +++ b/presentation/src/main/res/layout/tab_view.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/presentation/src/main/res/layout/theme_list_item.xml b/presentation/src/main/res/layout/theme_list_item.xml index 34614170235bb522fe79bd92a8ae33076f28eecf..da05ee17deb6a2a2fa2ccd5d6d5b0c04c28664a2 100644 --- a/presentation/src/main/res/layout/theme_list_item.xml +++ b/presentation/src/main/res/layout/theme_list_item.xml @@ -1,6 +1,6 @@ - diff --git a/presentation/src/main/res/layout/theme_picker_controller.xml b/presentation/src/main/res/layout/theme_picker_controller.xml index 7b3d7ec709761dec86f17c67395898500224a7ff..7ea2a1ee927a666f75046d735c94d7ad99b7e4bf 100644 --- a/presentation/src/main/res/layout/theme_picker_controller.xml +++ b/presentation/src/main/res/layout/theme_picker_controller.xml @@ -1,6 +1,6 @@ - + android:paddingStart="12dp" + android:paddingEnd="12dp" /> - + - @@ -41,14 +40,14 @@ android:id="@+id/title" android:layout_width="0dp" android:layout_height="0dp" + android:layout_alignTop="@id/toolbar" android:layout_alignBottom="@id/toolbar" android:layout_alignParentStart="true" - android:layout_alignTop="@id/toolbar" android:layout_toStartOf="@id/compose" android:background="@drawable/ripple" android:gravity="center_vertical" - android:paddingEnd="16dp" android:paddingStart="16dp" + android:paddingEnd="16dp" android:text="@string/app_name" android:textSize="18sp" tools:textColor="@color/textPrimary" /> @@ -57,9 +56,9 @@ android:id="@+id/compose" android:layout_width="48dp" android:layout_height="0dp" + android:layout_alignTop="@id/toolbar" android:layout_alignBottom="@id/toolbar" android:layout_alignParentEnd="true" - android:layout_alignTop="@id/toolbar" android:background="@drawable/ripple" android:padding="12dp" android:src="@drawable/ic_add_black_24dp" @@ -69,15 +68,15 @@ android:id="@+id/conversations" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_alignParentBottom="true" android:layout_below="@id/toolbar" + android:layout_alignParentBottom="true" android:cacheColorHint="#00000000" android:clipChildren="false" android:clipToPadding="false" android:divider="@null" android:dividerHeight="0dp" - android:paddingBottom="8dp" android:paddingTop="8dp" + android:paddingBottom="8dp" tools:listitem="@layout/widget_list_item" /> diff --git a/presentation/src/main/res/layout/widget_list_item.xml b/presentation/src/main/res/layout/widget_list_item.xml index 2d8693bda041c021cedaf4dbb8e789c97d68e434..2cdaf088a453150905a92a1fd1ef883817bc240f 100755 --- a/presentation/src/main/res/layout/widget_list_item.xml +++ b/presentation/src/main/res/layout/widget_list_item.xml @@ -1,7 +1,7 @@ - + android:paddingBottom="12dp"> @@ -99,8 +98,8 @@ android:id="@+id/snippet" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" android:layout_below="@id/name" + android:layout_alignParentStart="true" android:lines="1" android:singleLine="true" android:textSize="14sp" diff --git a/presentation/src/main/res/layout/widget_loading.xml b/presentation/src/main/res/layout/widget_loading.xml index ef562576421cfe68f8b84adc29bc563429545214..4ee493d8fcb346ab94ae6fa1558249eed3728950 100755 --- a/presentation/src/main/res/layout/widget_loading.xml +++ b/presentation/src/main/res/layout/widget_loading.xml @@ -1,6 +1,6 @@ - diff --git a/presentation/src/main/res/menu/blocked_messages.xml b/presentation/src/main/res/menu/blocked_messages.xml new file mode 100644 index 0000000000000000000000000000000000000000..70499d7c49f1cf5b1c36f2356d4301d8f889ee04 --- /dev/null +++ b/presentation/src/main/res/menu/blocked_messages.xml @@ -0,0 +1,37 @@ + + + + + + + + + diff --git a/presentation/src/main/res/menu/compose.xml b/presentation/src/main/res/menu/compose.xml index eeeffebb85e23e171adaf9f7f5b078d9f89f8fa6..85d05b57fc690e645e164c82b33e53a0acfe8155 100644 --- a/presentation/src/main/res/menu/compose.xml +++ b/presentation/src/main/res/menu/compose.xml @@ -1,5 +1,6 @@ - الإرسال في 23 ديسمبر جدولة رسالة الرسالة المجدولة أرسل الآن - حذف + Copy text + Delete المظهر عام @@ -200,10 +208,6 @@ نافذة منبثقة للرسائل الجديدة إلمس للاستبعاد إلمس خارج المنبثقة لإغلاقها - الحظر - - Should I Answer؟ - الترشيح التلقائي للرسائل من الأرقام غير المرغوبة باستخدام تطبيق \"Should I Answer\" الإرسال المؤخَّر إجراءات السَّحْب ضبط إجراءات السحب بالنسبة للمحادثات @@ -216,9 +220,11 @@ حذف مكالمة تعليمها مقروءة + التحديد كمقروء تأكيدات الإستلام تأكيد أنه تم إرسال الرسائل بنجاح + توقيع نزع حركات التشكيل حذف حركات التشكيل من المحارف في الرسائل النصية الصادرة أرقام الهواتف النقالة فقط @@ -231,10 +237,43 @@ تسجيل التصحيح مفعَّل تسجيل التصحيح معطَّل أدخل المدة (ثوان) - الحظر - المحادثات المحظورة لديك ستظهر هنا - إلغاء الحظر - هل ترغب إلغاء حظر هذه المحادثة؟ + المحظورات + Drop messages + Drop incoming messages from blocked senders instead of hiding them + المحادثات المحظورة + مدير المحظورات + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + الترشيح التلقائي للرسائل من الأرقام غير المرغوبة باستخدام تطبيق \"Should I Answer\" + نسخ الارقام المحظورة + Continue to %s and copy over your existing blocked numbers + الارقام المحظورة + ارقامك المحظورة سوف تظهر هنا + حظر رقم جديد + حظر الرسائل من + رقم الهاتف + حظر + الرسائل المحظورة + رسائلك المحظورة سوف تظهر هنا + حظر + الغاء الحظر + + الاستمرار الى %s وحظر هذا الرقم + الاستمرار الى %s وحظر هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + + + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + الاستمرار الى %s واتاحة هذا الرقم + حول النسخة المطوّر @@ -243,6 +282,38 @@ للاتصال الترخيص حقوق النسخ + دعم التطوير و فتح جميع المزايا + يمكنك إنقاذ المطوّر الجائع ب %s فقط + + ترقية مدى الحياة ل %1$s%2$s + + افتح + التبرع ل %1$s%2$s + شكراً لكم على دعم QKSMS! + جميع ميزات QKSMS+ متاحة لك الآن + QKSMS+ مجاني لمستخدمي متجر ف-درويد! لا تتردد في التبرع إن كنت ترغب دعم تطوير هذا التطبيق. + التبرع عن طريق بي بال + قريباً + سمات فاخرة + استفد من ألوان رائعة للسمات لاتوجد في لوح ألوان Material Design + إيموجي تلقائية ومخصصة + إنشاء اختصارات إيموجي تلقائية ومخصّصة + النسخ الاحتياطي للرسائل + النسخ الاحتياطي التلقائي لرسائلك. +لا تقلق ثانية من فقد سجلات رسائلك عندما تغير جوالك أو عند فقده + الرسائل المجدولة + جدولة الرسائل ليتم إرسالها تلقائياً في وقت وتاريخ محدد + إرسال متأخر + انتظر بضع ثوان قبل إرسال رسالتك + الوضع الليلي التلقائي + تمكين الوضع الليلي استناداً على الوقت من اليوم + الحظر المتقدم + حظر الرسائل التي تحوي كلمات أو تطابق أنماطاً + إعادة التوجيه التلقائية + أعد التوجيه تلقائياً للرسائل من مرسلين معينين + الرد التلقائي + الرد تلقائياً على الرسائل الواردة برسالة محددة مسبقاً + المزيد + QKSMS قيد التطوير النشط ، مشتراك سيتضمن كل ميزات QKSMS+ المستقبلية! تحميل… عرض المزيد من المحادثات علّمها مقروءة @@ -261,13 +332,14 @@ اتصال حذف + نعم + الاستمرار إلغاء حذف حفظ إيقاف المزيد تعيين - إلغاء الحظر تراجع تم النسخ تم حفظ المحادثة @@ -282,9 +354,10 @@ لم ترسَل الرسالة فشل الإرسال إلى %s - معطَّل - يعمل دوماً - تلقائياً + نظام + الغاء التفعيل + دائما مفعل + تلقائي عرض الاسم والرسالة diff --git a/presentation/src/main/res/values-bn/strings.xml b/presentation/src/main/res/values-bn/strings.xml index 1b78b52b6d2e802dd0c74c5236c2fba67f35e812..57f98df5830e3d3b796d5d9634b266916248d549 100644 --- a/presentation/src/main/res/values-bn/strings.xml +++ b/presentation/src/main/res/values-bn/strings.xml @@ -1,7 +1,7 @@ ২৩শে ডিসেম্বর পাঠানো হচ্ছে  পরবর্তীতে পাঠানোর জন্য একটি বার্তা পরিকল্পনা করুন পরিকল্পিত বার্তা এখন পাঠান - মুছে ফেলুন + Copy text + Delete উপস্থিতি সাধারণ @@ -196,10 +205,6 @@ নতুন বার্তার জন্য পপ-আপ সরিয়ে দিতে ট্যাপ করুন এটি বন্ধ করার জন্য পপআপের বাইরে ট্যাপ করুন - ব্লক করা - - আমার কি উত্তর দেওয়া উচিত? - স্বয়ংক্রিয়ভাবে \"Should I Answer\" অ্যাপ্লিকেশন ব্যবহার করে অযাচিত সংখ্যার বার্তাগুলি ফিল্টার করুন বিলম্বে পাঠানো সোয়াইপের জন্য অ্যাকশনগুলি কথোপকথনের জন্য সোয়াইপ অ্যাকশনগুলি কনফিগার করুন @@ -212,9 +217,11 @@ মুছে ফেলুন কল পঠিত হিসেবে চিহ্নিত করুন + না পড়া চিহ্নিত কর ডেলিভারি নিশ্চিতকরন নিশ্চিত করুন যে বার্তা সফলভাবে পাঠানো হয়েছে + স্বাক্ষর কথার টান সরিয়ে দিন বহির্গামী এসএমএস বার্তার অক্ষর থেকে কথার টান সরিয়ে দিন শুধু মোবাইল নম্বর @@ -227,10 +234,35 @@ ডিবাগ লগিং চালু করা হয়ছে ডিবাগ লগিং বন্ধ করা হয়ছে দৈর্ঘ্য (সেকেন্ডে) লিখুন - ব্লক করা - আপনার ব্লক করা কথোপকথন দেখা যাবে এখানে - আনব্লক করুন - আপনি এই কথোপকথন আনব্লক করতে চান? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + স্বয়ংক্রিয়ভাবে \"Should I Answer\" অ্যাপ্লিকেশন ব্যবহার করে অযাচিত সংখ্যার বার্তাগুলি ফিল্টার করুন + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + সম্পর্কে সংস্করণ ডেভেলপার @@ -239,6 +271,37 @@ যোগাযোগ করুন লাইসেন্স স্বত্ব + উন্নয়নে সমর্থন করুন, সবকিছু আনলক করুন + আপনি একটি ক্ষুধার ডেভেলপারকে বাঁচাতে পারেন মাত্র %s + + সারা জীবনের জন্য আপগ্রেড করুন %1$s %2$s দিয়ে + + %1$s %2$s দান করুন, + আনলক করুন + QKSMS সমর্থনের জন্য আপনাকে ধন্যবাদ! + আপনি এখন QKSMS+-এর সব বৈশিষ্ট্যেের সুবিধা পাচ্ছেন + F-Droid ব্যবহারকারীরা QKSMS+ বিনামূল্যে ব্যবহার করতে পারবেন! যদি আপনি উন্নয়ন সমর্থন করতে মুক্ত মনে অনুদান করুন। + পেপাল এর মাধ্যমে দান করুন + শীঘ্রই আসছে + প্রিমিয়াম থিম + Material Design প্যালেট পাওয়া যায় না এমন সুন্দর রঙয়ের থীম আনলক করুন + কাস্টম সক্রিয়-ইমোজি + কাস্টম সক্রিয়-ইমোজি শর্টকাট তৈরি করুন + বার্তা ব্যাক আপ + আপনার বার্তাগুলো স্বয়ংক্রিয়ভাবে ব্যাক আপ করুন। এবারআপনার ফোন পরিবর্তন বা আপনার ফোন নষ্ট হওয়ায় আপনাকে কখনো এসএমএস ইতিহাস হারানোর বিষয়ে উদ্বিগ্ন হতে হবে না + পরিকল্পিত বার্তাগুলি + পরিকল্পিত বার্তাগুলি স্বয়ংক্রিয়ভাবে একটি নির্দিষ্ট সময় ও তারিখ প্রেরণ করা হবে + বিলম্বে পাঠানো + আপনার বার্তা পাঠানোর আগে কয়েক সেকেন্ড অপেক্ষা করুন + স্বয়ংক্রিয় রাত্রি মোড + দিন সময় উপর ভিত্তি করে নাইট মোড সক্ষম করুন + উন্নীত আনব্লক করা + সেই সব বার্তা ব্লক কারুন যার কোন শব্দ বা নমুনার সাথে মিলে + সক্রিয়ভাবে সামনে পাঠান + নির্দিষ্ট প্রেরকের বার্তাগুলো স্বয়ংক্রিয়ভাবে সামনে পাঠান + স্বয়ংক্রিয়-উত্তর + আগত বার্তা জন্য স্বয়ংক্রিয়ভাবে পূর্বনির্ধারিত প্রতিক্রিয়া পাঠান + আরও + QKSMS সক্রিয় উন্নয়ন অধীনে এবং QKSMS+ এর সকাল ভবিষ্যত ফিচার আপনার কেনা অ্যাপেে অন্তর্ভুক্ত থাকবে! লোড হচ্ছে… আরও কথোপকথন প্রদর্শন করুন পড়া চিহ্নিত করুন @@ -257,13 +320,14 @@ কল মুছে ফেলুন + Yes + Continue বাতিল করুন মুছে ফেলুন সেভ করুন থামুন আরও সেট করুন - আনব্লক করুন বাতিল করো অনুলিপি করা হয়েছে আর্কাইভ করা কথোপকথন @@ -274,8 +338,9 @@ বার্তা পাঠাতে অক্ষম %s এর বার্তাটি পাঠাতে ব্যর্থ হয়েছে - নিষ্ক্রিয় - সবসময় চালু + পদ্ধতি + অক্ষম + সর্বদা চালু স্বয়ংক্রিয় diff --git a/presentation/src/main/res/values-cs/strings.xml b/presentation/src/main/res/values-cs/strings.xml index 9d8feb46cd52e21cf67ee97e408aa80f33a6cbca..a659d6f11fc587d341b3f01cde5b7aa62ce32899 100644 --- a/presentation/src/main/res/values-cs/strings.xml +++ b/presentation/src/main/res/values-cs/strings.xml @@ -1,7 +1,7 @@ Posíláno 23. prosince Naplánovat zprávu Naplánovaná zpráva Odeslat nyní + Kopírovat text Smazat Vzhled @@ -200,10 +208,6 @@ Vyskakovací okno pro nové zprávy Klepnutím zavřít Pro ukončení klepněte kamkoli mimo okno zprávy - Blokování - - Můžu to zvednout? - Automaticky filtrovat zprávy od nevyžádaných čísel pomocí aplikace „Můžu to zvednout?“ Zpožděné odeslání Akce přejetí Nastavení akcí pro přejetí v konverzacích @@ -216,9 +220,11 @@ Smazat Volat Přečteno + Označit jako nepřečtené Potvrzení o doručení Potvrdit, že zprávy byly odeslány úspěšně + Podpis Odstranit diakritiku Odstranit diakritiku v odesílaných SMS zprávách Pouze mobilní čísla @@ -231,10 +237,39 @@ Ladící protokolování zapnuto Ladící protokolování vypnuto Zadejte prodlevu (v sekundách) - Blokování - Zde se objeví vaše blokované konverzace - Odblokovat - Chcete odblokovat tuto konverzaci? + Blokování + Zahodit zprávy + Zahodit příchozí zprávy od blokovaných odesílatelů namísto jejich skrytí + Blokované konverzace + Správce blokování + QKSMS + Vestavěné blokování v QKSMS + Automatické filtrování hovorů a zpráv na jednom místě. Community IQ™ poskytuje ochranu před nevyžádanými zprávami od komunitou označených spammerů. + Automaticky filtrovat zprávy od nevyžádaných čísel pomocí aplikace „Můžu to zvednout?“ + Kopírovat blokovaná čísla + Pokračovat do %s a zkopírovat stávající zablokovaná čísla + Blokovaná čísla + Vámi blokovaná čísla se objeví zde + Blokovat nové číslo + Blokované zprávy od + Telefonní číslo + Blokovat + Blokované zprávy + Vámi blokované konverzace se objeví zde + Blokovat + Odblokovat + + Přejít do %s a zablokovat toto číslo + Přejít do %s a zablokovat tato čísla + Přejít do %s a zablokovat tato čísla + Přejít do %s a zablokovat tato čísla + + + Přejít do %s a povolit toto číslo + Přejít do %s a povolit tato čísla + Přejít do %s a povolit tato čísla + Přejít do %s a povolit tato čísla + O aplikaci Verze Vývojář @@ -243,6 +278,37 @@ Kontakt Licence Autorská práva + Podpoř vývoj, odemkni vše + Můžete zachránit hladovějícího vývojáře jen za %s + + Doživotní aktualizace za %1$s %2$s + + Odemknout + darovat %1$s %2$s + Děkujeme za podporu QKSMS! + Nyní máte přístup ke všem funkcím QKSMS+ + QKSMS + je zdarma pro uživatele F-Droid! Pokud byste chtěli podpořit vývoj, neváhejte přispět. + Darovat pomocí PayPal + Připravujeme + Prémiové motivy vzhledu + Nádherné barvy, které nejsou na paletě Material designu + Vlastní automatické emoji + Vytvořte si zkratky pro vlastní automatické emoji + Zálohování zpráv + Zálohujte automaticky své zprávy. Už se nemusíte obávat ztráty historie zpráv při ztrátě nebo výměně telefonu + Plánované zprávy + Plánované zprávy budou automaticky odeslány v určené datum a čas + Zpožděné odeslání + Vyčkání několik sekund před odesláním zprávy + Automatický noční režim + Zapnutí nočního režimu podle denní doby + Pokročilé blokování + Blokování zpráv dle klíčových slov nebo vzorů + Automatické přeposílání + Automatické přeposílání zpráv od určitých odesílatelů + Automatická odpověď + Automatické odpovídání na příchozí zprávy přednastavenou odpovědí + Více + QKSMS je v aktivním vývoji a váš nákup bude obsahovat i všechny budoucí funkce QKSMS+! Načítání… Zobrazit další konverzace Označit jako přečtené @@ -261,13 +327,14 @@ Volat Smazat + Ano + Pokračovat Zrušit Odstranit Uložit Zastavit Více Nastavit - Odblokovat Zpět Zkopírováno Archivované konverzace @@ -280,9 +347,10 @@ Zpráva nebyla odeslána Odeslání zprávy pro %s se nezdařilo - Zakázáno + Dle systému + Vypnuto Vždy zapnuto - Automatický + Automaticky Zobrazit jméno a text zprávy diff --git a/presentation/src/main/res/values-da/strings.xml b/presentation/src/main/res/values-da/strings.xml index 497fb15847119b66fa3a7a98a4dae363d26d04cb..93dad5403ed87acff6a04691be84d5f87b997f62 100644 --- a/presentation/src/main/res/values-da/strings.xml +++ b/presentation/src/main/res/values-da/strings.xml @@ -1,7 +1,7 @@ Sender d. 23. december  Planlæg en besked Planlagt besked Send nu + Kopiér tekst Slet Udseende @@ -192,10 +200,6 @@ Popup ved nye SMS\'er Tryk for at afvise Tryk udenfor popup-vinduet for at lukke det - Blokering - - Bør jeg besvare? - Filtrér automatisk beskeder fra uopfordrede numre vha. \"Bør jeg besvare\"-app\'en Forsinket afsendelse Strygehandlinger Opsæt strygehandlinger for samtaler @@ -208,9 +212,11 @@ Slet Ring op Markér som læst + Markér som ulæst Leveringskvitteringer Bekræft, at SMS\'er er afsendt korrekt + Signatur Fjern betoninger Fjern betoninger fra tegn i udgående SMS\'er Kun mobilnumre @@ -223,10 +229,35 @@ Fejlfindingslogning aktiveret Fejlfindingslogning deaktiveret Angiv varighed (sekunder) - Blokering - Dine blokerede samtaler vil fremgå her - Afblokér - Fjern blokeringen af denne samtale? + Blokering + Drop beskeder + Drop indgående beskeder fra blokerede afsendere i stedet for at skjule dem + Blokerede samtaler + Blokeringshåndtering + QKSMS + Indbygget blokeringsfunktionalitet i QKSMS + Filtrér automatisk dine opkald og beskeder på ét praktisk sted! Community IQ ™ giver dig mulighed for at forhindre uønskede beskeder fra spammere kendt af fællesskabet + Filtrér automatisk beskeder fra uopfordrede numre vha. \"Bør jeg besvare\"-app\'en + Kopiér blokerede numre + Fortsæt til %s og kopiér dine blokerede nummer dertil + Blokerede numre + Dine blokerede numre vil fremgå hér + Blokér et nyt nummer + Blokér SMS\'er fra + Telefonnummer + Blokér + Blokerede beskeder + Dine blokerede beskeder vil fremgå hér + Blokér + Afblokér + + Fortsæt til %s og blokér dette nummer + Fortsæt til %s og blokér disse numre + + + Fortsæt til %s og tillad dette nummer + Fortsæt til %s og tillad disse numre + Om Version Udvikler @@ -235,6 +266,37 @@ Kontakt Licens Copyright + Støt udviklingen, oplås alt + Du kan redde en sultende udvikler for kun %s + + Livstidsopgradering for %1$s %2$s + + Oplås + donér for %1$s %2$s + Tak for din støtte til QKSMS! + Du har nu adgang til alle QKSMS+-funktioner + QKSMS+ er gratis for F-Droid-brugere! Ønsker du at støtte udviklingen, er du velkommen til at donere. + Donér via PayPal + Kommer snart + Premium-temaer + Oplås smukke temafarver, som ikke er tilgængelig på Material Design-paletten + Tilpasset auto-emoji + Opret tilpassede auto-emoji-genveje + Beskedsikkerhedskopiering + Sikkerhedskopiér automatisk dine beskeder. Ikke flere bekymringer over tab af din historik, hvis du udskifter eller mister din mobil + Planlagte beskeder + Planlæg beskeder til automatisk at blive sendt på et specifikt tidspunkt og dato + Forsinket afsendelse + Vent et par sekunder før du afsender beskeden + Automatisk nattilstand + Aktivér nattilstanden baseret på klokkeslæt + Avanceret blokering + Blokér beskeder indeholdende nøgleord eller mønstermatch + Auto-videresendelse + Videresend automatisk beskeder fra bestemte afsendere + Autosvar + Besvarer automatisk indgående beskeder med et foruddefineret svar + Flere + QKSMS er under aktiv udvikling, og dit køb vil omfatte alle fremtidige QKSMS+ funktioner! Indlæser… Se flere samtaler Markér som læst @@ -253,13 +315,14 @@ Ring op Slet + Ja + Fortsæt Annullér Slet Gem Stop Flere Indstil - Afblokér Fortryd Kopieret Arkiveret samtale @@ -270,8 +333,9 @@ Besked ikke afsendt Mislykkedes at afsende beskeden til %s - Deakitiveret - Altid aktiv + System + Deaktiveret + Altid slået til Automatisk diff --git a/presentation/src/main/res/values-de/strings.xml b/presentation/src/main/res/values-de/strings.xml index 03d764db2c0b266ecbbb1204f5e7150256b8521a..26ebd45e5d050c92aa0a7aeab92612eb7e082c02 100644 --- a/presentation/src/main/res/values-de/strings.xml +++ b/presentation/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Am 23. Dezember senden  Nachricht verschieben Geplante Nachricht Sofort senden + Text kopieren Löschen Aussehen @@ -196,10 +204,6 @@ Popup für neue Nachrichten Zum Verwerfen tippen Tippe außerhalb des Popup um es zu schließen - Blockieren - - Should I Answer? - Nachrichten von unerwünschten Nummern automatisch mit Hilfe der App \"Should I Answer?\" filtern Verzögertes Senden Gesten Konfiguriere Gesten für Unterhaltungen @@ -212,9 +216,11 @@ Löschen Anrufen Als gelesen markieren + Einstellung Streichbewegung Zustellbestätigungen Bestätigung, dass Nachrichten erfolgreich gesendet wurden + Signatur Akzente entfernen Akzente aus Zeichen in ausgehenden SMS-Nachrichten entfernen Nur Mobilfunknummern @@ -227,10 +233,35 @@ Protokollierung zur Fehlerbehebung aktiviert Protokollierung zur Fehlerbehebung deaktiviert Dauer eingeben (in Sekunden) - Blockieren - Blockierte Unterhaltungen werden hier angezeigt - Entsperren - Willst Du diese Unterhaltung entsperren? + Blockieren + Nachrichten verwerfen + Eingehende Nachrichten von blockierten Absendern verwefen anstatt sie auszublenden + Blockierte Unterhaltungen + Blockiermanager + QKSMS + In QKSMS integrierte Blockierfunktion + Filtern Sie automatisch Ihre Anrufe und Nachrichten in einer geeigneten Umgeben! Community IQ™ erlaubt es Ihnen unerwünschte Nachrichten von bekannten Spamnern zu unterbinden + Nachrichten von unerwünschten Nummern automatisch mit Hilfe der App \"Should I Answer?\" filtern + blockierte Nummer(n) kopieren + Zu %s wechseln und zu den existierenden blockierten Nummern kopieren + blockierte Nummern + Ihre blockierte Nummern werden hier angezeigt + Eine neue Nummer blockieren + Blockiere Texte von + Telefonnummer + Blockieren + Blockierte Nachrichten + Ihre blockierte Nachrichten werden hier angezeigt + Blockieren + Freigeben + + Wechseln zu %s und diese Nummer blockieren + Wechseln zu %s und diese Nummern blockieren + + + Wechseln zu %s und diese Nummer erlauben + Wechseln zu %s und diese Nummern erlauben + Über Version Entwickler @@ -239,6 +270,37 @@ Kontakt Lizenz Urheberrecht + Unterstütze die Entwicklung, schalte alle Funktionen frei + Rette einen hungernden Entwickler für nur %s + + Lebenslanges Upgrade für %1$s %2$s + + Entsperren & Spenden für %1$s %2$s + Vielen Dank für die Unterstützung von QKSMS! + Du kannst jetzt alle QKSMS+ Funktionen verwenden + QKSMS+ ist für Nutzer von F-Droid kostenfrei! Spende gerne um die Entwicklung zu Unterstützen. + Spenden per PayPal + Demnächst verfügbar + Premium-Erscheinungsbilder + Wundervolle Farben, die kein Teil der Material Design-Farbpalette sind, für das Erscheinungsbild freischalten + Benutzerdefinierte Auto-Emoji + Benutzerdefinierte Auto-Emoji-Verknüpfungen erstellen + Nachrichtensicherung + Sichere Deine Nachrichten automatisch. Solltest Du Dein Handy wechseln oder es verlieren, musst Du Dir keine Sorgen mehr machen + Geplante Nachrichten + Den automatischen Versand von Nachrichten zu einem bestimmen Zeitpunkt planen + Verzögertes Senden + Wartet einige Sekunden, bevor deine Nachricht gesendet wird + Automatischer Nachtmodus + Nachtmodus basierend auf der Uhrzeit aktivieren + Erweitertes Blockieren + Nachrichten die bestimmte Wörter oder Muster enthalten blockieren + Automatische Weiterleitung + Nachrichten von bestimmten Absendern automatisch weiterleiten + Automatische Antwort + Eingehende Nachrichten werden mit einer voreingestellten Antwort beantwortet + Mehr + QKSMS wird aktiv weiterentwickelt und Dein Erwerb wird alle zukünftigen Funktionen enthalten! Lädt… Weitere Unterhaltungen anzeigen Als gelesen markieren @@ -257,13 +319,14 @@ Anrufen Löschen + Ja + Fortsetzen Abbrechen Löschen Speichern Stop Mehr OK - Entsperren Rückgängig Kopiert Archivierte Unterhaltung @@ -274,8 +337,9 @@ Nachricht wurde nicht gesendet Die Nachricht konnte nicht an %s gesendet werden + System Deaktiviert - Immer aktiviert + Immer an Automatisch diff --git a/presentation/src/main/res/values-no/strings.xml b/presentation/src/main/res/values-el/strings.xml similarity index 52% rename from presentation/src/main/res/values-no/strings.xml rename to presentation/src/main/res/values-el/strings.xml index 1499ec0504cb397feca24c7819b9901b25c1d637..37e2e1c84110c4e3dcaf92ed0b3c417ab68295e7 100644 --- a/presentation/src/main/res/values-no/strings.xml +++ b/presentation/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ + Sending on December 23rd  + Schedule a message + Scheduled message - Send nå - Slett + Send now + Copy text + Delete - Utseende - Generelt - QK Svar - Tema - Nattmodus - Ren svart nattmodus - Starttid - Sluttidspunkt - Skriftstørrelse - Bruk systemskrift - Automatisk emoji - Varsler - Trykk for å tilpasse - Handlinger - Knapp 1 - Knapp 2 - Knapp 3 - Forhåndsvisninger av varsler - Vibrasjon - Lyd - Ingen - QK Svar + Appearance + General + QK Reply + Theme + Night mode + Pure black night mode + Start time + End time + Font size + Use system font + Automatic emoji + Notifications + Tap to customize + Actions + Button 1 + Button 2 + Button 3 + Notification previews + Vibration + Sound + None + QK Reply Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -209,10 +211,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -225,10 +229,35 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About Version Developer @@ -237,9 +266,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -250,18 +310,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -272,6 +333,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-es/strings.xml b/presentation/src/main/res/values-es/strings.xml index 546b832680b3f7ad7cd61a2a25f382d83d43cd66..d7dd20d94b384386028e3b7b5198fd12947c81bc 100644 --- a/presentation/src/main/res/values-es/strings.xml +++ b/presentation/src/main/res/values-es/strings.xml @@ -1,7 +1,7 @@ Enviando el 23 de Diciembre Programar un mensaje Mensaje programado Enviar ahora - Eliminar + Copy text + Delete Apariencia General @@ -192,10 +200,6 @@ Ventana emergente para los nuevos mensajes Tocar para descartar Toque fuera de la ventana emergente para cerrarla - Bloquedo - - ¿Debo responder? - Filtra automáticamente los mensajes de números no solicitados mediante la aplicación \"¿Debo responder?\" Retraso en el envío Acciones al deslizar Configurar acciones de deslizamiento para conversaciones @@ -208,9 +212,11 @@ Eliminar Llamar Marcar como leído + Marcar como no leído Confirmaciones de envió Confirmar cuando los mensajes se envien con éxito + Firma Detalles en tiras Quitar acentos de caracteres en los mensajes SMS salientes Sólo números móviles @@ -223,10 +229,35 @@ Registro de depuración activado Registro de depuración desactivado Introduzca la duración (segundos) - Bloqueado - Sus conversaciones bloqueadas aparecerán aquí - Desbloquear - ¿Quieres desbloquear esta conversación? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Filtra automáticamente los mensajes de números no solicitados mediante la aplicación \"¿Debo responder?\" + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Acerca de Versión Desarrollador @@ -235,6 +266,37 @@ Contáctanos Licencia Derechos de autor + Apoye el desarrollo, desbloquee todo + Puedes salvar a un desarrollador hambriento por sólo %s + + Actualización de por vida por %1$s %2$s + + Desbloquear + donación %1$s %2$s + Gracias por apoyar a QKSMS! + Ahora tienes acceso a todas las funciones de QKSMS + + QKSMS+ es gratis para los usuarios de F-Droid! Si desea apoyar el desarrollo, no dude en hacer una donación. + Donar con PayPal + Próximamente + Temas Premium + Desbloquea hermosos temas de colores que no están disponibles en la paleta Material Design + Auto-emoji personalizado + Crear accesos directos personalizados de auto-emoji + Copia de seguridad de mensajes + Copia de seguridad automática de tus mensajes. Nunca más se preocupe por perder su historial si cambia o pierde su teléfono + Mensajes programados + Programa mensajes para enviarlos automáticamente a una hora y fecha específica + Retraso en el envío + Espere unos segundos antes de enviar su mensaje + Modo nocturno automático + Activar el modo nocturno basado en la hora del día + Bloqueo avanzado + Bloquear los mensajes que contengan palabras clave o patrones coincidentes + Reenvío automático + Reenviar automáticamente los mensajes de determinados remitentes + Auto-responder + Responder automáticamente a mensajes entrantes con una respuesta preestablecida + Más + QKSMS está bajo desarollo activo, y sus compras incluirán todas las características futuras de QKSMS+! Cargando… Ver más conversaciones Marcar como leído @@ -253,13 +315,14 @@ Llamar Eliminar + Yes + Continue Cancelar Eliminar Guardar Detener Más Establecer - Desbloquear Deshacer Copiado Conversación archivada @@ -270,9 +333,10 @@ Mensaje no enviado El mensaje a %s no se pudo enviar - Desabilitado - Siempre activado - Automático + System + Disabled + Always on + Automatic Mostrar nombre y el mensaje diff --git a/presentation/src/main/res/values-fa/strings.xml b/presentation/src/main/res/values-fa/strings.xml index c3ad8844b26aded4daccc33d15c2d2b4b7d6fc50..9978f955a8bcb35b26ba02ff0eb06585bbb07922 100644 --- a/presentation/src/main/res/values-fa/strings.xml +++ b/presentation/src/main/res/values-fa/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message - Send now + ارسال هم‌اکنون + Copy text Delete ظاهر @@ -194,10 +202,6 @@ پنجره پیام های جدید برای رد کردن ضربه بزنید Tap outside of the popup to close it - مسدود کردن - - باید پاسخ بدهم؟ - با استفاده از برنامه «باید جواب بده»، پیامها را از شماره های ناخواسته به طور خودکار فیلتر کنید ارسال با تاخیر Swipe actions تنظیم کشیدن برای گفتگو @@ -210,9 +214,11 @@ پاک کردن تماس خوانده شده + علامت گذاری بعنوان خوانده نشده تأیید تحویل تأیید ارسال موفق پیام + امضا حذف لهجه‌ها حذف کاراکترهای اضافه پیام ارسالی فقط شماره های تلفن @@ -225,10 +231,35 @@ ورود به سیستم دیباگ فعال است ورد به سیستم دیباگ غیرفعال است زمان (ثانیه) را وارد کنید - مسدود کردن - مکالمات مسدود شده شما در اینجا نشان داده می شود - رفع مسدودیت - می‌خواهید مسدودیت این گفتگو را رفع کنید؟ + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + با استفاده از برنامه «باید جواب بده»، پیامها را از شماره های ناخواسته به طور خودکار فیلتر کنید + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + درباره نسخه توسعه دهنده @@ -237,6 +268,37 @@ مخاطب مجوز حق نشر + حمایت از تولید کننده،بازشدن همه چیز + شما میتوانید با %s از ما حمایت کنید + + زمان توسعه %1$s %2$s + + نسخه پیشرفته و حمایت %1$s %2$s + ممنون از حمایت شما! + حال شما به همه امکانات دسترسی دارید + QKSMS + برای کاربران F-Droid رایگان است! اگر میخواهید از توسعه حمایت کنید، احساس رایگان کنید تا کمک مالی شود. + از طریق PayPal کمک مالی کنید + به زودی + پوسته پیشرفته + در حالت متریال دیزاین رنگبندی پوسته امکانپذیر نیست + شکلک خودکار سفارشی + ساخت میانبرهای شخصی برای شکلک اتوماتیک + پشتیبان‌گیری پیامها + از پیام‌هایتان به‌طور خودکار پشتیبان بگیرید. دیگر نگران از دست دادن پیام ها نباشید وقتی تلفن همراهتان را عوض می‌کنید یا از دست می‌دهید + پیام های برنامه ریزی شده + پیام های برنامه ریزی شده به طور خودکار در یک زمان و تاریخ خاص ارسال می شود + تاخیر در ارسال + لطفا چند ثانیه صبر کنید + حالت شب اتوماتیک + فعال کردن حالت شب براساس زمان روز + مسدود کردن پیشرفته + هرزنامه ها وپیام های بد را مسدود کن + هدایت خودکار + هدایت کردن پیام از فرستندگان خاص + پاسخ خودکار + ارسال جواب خودکار به پیامک ها + بیشتر + QKSMS در حال توسعه فعال است، و خرید شما شامل تمام ویژگی های آینده QKSMS + نیز هست ! بارگذاری… مشاهده مکالمات بیشتر خوانده شده @@ -255,13 +317,14 @@ تماس پاک کردن + Yes + Continue لغو حذف ذخیره توقف بيشتر تنظیم - رفع مسدودیت برگرده کپی شد گفتگوی بایگانی شده @@ -272,6 +335,7 @@ ارسال نشد پیام به٪ s فرستاده نشد + سامانه غیرفعال همیشه روشن خودکار @@ -290,17 +354,17 @@ بدون تاخیر کوتاه - Medium - Long + متوسط + طولانی ۱۰۰KB ۲۰۰KB - 300KB (Recommended) + 300 کیلوبایت(توصیه شده) ۶۰۰KB ۱۰۰۰KB ۲۰۰۰KB - No compression + بدون فشردگی باشه diff --git a/presentation/src/main/res/values-fi/strings.xml b/presentation/src/main/res/values-fi/strings.xml index 64491f9cb431abb7fba00fadd1cf15b6c5022b73..489836d5ab2a74f185a538f04cd5beec92c89905 100644 --- a/presentation/src/main/res/values-fi/strings.xml +++ b/presentation/src/main/res/values-fi/strings.xml @@ -1,7 +1,7 @@ Lähetetään joulukuun 23. päivänä Ajoita viesti Ajoitettu viesti Lähetä nyt - Poista + Copy text + Delete Ulkoasu Yleiset @@ -196,10 +204,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Lykätty lähetys Swipe actions Configure swipe actions for conversations @@ -211,10 +215,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -227,10 +233,35 @@ Debug logging enabled Debug logging disabled Syötä kesto (sekuntia) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Tietoja Versio Kehittäjä @@ -239,6 +270,37 @@ Yhteystiedot Lisenssi Tekijänoikeus + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Kiitos kun tuit QKSMS:ää! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Lahjoita PayPalilla + Tulossa pian + Premium-teemat + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Viestien varmuuskopiointi + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Ajoitetut viestit + Ajoita viestit lähtemään automaattisesti tiettyinä aikoina ja päivinä + Lykätty lähetys + Odota muutama sekunti ennen viestin lähettämistä + Automatic night mode + Enable night mode based on the time of day + Edistynyt estäminen + Block messages that contain keywords or match patterns + Automaattinen välitys + Välitä tiettyjen lähettäjien viestit automaattisesti + Automaattinen vastaus + Automatically respond to incoming messages with a preset response + Lisää + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Ladataan… Näytä lisää keskusteluja Merkitse luetuksi @@ -257,13 +319,14 @@ Soita Delete + Yes + Continue Peruuta Poista Tallenna Stop Lisää Aseta - Poista esto Peru Kopioitu Arkistoidut keskustelut @@ -274,9 +337,10 @@ Viestiä ei lähetetty Viestin lähetys vastaanottajalle %s epäonnistui - Pois käytöstä - Aina käytössä - Automaattinen + System + Disabled + Always on + Automatic Näytä nimi ja viesti diff --git a/presentation/src/main/res/values-fr/strings.xml b/presentation/src/main/res/values-fr/strings.xml index 1242e29a024c541beb6c67d1d28ee6eac6ab07c5..618215f756d986844b63329603e93deb1cb29264 100644 --- a/presentation/src/main/res/values-fr/strings.xml +++ b/presentation/src/main/res/values-fr/strings.xml @@ -1,7 +1,7 @@ Envoyé le 23 décembre  Programmer un message Message programmé Envoyer maintenant + Copier le texte Supprimer Apparence @@ -196,10 +204,6 @@ Fenêtre pour les nouveaux messages Appuyer pour annuler Appuyer en dehors de la fenêtre pour la fermer - Bloquer - - Devrais-je répondre ? - Filtrer automatiquement les messages de numéros non sollicités à l’aide de l’application « Devrais-je répondre » Envoi retardé Actions de balayage Configurer les actions de balayage pour les conversations @@ -212,9 +216,11 @@ Supprimer Appeler Marquer comme lu + Marquer comme non-lu Accusés de réception Demander un accusé de réception pour chaque SMS envoyé + Signature Enlever les accents Supprimer les accents des caractères lors de l\'envoi de messages SMS Numéros de mobile @@ -227,10 +233,35 @@ Journal de débogage activé Journal de débogage désactivé Entrez la durée (en secondes) - Bloquer - Vos conversations bloquées apparaîtront ici - Débloquer - Voulez-vous débloquer cette conversation ? + Blocage + Supprimer les messages + Supprimer les messages entrants des expéditeurs bloqués au lieu de les masquer + Conversations bloquées + Gestionnaire de blocage + QKSMS + Fonction de blocage intégrée dans QKSMS + Filtrez automatiquement vos appels et vos messages dans un seul endroit pratique ! La communauté IQ™ vous permet d\'éviter les messages indésirables des spammeurs connus de celle-ci + Filtrer automatiquement les messages de numéros non sollicités à l’aide de l’application « Devrais-je répondre » + Copier les numéros bloqués + Utiliser %s et écraser vos numéros bloqués existants + Numéros bloqués + Vos numéros bloqués apparaîtront ici + Bloquer un nouveau numéro + Bloquer les messages de + Numéro de téléphone + Bloquer + Messages bloqués + Vos messages bloqués apparaîtront ici + Bloquer + Débloquer + + Continuer avec %s et bloquer ce numéro + Continuer à %s et bloquer ces numéros + + + Continuer avec %s et autoriser ce numéro + Continuer à %s et autoriser ces numéros + À propos Version Développeur @@ -257,13 +288,14 @@ Appeler Supprimer + Oui + Continuer Annuler Supprimer Enregistrer Arrêter Plus Choisir - Débloquer Annuler Copié Conversation archivée @@ -274,6 +306,7 @@ Message non envoyé Le message à %s n\'a pu être envoyé + Système Désactivé Toujours activé Automatique diff --git a/presentation/src/main/res/values-hi/strings.xml b/presentation/src/main/res/values-hi/strings.xml index 67ce9863334efcdb51fdc32788b5affd3c1e1ef8..5c00ae9e9f0eb6e67fa2ba35860fa7c6289ad9cf 100644 --- a/presentation/src/main/res/values-hi/strings.xml +++ b/presentation/src/main/res/values-hi/strings.xml @@ -1,7 +1,7 @@ - New conversation - Compose - Shortcut disabled + नई बातचीत + रचना + शॉर्टकट अक्षम किया गया संग्रहीत सेटिंग्स सूचनाएं @@ -39,178 +39,188 @@ संग्रह असंग्रहित हटाएँ - Pin to top - Unpin + Add to contacts + शीर्ष पर पिन करें + अनपिन पढ़ा हुआ चिह्नित करें अपठित चिह्नित करें ब्लॉक संदेशों को सिंक्रनाइज़ कर रहा है.. । - You: %s - Results in messages - %d messages - Your conversations will appear here - No results - Your archived conversations will appear here - Start new conversation - Love texting again - Make Message your default SMS app - Change - Permission required - Message needs permission to send and view SMS messages - Message needs permission to view your contacts - Allow - Inbox - Archived - Scheduled - Blocking - More - Settings - Help & feedback - Invite friends - Delete + आप: %s + Draft: %s + संदेशों में परिणाम + %d संदेश + आपकी बातचीत यहां दिखाई देगी + कोई परिणाम नहीं + आपके संग्रहीत वार्तालाप यहां दिखाई देंगे + नई वार्तालाप प्रारंभ करें + फिर से टेक्स्टिंग करिये + QKSMS अपना डिफ़ॉल्ट SMS एप्लिकेशन बनाएं + बदलें + अनुमति आवश्यक + QKSMS भेजने और एसएमएस संदेश देखने के लिए अनुमति की जरूरत है + QKSMS अपने संपर्कों को देखने के लिए अनुमति की जरूरत है + अनुमति दे + इनबॉक्स + संग्रहीत + अनुसूचित + अवरुद्ध + और + सेटिंग्स + मदद और प्रतिक्रिया + मित्रों को आमंत्रित करें + अद्भुत नई सुविधाओं को अनलॉक करें, और विकास का समर्थन करें + QKSMS का आनंद ले रहे? + कुछ प्यार साझा करें और गूगल प्ले पर हमें मूल्यांकन करें + ठीक! + खारिज + हटाएँ - Are you sure you would like to delete this conversation? - Are you sure you would like to delete %d conversations? + क्या आप वाकई इस वार्तालाप को हटाना चाहते हैं? + क्या आप वाकई %d वार्तालापों को हटाना चाहते हैं? - Copy text - Forward - Delete + पाठ की प्रतिलिपि बनाएं + अग्रेषित करें + मिटाएँ - %d selected - %1$d of %2$d results - Send as group message - Recipients and replies will be visible to everyone - This is the start of your conversation. Say something nice! - Contact card - Scheduled for - Selected time must be in the future! - Added to scheduled messages - Write a message… - Copy text - Forward - Delete - Previous - Next - Clear - Message details - Type: %s - From: %s - To: %s - Subject: %s - Priority: %s - Size: %s - Sent: %s - Received: %s - Delivered: %s - Error code: %d + > + + %d चयनित + %1$d के %2$d परिणाम + समूह संदेश के रूप में भेजें + प्राप्तकर्ता और उत्तर सभी को दिखाई देंगे + यह आपकी बातचीत की शुरुआत है । कुछ अच्छा कहिये! + संपर्क कार्ड + के लिए निर्धारित + चयनित समय भविष्य में होना चाहिए! + आप QKSMS + अनुसूचित संदेश का उपयोग करने के लिए अनलॉक करना होगा + अनुसूचित संदेशों में जोड़ा गया + संदेश लिखें + पाठ की प्रतिलिपि बनाएं + आगे + मिटायें + पिछला + अगला + साफ करें + संदेश विवरण + टाइप %s + से: %s + को: %s + विषय: %s + महत्व: %s + आकार: %s + भेजा गया: %s + प्राप्त: %s + वितरित: %s + त्रुटि संकेत: %d Add an attachment - Attach a photo - Take a photo - Schedule message + चित्र संलग्न करें + चित्र खिंचे + संदेश अनुसूचित करें Attach a contact Error reading contact %s selected, change SIM card - Send message - Sending… - Delivered %s - Failed to send. Tap to try again - Details + संदेश भेजें + भेज रहा है… + %s वितरित + भेजने में असफल। पुनः प्रयास करें + विवरण Conversation title - Enter title - Notifications - Theme - Archive + अधिसूचनाएँ + थीम + संग्रह Unarchive - Block + अवरुदध करें Unblock - Delete conversation + वार्तालाप मिटाऐं Couldn\'t load media - Saved to gallery + गैलरी मैं सेव किया गया है Backup and restore Backing up messages Restoring from backup - Last backup - Loading… - Never + पिछला बेकप + लोड हो रहा है... + कभी नहीं Restore - Select a backup + किसी बैकअप का चयन करें + Please unlock QKSMS+ to use backup and restore Backup in progress… Restore in progress… Restore from backup Are you sure you would like to restore your messages from this backup? Stop restore Messages that have already been restored will remain on your device - Backups - No backups found + बैकअप + कोई बैकअप नहीं मिला - %d message - %d messages + %d संदेश + %d संदेशें - Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! - Backup now - Parsing backup… - %d/%d messages - Saving backup… - Syncing messages… - Finished! - Backup and restore - Scheduled - Automatically send a message, at the exact moment you\'d like - Hey! When was your birthday again? - It\'s on December 23rd - Happy birthday! Look at what a great friend I am, remembering your birthday - Sending on December 23rd  - Schedule a message - Scheduled message + वर्तमान में, केवल एसएमएस बैकअप और बहाल द्वारा समर्थित है| एमएमएस समर्थन और अनुसूचित बैकअप जल्द ही आ रहा है! + अभी बैकअप लें + बैकअप पदच्छेद हो रहा है... + %d/%d संदेशें + बैकअप सहेजा जा रहा है... + संदेश सिंक्रनाइज़ हो रहा है... + समाप्त! + बैकअप और पुनर्स्थापना + अनुसूचित + स्वचालित रूप से एक संदेश भेजें, जिस समय आप चाहते हैं + सुनो! आपका जन्मदिन कब था? + यह 23 दिसंबर को था + जन्मदिन की शुभकामनाएं! देखो, मैं कौन सा मित्र हूं, तुम्हारा जन्मदिन याद कर रहा हूं + + 23 दिसंबर को भेजा जा रहा है  + एक संदेश अनुसूचित करें + निर्धारित संदेश - Send now + अभी भेजें + Copy text Delete - Appearance - General - QK Reply - Theme - Night mode - Pure black night mode - Start time - End time - Font size - Use system font + दिखावट + सामान्य + QK उत्तर + थीम + रात्रि अंदाज़ + शुद्ध काली रात अंदाज़ + शुरुआत समय + अंत समय + लिखाई का आकर + यंत्र ध्वनियां उपयोग करें Automatic emoji - Notifications + सूचनाएं Tap to customize - Actions - Button 1 - Button 2 - Button 3 - Notification previews - Vibration - Sound - None - QK Reply + कार्रवाई + बटन एक + बटन दो + बटन तीन + अधिसूचना सेटिंग्स + कंपन + ध्वनियाँ + कुछ नहीं + QK जवाब Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations - Right swipe - Left swipe - CHANGE + दाहिने तरफ से खींचे + बायें तरफ से खींचे + बदलें None - Archive - Delete - Call - Mark read + संग्रह + मिटायें + कॉल करें + Mark as read + Mark as unread - Delivery confirmations + वितरण पुष्टि Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -218,48 +228,104 @@ Auto-compress MMS attachments Sync messages Re-sync your messages with the native Android SMS database - About Message - Version %s + About QKSMS + संस्करण %s Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About - Version + संस्करण Developer - Source code - Changelog - Contact - License - Copyright - Loading… - View more conversations - Mark read - Call - Delete - Show more - Show less - Open conversation + श्रोत कोड + बदलाव + संपर्क + लाइसेंस + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + जल्द आ रहा है + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + अधिक + QKSMS is under active development, and your purchase will include all future QKSMS+ features! + तैयार हो रहा है… + अधिक वार्तालाप देखें + Mark as read + कॉल + मिटायें + अधिक दिखाएं + कम दिखाएं + वार्तालाप खोलें Material HEX - Apply + लागू करें None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -267,51 +333,52 @@ New message %s new messages - Message not sent - The message to %s failed to send + संदेश नही भेजा गया + %s को संदेश भेजने में असफल + System Disabled Always on Automatic - Show name and message - Show name - Hide contents + नाम व संदेश दिखाएं + नाम दिखाएं + सामाग्री छुपाएं - Small - Normal - Large - Larger + छोटा + सामान्य + बड़ा + और बड़ा - No delay + कोइ विलम्ब नही Short - Medium - Long + मध्यम + बड़ा - 100KB - 200KB - 300KB (Recommended) - 600KB - 1000KB - 2000KB + 100 किलो बाइट + 200 किलो बाइट + 300 किलो बाइट(सुझाव) + 600 किलो बाइट + 1000 किलो बाइट + 2000 किलो बाइट No compression - Okay - Give me a moment + ठीक + एक क्षण दें On my way - Thanks - Sounds good - What\'s up? + शुक्रिया + सही है + नया क्या है Agreed - No + नहीं Love you - Sorry + मुझे खेद है LOL - That\'s okay + ठीक है diff --git a/presentation/src/main/res/values-hr/strings.xml b/presentation/src/main/res/values-hr/strings.xml index b581e84bfeac1a561ac9b138eb6b397c7aea01da..994087b6618a049e0e06a7ebf7b2074a9828f2b0 100644 --- a/presentation/src/main/res/values-hr/strings.xml +++ b/presentation/src/main/res/values-hr/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Izgled @@ -194,10 +202,6 @@ Skočni prozor za nove poruke Dodirnite za odbacivanje Dodirnite izvan skočnog prozora za zatvaranje - Blokiranje - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -209,10 +213,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Potvrde o dostavi Budite sigurni da su se poruke uspješno poslale + Signature Strip naglasci Uklonite naglaske sa znakova u odlaznim SMS porukama Mobile numbers only @@ -225,10 +231,37 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blokiranje - Vaši blokirani razgovori će se pojaviti ovdje - Deblokiraj - Želite li deblokirati ovaj razgovor? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Continue to %s and allow these numbers + O aplikaciji Verzija Autor @@ -237,9 +270,40 @@ Kontakt Licenca Autorska prava + Podržite razvoj, otključajte sve + Možete spasiti izgladnjelog programera za samo %s + + Doživotna nadogradnja za %1$s %2$s + + Otključajte + za %1$s %2$s + Hvala vam za podržavanje QKSMS! + Sada imate pristup svim QKSMS+ značajkama + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donirajte putem PayPala + Uskoro dolazi + Premium teme + Otključajte lijepe boje tema koje nisu dostupne na paleti materijalnog dizajna + Prilagođeni automatski emoji + Stvorite prilagođene prečace za automatski emoji + Sigurnosno kopiranje poruka + Automatski sigurnosno kopirajte svoje poruke. Nikad više nemojte brinuti zbog gubitka povijesti ako promijenite telefon ili ga izgubite + Zakazane poruke + Schedule messages to automatically be sent at a specific time and date + Odgođeno slanje + Pričekajte nekoliko sekundi prije slanja poruke + Automatski noćni način + Omogućite noćni način prema dobi dana + Napredno blokiranje + Blokirajte poruke koje sadrže ključne riječi ili obrasce + Automatsko prosljeđivanje + Automatski proslijedite poruke određenih pošiljatelja + Automatsko odgovaranje + Automatski odgovorite na poruke sa prilagođenim odgovorima +  Više + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Učitavanje… View more conversations - Mark read + Mark as read Call Delete Show more @@ -250,18 +314,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Odustani Izbriši Save Stop  Više Set - Unblock Poništi Kopirano Razgovor je arhiviran @@ -273,9 +338,10 @@ Poruka nije poslana Poruku %s se nije uspjela poslati - Onemogućeno - Uvijek uključen - Automatski + System + Disabled + Always on + Automatic Show name and message diff --git a/presentation/src/main/res/values-hu/strings.xml b/presentation/src/main/res/values-hu/strings.xml index 5b357697559ca7c588dc7e21226e1836a589d8cc..7053077527f9e647cc5e5b26fc04c67b6b79855c 100644 --- a/presentation/src/main/res/values-hu/strings.xml +++ b/presentation/src/main/res/values-hu/strings.xml @@ -1,7 +1,7 @@ + Küldés december 23-án Küldés időzítése Időzített üzenet Küldés most - Törlés + Copy text + Delete Megjelenés Általános @@ -185,7 +190,7 @@ Rendszer betűtípus visszaállítása Automatikus emoji Értesítések - Tap to customize + Érintsd meg a testreszabáshoz Műveletek Gomb 1 Gomb 2 @@ -198,15 +203,11 @@ Felugró új üzenetek Érintsd meg az eltüntetéshez Érintsd meg a felugró ablakon kívül a lezáráshoz - Blokkolás - - Should I Answer? - Automatikusan szűrheti az üzeneteket a nem kívánt számok alapján a \"Should I Answer\" alkalmazás használatával Késleltetett küldés - Swipe actions - Configure swipe actions for conversations - Right swipe - Left swipe + Elhúzásos funkciók + A beszélgetésekhez tartozó elhúzások beállítása + Jobbra húzás + Balra húzás MEGVÁLTOZTATÁS Nincs @@ -214,25 +215,52 @@ Törlés Hívás Megjelölés olvasottként + Megjelölés olvasatlanként Elküldés megerősítése Megerősíti, hogy az üzenetek sikeresen el lettek-e küldve + Aláírás Ékezetek törlése A kimenő SMS-üzenetek ékezetes karaktereinek átalakítása Csak mobilszámok Üzenet írásánál csak a mobilszámokat mutassa MMS-mellékletek automatikus tömörítése Üzenetek szinkronizálása - Üzenetek szinkronizálása a natív Android SMS-adatbázisban - A Message-ről + Üzenetek újra-szinkronizálása a natív Android SMS-adatbázissal + A QKSMS-ről %s verzió - Debug logging enabled - Debug logging disabled + Hibanaplózás engedélyezve + Hibanaplózás tiltva Nyomva tartás ideje (másodpercben) - Blokkolás - A letiltott beszélgetések itt jelennek meg - Tiltás feloldása - Szeretnéd feloldani ezt a beszélgetést? + Blocking + Üzenetek eldobása + Üzenetek eldobása a blokkolt küldőktől az elrejtésük helyett + Tiltott társalgások + Tiltáskezelő + QKSMS + A QKSMS beépített tiltási funkciója + Egy kényelmesen kezelhető helyen a hívások és üzenetek szűrése! A Community IQ™ megakadályozza a közösség által ismert spammerek üzeneteit + Automatikusan szűrheti az üzeneteket a nem kívánt számok alapján a \"Should I Answer\" alkalmazás használatával + Tiltott számok másolása + %s másolása a már tiltott számok helyére + Tiltott számok + A tiltott számok itt fognak megjelenni + Új szám tiltása + Üzenetek tiltása: + Telefonszám + Block + Tiltott üzenetek + A tiltott üzenetek itt fognak megjelenni + Block + Unblock + + %s és ennek a számnak a blokkolása + %s és ezeknek a számoknak a tiltása + + + %s és ennek a számnak az engedélyezése + %s és ezeknek a számoknak az engedélyezése + Névjegy Verzió Fejlesztői @@ -240,18 +268,48 @@ Változások listája Kapcsolat Licensz - Copyright - Loading… - View more conversations - Mark read - Call - Delete - Show more - Show less - Open conversation + Fejlesztés támogatása, minden funkció engedélyezése + Csak %s hogy megmenthess egy éhező fejlesztőt + + Életre szóló upgrade %1$s %2$s + + Minden funkció engedélyezése + adomány %1$s %2$s + Köszönjük, hogy támogatot a QKSMS-t! + MOst hozzáférsz minden QKSMS+ funkcióhoz + A QKSMS+ minden F-Droid felhasználónak ingyenes. Ha támogatni szeretnéd a fejlesztést az alábbi módon megteheted. + Támogatás PayPalon keresztül + Hamarosan + Prémium témák + Használj olyan csodaszép témákat amik nem érhetőek el a Material Design palettáján + Személyre szabott auto-emoji + Készíts személyre szabott emoji rövidítéseket + Üzenet biztonsági mentés + Az üzenetek automatikus mentése. Nem kell többé aggódnod, hogy elvesznek az üzeneteid ha telefont cserélsz + Időzített üzenetek + Az időzített üzenetek automatikusan a megadott időpontban kerülnek elküldésre + Késleltetett küldés + Küldés előtt várjon pár másodpercet + Automatikus éjjeli mód + Az éjjeli mód beálíltása napszaktól függően + Fejlett blokkolás + Megadott kulcsszavakat vagy szövegrészleteket tartalmazó üzenetek blokkolása + Automatikus továbbítás + Adott küldőktől jövő üzenetek automatikus továbbítása + Automatikus válasz + Előre megadott sablonok küldése bejövő üzenetekre + Tovább + A QKSMS folyamatos fejlesztés alatt áll és a vásárlásod a jövőbeni QKSMS+ funkciókat is tartalmazza! + Betöltés… + További társalgások megtekintése + Olvasottnak jelölés + Hívás + Törlés + Több megjelenítése + Kevesebb megjelenítése + Társalgás kinyitása Material HEX - Apply + Alkalmaz Nincs Megjelölés olvasottként @@ -259,16 +317,18 @@ Hívás Törlés - Cancel - Delete - Save - Stop - More + Igen + Continue + Mégsem + Törlés + Mentés + Megállítás + Továbbiak Beállítás - Blokk feloldás Visszavonás Átmásolva Archivált beszélgetés + Ehhez ki kell nyitnod a QKSMS+-t Új üzenet %s új üzenet @@ -276,8 +336,9 @@ Az üzenet küldése sikertelen Nem sikerült az üzenetetet %s címzettnek elküldeni + Rendszer Letiltva - Mindig bekapcsolva + Mindig bekapcsolt Automatikus @@ -308,14 +369,14 @@ Oké - Adjon egy percet + Adj egy percet Rajta vagyok Köszönöm Jónak tűnik Mi a helyzet? Elfogadva Nem - Szeretem + Szeretlek Sajnálom LOL Semmi baj diff --git a/presentation/src/main/res/values-in/strings.xml b/presentation/src/main/res/values-in/strings.xml index ba12c9241cf732df38d3934495607e31f12e8a76..b650a66f18a38191344d1b9add4d0d88c20f07c7 100644 --- a/presentation/src/main/res/values-in/strings.xml +++ b/presentation/src/main/res/values-in/strings.xml @@ -1,7 +1,7 @@ Mengirim pada tanggal 23 Desember Jadwalkan pesan Pesan terjadwal Kirim sekarang - Hapus + Copy text + Delete Tampilan Umum - QK Balas + Balasan cepat Tema Mode malam Mode malam hitam pekat @@ -179,7 +188,7 @@ Gunakan fon sistem Emoji otomatis Notifikasi - Sentuh untuk ubahsuai + Ketuk untuk ubahsuai Tindakan Tombol 1 Tombol 2 @@ -188,19 +197,15 @@ Getar Suara Tidak ada - QK Balas + Balasan cepat Popup untuk pesan baru - Sentuh untuk mengabaikan - Sentuh di luar popup untuk menutupnya - Pemblokiran - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app - Delayed sending - Swipe actions - Configure swipe actions for conversations - Right swipe - Left swipe + Ketuk untuk mengabaikan + Ketuk di luar popup untuk menutupnya + Pengiriman tertunda + Tindakan geser + Atur tindakan geser untuk percakapan + Geser ke kanan + Geser ke kiri UBAH Tidak ada @@ -208,25 +213,50 @@ Hapus Panggil Tandai dibaca + Tandai belum dibaca Konfirmasi pengiriman Mengonfirmasi bahwa pesan telah berhasil dikirim - Strip accents - Remove accents from characters in outgoing SMS messages + Tanda tangan + Hapus aksen + Membuang aksen dari karakter di dalam pesan SMS keluar Hanya nomor seluler Saat menulis pesan, hanya tampilkan nomor seluler Otomatis kompres lampiran MMS Sinkronisasi pesan - Sinkron ulang pesan anda dengan basis data apl SMS Android bawaan - Tentang Message + Sinkronisasi ulang pesan anda dengan basis data aplikasi SMS Android bawaan + Tentang QKSMS Versi %s Log awakutu diaktifkan Log awakutu dinonaktifkan Masukkan durasi (detik) - Pemblokiran - Percakapan yang anda blokir akan muncul di sini - Buka blokir - Apakah anda ingin membuka blokir percakapan ini? + Pemblokiran + Tolak pesan + Tolak pesan masuk dari pengirim yang diblokir + Percakapan diblokir + Blocking Manager + QKSMS + Fitur pemblokiran pesan di dalam QKSMS + Otomatis menyaring panggilan dan pesan di dalam satu tempat! Community IQ™ memungkinkan anda memblokir pesan yang tidak diinginkan dari daftar spammer + Secara otomatis menyaring pesan dari nomor yang tidak diinginkan menggunakan aplikasi \"Should I Answer\" + Salin nomor yang diblokir + Lanjutkan ke %s dan salin semua nomor yang diblokir + Nomor yang diblokir + Nomor yang anda blokir akan ditampilkan di sini + Blokir nomor baru + Blokir pesan sms dari + Nomor telepon + Blokir + Pesan yang diblokir + Pesan yang anda blokir akan ditampilkan di sini + Blokir + Buka blokir + + Lanjutkan ke %s dan blokir nomor ini + + + Lanjutkan ke %s dan izinkan nomor ini + Tentang Versi Pengembang @@ -235,6 +265,37 @@ Kontak Lisensi Hak cipta + Dukung pengembangan, dan buka semua fitur + Anda bisa membantu pengembang hanya dengan %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Terima kasih telah mendukung QKSMS! + Anda sekarang bisa mengakses semua fitur QKSMS+ + QKSMS+ selalu gratis untuk pengguna F-Droid! Jika anda ingin mendukung pengembangan, jangan ragu untuk donasi. + Donasi via PayPal + Segera hadir + Tema premium + Unlock beautiful theme colors not available on the Material Design palette + Emoji otomatis khusus + Buat pintasan emoji otomatis khusus + Cadangan pesan + Cadangkan otomatis pesan anda. Tidak perlu khawatir kehilangan riwayat pesan jika anda mengganti telepon atau telepon hilang + Pesan terjadwal + Menjadwalkan pesan untuk secara otomatis dikirim pada waktu dan tanggal tertentu + Pengiriman tertunda + Tunggu beberapa detik sebelum mengirim pesan + Mode malam otomatis + Aktifkan mode malam berdasarkan waktu + Pemblokiran lanjutan + Memblokir pesan yang mengandung kata tertentu atau sesuai dengan pola + Penerusan otomatis + Secara otomatis meneruskan pesan dari pengirim tertentu + Balasan otomatis + Secara otomatis membalas pesan masuk dengan pesan balasan khusus + Lainnya + QKSMS sedang dalam pengembangan aktif, dan pembelian anda juga mencakup semua fitur QKSMS+ yang akan datang! Memuat… Lihat percakapan lainnya Tandai dibaca @@ -253,13 +314,14 @@ Panggil Hapus + Ya + Lanjutkan Batal Hapus Simpan Setop Lainnya Set - Buka blokir Urungkan Disalin Percakapan diarsipkan @@ -267,10 +329,11 @@ %s pesan baru Pesan tidak terkirim - Pesan ke %s gagal untuk dikirim + Pesan ke %s gagal dikirim + Sistem Nonaktif - Selalu nyala + Selalu aktif Otomatis diff --git a/presentation/src/main/res/values-it/strings.xml b/presentation/src/main/res/values-it/strings.xml index ee4d34a27e9f995527a3aceb7f819f122d65b6b0..80f17e0ec43f21cf5be54395f6f120932d478f0f 100644 --- a/presentation/src/main/res/values-it/strings.xml +++ b/presentation/src/main/res/values-it/strings.xml @@ -1,7 +1,7 @@ Invio il 23 dicembre  Schedula un messaggio Messaggio pianificato Invia ora - Elimina + Copia testo + Cancella Aspetto Generale @@ -196,10 +204,6 @@ Popup per i nuovi messaggi Tocca per archiviare Tocca al di fuori del popup per chiuderlo - Blocco - - Dovrei rispondere? - Filtrare automaticamente i messaggi provenienti da numeri indesiderati utilizzando l\'app \"Dovrei rispondere?\" Invio posticipato Azioni di scorrimento Configura le azioni di scorrimento per le conversazioni @@ -212,9 +216,11 @@ Elimina Chiama Segna come letto + Segna come non letto Conferme di recapito Conferma che i messaggi siano stati inviati con successo + Firma Elimina gli accenti Rimuovi gli accenti dai caratteri negli SMS in uscita Solo numeri mobili @@ -227,10 +233,35 @@ Registrazione del debug attivata Registrazione del debug disattivata Inserisci la durata (in secondi) - Mittenti indesiderati - Le tue conversazioni bloccate appariranno qui - Ripristina mittente - Vuoi ripristinare questa conversazione? + Bloccare + Elimina messaggi + Elimina i messaggi in arrivo dai mittenti bloccati invece di nasconderli + Conversazioni bloccate + Gestore blocchi + QKSMS + Funzionalità di blocco integrata in QKSMS + Filtra automaticamente le tue chiamate e i tuoi messaggi in un unico comodo posto! Community IQ™ ti consente di prevenire messaggi indesiderati dagli spammer noti alla comunità + Filtrare automaticamente i messaggi provenienti da numeri indesiderati utilizzando l\'app \"Dovrei rispondere?\" + Copia i numeri bloccati + Continua a copiare %s sui tuoi numeri bloccati esistenti + Numeri bloccati + I tuoi numeri bloccati appariranno qui + Blocca un nuovo numero + Blocca i testi da + Numero di telefono + Blocca + Messaggi bloccati + I tuoi messaggi bloccati appariranno qui + Blocca + Sblocca + + Continua a %s e blocca questo numero + Continua a %s e blocca questi numeri + + + Continua a %s e permetti questo numero + Continua a %s e consenti questi numeri + Informazioni Versione Sviluppatore @@ -239,6 +270,37 @@ Contatto Licenza Copyright + Supporta lo sviluppo, sblocca tutte le funzioni + Salva uno sviluppatore affamato per soli %s + + Upgrade a vita per %1$s %2$s + + Sblocca & dona per %1$s %2$s + Grazie per il supporto a QKSMS! + È ora possibile accedere a tutte le funzioni QKSMS+ + QKSMS+ è gratuito per gli utenti di F-Droid! Se desideri sostenere il progetto, una tua donazione sarebbe molto apprezzata. + Fai una donazione tramite PayPal + Prossimamente + Temi Premium + Sblocca splendidi colori non disponibili nella tavolozza del Material Design + Personalizza gli emoji automatici + Crea scorciatoie personalizzate per gli emoji automatici + Backup dei messaggi + Effettua automaticamente il backup dei tuoi messaggi. Non dovrai più preoccuparti di perdere la cronologia quando cambi il telefono o lo perdi + Messaggi pianificati + Messaggi pianificati da inviare automaticamente ad una specifica data e ora + Invio posticipato + Attendere alcuni secondi prima di inviare il tuo messaggio + Modalità notturna automatica + Attiva la modalità notturna in base all\'ora del giorno + Blocco avanzato + Bloccare i messaggi che contengono parole chiave o corrispondono a modelli + Inoltro automatico + Inoltrare automaticamente i messaggi da determinati mittenti + Risposta automatica + Rispondere automaticamente ai messaggi in arrivo con una risposta preimpostata + Altro + QKSMS è sotto sviluppo attivo, e l\'acquisto includerà tutte le caratteristiche future di QKSMS+! Caricamento… Mostra altre conversazioni Segna come letto @@ -257,13 +319,14 @@ Chiama Elimina + + Continua Annulla Elimina Salva Stop Altro Set - Ripristina Annulla Copiato Conversazione archiviata @@ -274,9 +337,10 @@ Messaggi non inviati Il messaggio a %s non è stato inviato - Disabilitata - Sempre attiva - Automatica + Sistema + Disabilitato + Sempre acceso + Automatico Mostra il nome e il messaggio diff --git a/presentation/src/main/res/values-iw/strings.xml b/presentation/src/main/res/values-iw/strings.xml index 8baa3c0e116f84c99d5a09f3b01820b6d14376dc..2bb27656bfc75ed93bf819da70bb9f05775910e3 100644 --- a/presentation/src/main/res/values-iw/strings.xml +++ b/presentation/src/main/res/values-iw/strings.xml @@ -1,7 +1,7 @@ לשליחה ב־23 בדצמבר תזמון הודעה הודעה מתוזמנת לשלוח כע + העתקת טקסט מחיקה מראה @@ -196,10 +204,6 @@ חלונית קופצת להודעות חדשות יש לגעת כדי להתעלם יש לגעת מחוץ לחלונית הקופצת כדי לסגור אותה - חסימה - - Should I Answer?‎ (שאענה?) - לסנן אוטומטית הודעות ממספרים בלתי רצויים על ידי שימוש ביישומון „Should I Answer” (שאענה) שליחה בעיכוב פעולות גרירה להגדיר פעולות גרירה לדיונים @@ -212,9 +216,11 @@ מחיקה התקשרות סימון כנקרא + סימון כלא נקרא אישור מסירה אישור שההודעות נשלחו בהצלחה + חתימה סינון סימנים דיאקריטיים הסרת סימנים דיאקריטיים מתווים במסרונים יוצאים מספרי ניידים בלבד @@ -227,10 +233,39 @@ תיעוד ניפוי שגיאות פעיל תיעוד ניפוי שגיאות כבוי משך הזמן (בשניות) - בחסימה - ההתכתבויות שחסמת תופענה כאן - שחרור חסימה - לשחרר את החסימה של ההתכתבות הזאת? + חסימה + השמטת הודעות + להשמיט הודעות נכנסות משולחים חסומים במקום להסתיר אותן + דיונים חסומים + מנהל חסימות + QKSMS + תכונת חסימה מובנית בתוך QKSMS + לסנן אוטומטית את השיחות וההודעות שלך במקום אחיד ונוח! Community IQ™‎ מאפשר לך להימנע מהודעות בלתי רצויות בזכות דיווחים על מפיצי ספאם מצד הקהילה + לסנן אוטומטית הודעות ממספרים בלתי רצויים על ידי שימוש ביישומון „Should I Answer” (שאענה) + העתקת מספרים חסומים + להמשיך אל %s כדי להעתיק לכאן את המספרים שחסומים אצלך כרגע + מספרים חסומים + המספרים שחסמת יופיעו כאן + חסימת מספר חדש + חסימת הודעות מאת + מספר טלפון + חסימה + הודעות חסומות + ההודעות שנחסמו תופענה להלן + חסימה + שחרור + + להמשיך אל %s ולחסום את המספר הזה + להמשיך אל %s ולחסום את המספרים האלו + להמשיך אל %s ולחסום את המספרים האלו + להמשיך אל %s ולחסום את המספרים האלו + + + להמשיך אל %s ולאפשר את המספר הזה + להמשיך אל %s ולאפשר את המספרים האלו + להמשיך אל %s ולאפשר את המספרים האלו + להמשיך אל %s ולאפשר את המספרים האלו + על אודות גרסה מפתח @@ -239,6 +274,37 @@ איש קשר רישיון זכויות יוצרים + לתמיכה בפיתוח, ניתן לשחרר את הכול + באפשרותך להציל מפתח גווע ברעב עבור %s בלבד + + שדרוג לכל החיים תמורת %1$s %2$s + + שחרור ותרומה תמורת %1$s %2$s + תודה לך על תמיכתך ב־QKSMS! + כעת יש לך גישה לכל התכונות של QKSMS+‎ + התכנית QKSMS+‎ מוגשת בחינם למשתמשי F-Droid! אם מעניין אותך לתמוך בפיתוח, אפשר להגיש תרומה. + תרומה באמצעות PayPal + בקרוב + ערכות עיצוב פרימיום + חשיפת צבעי ערכות עיצוב שאינן זמינות בערת הצבעים של עיצובי Material + אימוג׳י אוטומטי בהתאמה אישית + יצירת קיצורי דרך מותאמים אישית לאימוג׳י + גיבוי הודעות + גיבוי ההודעות שלך אוטומטית. לעולם לא תטריד אותך האפשרות שההיסטוריה שלך תלך לאיבוד עקב החלפת טלפונים או אבדן טלפון + הודעות מתוזמנות + תזמון הודעות לשליחה אוטומטית במועדים שנקבעו מראש + שליחה מושהית + להמתין מספר שניות בטרם שליחת ההודעה + מצב לילה אוטומטי + הפעלת מצב לילה בהתאם לשעה ביום + חסימה מתקדמת + חסימת הודעות שמכילות מילות מפתח או שהן תואמות לדפוס מסוים + העברה אוטומטית + העברה אוטומטית של הודעות משולחים מסוימים + תגובה אוטומטית + להגיב אוטומטית להודעות נכנסות עם תשובה שנכתבה מראש + עוד + הפיתוח של QKSMS מתקיים בימים אלו והרכישה שלך תכלול את כל התכונות העתידיות של QKSMS+‎! בטעינה… הצגת התכתבויות נוספות לסמן כנקראה @@ -257,13 +323,14 @@ התקשרות מחיקה + כן + המשך ביטול מחיקה שמירה עצירה עוד הגדרה - שחרור חסימה ביטול הועתק דיון בארכיון @@ -276,8 +343,9 @@ הודעה לא נשלחה שליחת ההודעה אל %s נכשלה + מערכת מושבת - תמיד פעיל + תמיד מופעל אוטומטי diff --git a/presentation/src/main/res/values-ja/strings.xml b/presentation/src/main/res/values-ja/strings.xml index c4e70af879dc96ab6854d1c7e7c330ec323b7b4f..4157d0cc84842307097f66129cf9249ee677dbaf 100644 --- a/presentation/src/main/res/values-ja/strings.xml +++ b/presentation/src/main/res/values-ja/strings.xml @@ -1,7 +1,7 @@ 12月23日に発送 メッセージをスケジュールする スケジュールされたメッセージ 今すぐ送る + テキストをコピー 削除 外観 @@ -190,10 +198,6 @@ 新しいメッセージのポップアップ タップして閉じる ポップアップの外側をタップすると閉じます - ブロッキング - - Should I Answer? - \"Should I Answer\" アプリを使用して、迷惑な番号からのメッセージを自動的にフィルタします 遅延送信 スワイプアクション 会話のスワイプアクションを設定する @@ -206,9 +210,11 @@ 削除 電話 既読にする + 未読にする 配信確認 メッセージが正常に送信されたことを確認します + 署名 アクセントの除去 送信する SMS メッセージの文字からアクセントを削除します 携帯電話番号のみ @@ -221,10 +227,33 @@ デバッグログが有効 デバッグログを無効にする 期間を入力してください (秒) - ブロック - ブロックした会話がここに表示されます - ブロックを解除 - この会話のブロックを解除しますか? + ブロック + メッセージをドロップ + ブロックした送信者からの受信メッセージを非表示にする代わりにドロップします + ブロックした会話 + ブロッキングマネージャー + QKSMS + QKSMS の内蔵ブロック機能 + 一か所で便利に通話とメッセージを自動的にフィルタリングします! Community IQ™を使用すると、コミュニティで知られているスパマーからの不要なメッセージを防ぐことができます + \"Should I Answer\" アプリを使用して、迷惑な番号からのメッセージを自動的にフィルタします + ブロックした番号をコピー + 既存のブロックした番号のコピーを %s まで続けます + ブロックした番号 + ブロックした番号がここに表示されます + 新しい番号をブロック + 次からのテキストをブロック + 電話番号 + ブロック + ブロックしたメッセージ + ブロックしたメッセージがここに表示されます + ブロック + ブロック解除 + + %s まで続けてこの番号をブロック + + + %s まで続けてこの番号を許可 + このアプリについて バージョン 開発者 @@ -233,6 +262,37 @@ お問い合わせ ライセンス 著作権 + 開発をサポートして、すべてのロックを解除します + ちょうど %s 人の飢えている開発者を助けることができます + + %1$s %2$s のライフタイムのアップグレード + + %1$s %2$s のロック解除と寄付 + QKSMS を支援いただきありがとうございます! + QKSMS+ のすべての機能にアクセスできます + QKSMS+ は F-Droid ユーザーは無料です! 開発をサポートしたい場合は、気軽に寄付をしてください。 + PayPal で寄付する + 近日登場 + プレミアムテーマ + マテリアルデザインパレットで利用できない美しいテーマカラーのロックを解除します + カスタム自動絵文字 + カスタム自動絵文字のショートカットを作成します + メッセージのバックアップ + メッセージを自動的にバックアップします。携帯電話を変更したり、お使いの携帯電話をなくしても、もう二度と履歴がなくなる心配はいりません。 + スケジュールされたメッセージ + 特定の日時に自動的に送信されるようにメッセージをスケジュールする + 送信を遅らせる + メッセージを送信する前に、数秒間待ちます + 自動夜間モード + 時間に基づいて夜間モードを有効にします + 高度なブロック + キーワードを含む、またはパターンに一致するメッセージをブロックします + 自動転送 + 特定の送信者からのメッセージを自動的に転送します + 自動応答 + あらかじめ設定された応答で、受信したメッセージに自動的に返信します + さらに + QKMSは活発に開発中です、そして、あなたの購入はすべての将来のQKMS+機能を含みます! ロード中... さらに会話を表示 既読にする @@ -251,13 +311,14 @@ 電話 削除 + はい + 続行 キャンセル 削除 保存する やめる さらに 据える - ブロックを解除 元に戻す コピーしました 会話をアーカイブしました @@ -267,8 +328,9 @@ メッセージは送信されませんでした %s へのメッセージの送信に失敗しました - 無効です - 常にオン + システム + 無効 + 常にON 自動 diff --git a/presentation/src/main/res/values-ko/strings.xml b/presentation/src/main/res/values-ko/strings.xml index 79489e916f5e8e321d46b6f70eba4951141a6e5a..3e31cfcf6b2690e6d497078b96f731bc4725abb4 100644 --- a/presentation/src/main/res/values-ko/strings.xml +++ b/presentation/src/main/res/values-ko/strings.xml @@ -1,7 +1,7 @@ 12 월 23 일에 보냅니다. 메시지 예약 전송하기 예약된 메시지 지금 보내기 - 삭제 + Copy text + Delete 외관 일반 설정 @@ -192,10 +200,6 @@ 새로운 메시지에 대한 팝업 탭해서 닫기 아무곳을 터치하면 팝업이 닫힙니다. - 차단 - - Should I Answer 앱 - Should I Answer 앱을 이용해 필요없는 번호에서 보낸 메시지를 자동으로 필터링하기 전송 지연 쓸어넘기기 설정 대화를 쓸어넘겼을 때 할 작업 @@ -208,9 +212,11 @@ 삭제 전화 읽음으로 표시 + 읽음으로 표시 전송 확인 전송 성공을 확인합니다 + Signature 스트립 악센트 SMS를 주고받을 때, 대문자 없애기 전화번호만 보이기 @@ -223,10 +229,33 @@ 디버그 로깅 설정됨 디버그 로깅 해제됨 기간 (초)을 입력 - 차단 - 차단된 대화가 여기에 나타납니다. - 차단 해제 - 이 대화를 차단할까요? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Should I Answer 앱을 이용해 필요없는 번호에서 보낸 메시지를 자동으로 필터링하기 + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block these numbers + + + Continue to %s and allow these numbers + 정보 버전 개발자 @@ -235,6 +264,37 @@ 연락처 라이선스 저작권 + 개발을 지원하고 모든 기능을 해제하세요. + 배고픈 개발자를 위해 %s 만큼만으로도 구할 수 있습니다. + + %1$s %2$s 으로 평생 업그레이드 + + %1$s %2$s 으로 프리미엄 기능 잠금 해제 및 기부 + QKSMS를 지원해 주셔서 감사합니다! + 이제 모든 QKSMS+ 기능에 액세스할 수 있습니다 + QKSMS+는 F-Droid 사용자들에게 무료입니다! 만약 여러분이 개발을 지원하고 싶으시다면, 기부해 주시기 바랍니다. + 페이팔을 통해 기부 + 출시 예정 + 프리미엄 테마 + Material Design 팔레트에선 볼 수 없던 아름다운 테마를 잠금해제 하세요! + 사용자 지정 이모티콘 + 사용자 지정 이모티콘 바로 가기 만들기 + 메시지 백업 + 자동으로 메시지를 백업해서 휴대폰을 바꾸거나 잊어버려도 문제없죠! + 예약 전송 메시지 + 특정한 시간에 메시지가 보내지도록 메시지 전송을 예약하세요 + 지연 전송 + 지연 전송이라는 멋진 기능이 있답니다! + 자동 나이트 모드 + 시간에 따라 나이트 모드 설정하기 + 고급 차단 기능 + 특정 키워드 및 패턴이 있을 때 차단합니다 + 자동 전달 + 자동으로 특정한 보낸 사람으로부터 메시지를 전달합니다 + 자동 응답 + 미리 설정된 응답으로 받은 메시지에 자동으로 응답합니다 + 더보기 + QKSMS는 개발이 활성화되어 있고 여러분의 구입이 모든 미래의 QKSMS+ 기능들이 포함될 것입니다! 불러오는 중… 더 많은 대화 보기 읽음으로 표시 @@ -253,13 +313,14 @@ 전화하기 삭제하기 + Yes + Continue 취소 삭제 저장 중지 더보기 설정 - 차단 해제 되돌리기 복사됨 보관된 대화 @@ -271,9 +332,10 @@ 메시지 전송 실패 %s 에게 보낼 메시지 전송이 실패했습니다 - 비활성화됨 - 항상 표시 - 자동 + System + Disabled + Always on + Automatic 이름 및 메시지 표시 diff --git a/presentation/src/main/res/values-lt/strings.xml b/presentation/src/main/res/values-lt/strings.xml index 9b6240d3aa7f5bdc68fa8c311cf9fde9fe092072..3a36dd3fcf8a186d3cd450f803b932a5da7999df 100644 --- a/presentation/src/main/res/values-lt/strings.xml +++ b/presentation/src/main/res/values-lt/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Išvaizda @@ -196,10 +204,6 @@ Iššokantis langas naujiems pranešimams Spustelėkite, kad atmestumėte Spustelėkite bet kur už iššokamo lango, kad išjungtumėte - Blokavimas - - Ar reikia atsakyti? - Automatiškai filtruoti žinutes nuo nežinomų numerių su „Ar reikia atsakyti“ programėle Delayed sending Swipe actions Configure swipe actions for conversations @@ -211,10 +215,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Žinučių pristatymo patvirtinimai Patvirtinti, kad pranešimai buvo sėkmingai išsiųsti + Signature Trinti akcentus Ištrinti jūsų SMS žinučių simbolių akcentus Mobile numbers only @@ -227,10 +233,39 @@ Debug logging enabled Debug logging disabled Įvesti trukmę (sek.) - Blokavimas - Užblokuoti pokalbiai bus rodomi čia - Atblokuoti - Ar norite atblokuoti šį pokalbį? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatiškai filtruoti žinutes nuo nežinomų numerių su „Ar reikia atsakyti“ programėle + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + Continue to %s and block these numbers + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Continue to %s and allow these numbers + Continue to %s and allow these numbers + Apie Versija Kūrėjas @@ -239,6 +274,37 @@ Kontaktai Licencija Autorinės teisės + Remti programos vystymąsi, atrakinti viską + Galite išsaugoti badaujantį programėlės kūrėją tik už %s + + Amžininis programėlės funkcijų atrakinimas %1$s %2$s + + Atrakinti + aukoti %1$s %2$s + Ačiū už paramą! + Dabar jūs turite prieigą prie visoms QKSMS+ funkcijoms + QKSMS+ yra nemokama F-Droid vartotojams! Taip pat galite paremti programos kūrėją aukodami. + Paaukoti per PayPal + Jau greitai + Premium temos + Atrakink kitas spalvas, kurios nėra „Material“ paletėje + Individualizuoti auto-emoji + Sukurkite pasirinktinius automatinių emoji sparčiuosius klavišus + Žinučių atsarginis kopijavimas + Automatinis atsarginių pranešimų kopijavimas. Niekada nerimaukite, kad prarasite žinutes, jei pakeisite telefonus arba pamesite telefoną + Planuotos žinutės + Schedule messages to automatically be sent at a specific time and date + Atidėtas siuntimas + Palaukti kelias sekundes prieš išsiųntant pranešimą + Automatinė nakties tema + Įjungti naktinį režimą pagal dienos laiką + Sudėtingesnis blokavimas + Blokuoti žinutes pagal raktinius žodžius arba šabloninę struktūrą + Automatiškai persiųsti + Automatiškai persiųsti žinutes nuo tam tikrų siuntėjų + Automatiškai atsakyti + Automatiškai atsakyti į žinutes su tam tikru atsaku + Daugiau + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Kraunama… Žiūrėti daugiau pokalbių Žymėti skaitytą @@ -252,18 +318,19 @@ Pritaikyti None - Mark read + Mark as read Reply Call Delete + Yes + Continue Atšaukti Ištrinti Save Stop Daugiau Padaryti - Atblokuoti Anuliuoti Nukopijuota Archyvuotas pokalbis @@ -276,9 +343,10 @@ Žinutė neišsiųsta Žinutę, skirta %s, išsiųsti nepavyko - Išjungtas - Visadas įjungtas - Automatinis + System + Disabled + Always on + Automatic Rodyti vardą ir žinutę diff --git a/presentation/src/main/res/values-nb/strings.xml b/presentation/src/main/res/values-nb/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..26b6ffd4efba49700b4e5ef290a2ed3f51cb0727 --- /dev/null +++ b/presentation/src/main/res/values-nb/strings.xml @@ -0,0 +1,384 @@ + + + + + Ny samtale + Skriv + Snarvei deaktivert + Arkivert + Innstillinger + Varsler + Drakt + Søk i innboks … + Skriv navn eller nummer + Hopp over + Fortsett + Ring + Detaljer + Lagre til galleri + Åpne navigasjonsskuff + %d valgt + Tøm + Arkiv + Fjern fra arkiv + Slett + Add to contacts + Fest øverst + Løsne + Marker som lest + Marker som ulest + Blokker + Synkroniserer meldinger… + Du: %s + Draft: %s + Resultater i meldinger + %d meldinger + Samtalene dine vises her + Ingen treff + Arkiverte samtaler vises her + Start ny samtale + Elsk teksting igjen + Gjør QKSMS til standard SMS-app + Endre + Tillatelse kreves + QKSMS trenger tillatelse til å sende og vise tekstmeldinger + QKSMS trenger tillatelse til å lese kontaktene dine + Tillat + Innboks + Arkivert + Planlagt + Blokkert + Mer + Innstillinger + Hjelp og tilbakemelding + Inviter venner + Aktiver fantastiske nye funksjoner og støtt utviklingen + Liker du QKSMS? + Del litt kjærlighet og vurder oss på Google Play! + Ok! + Avvis + Slett + + Er du sikker på at du vil slette samtalen? + Er du sikker på at du vil slette %d samtaler? + + + Kopier tekst + Videresend + Slett + + %d valgt + %1$d av %2$d funn + Send som gruppemelding + Mottakere og svar er synlig for alle + Nå starter samtalen. Si noe hyggelig! + Kontakt + Planlagt for + Valgt tid må være i fremtid. + Du må ha QKSMS+ for å kunne bruke planlagte meldinger + Lagt til planlagte meldinger + Skriv en melding + Kopier tekst + Videresend + Slett + Forrige + Neste + Tøm + Meldingsinformasjon + Type: %s + Fra: %s + Til: %s + Emne: %s + Prioritet: %s + Størrelse: %s + Sendt: %s + Mottatt: %s + Levert: %s + Feilkode: %d + Legg til vedlegg + Legg til bilde + Ta bilde + Planlegg melding + Legg til kontakt + Feil ved åpning av kontakt + %s er valgt, velg annet SIM-kort + Send melding + Sender … + Levert %s + Feil ved sending. Trykk for å prøve igjen + Detaljer + Tittel på samtalen + Varsler + Drakt + Arkiv + Fjern fra arkiv + Blokker + Ta bort blokkering + Slett samtale + Media kunne ikke lastes + Lagret i galleriet + Sikret + Sikkerhetskopierer meldinger + Gjenoppretter fra sikkerhetskopi + Siste sikkerhetskopi + Henter … + Aldri + Gjenopprett + Velg en sikkerhetskopi + Vennligst aktiver QKSMS+ for å bruke sikkerhetskopiering og gjenoppretting + Sikkerhetskopiering pågår … + Gjenoppretting pågår … + Gjenopprett fra sikkerhetskopi + Er du sikker på at du vil gjenopprette meldinger fra denne sikkerhetskopien? + Avbryt gjenoppretting + Meldinger som allerede er gjenopprettet vil forbli på din enhet + Sikkerhetskopier + Fant ingen sikkerhetskopier + + %d melding + %d meldinger + + Per idag er bare mulig å sikkerhetskopiere SMS. MMS og planlagte sikkerhetskopier kommer snart. + Sikkerhetskopier nå + Ser gjennom sikkerhetskopien … + %d/%d meldinger + Lagrer sikkerhetskopi … + Synkroniserer meldinger … + Ferdig! + Sikkerhetskopiering og gjenoppretting + Planlagt + Send automatisk en melding på det tidspunktet du ønsker + Hei! Når var fødselsdagen din igjen? + Den er på 23. desember + Gratulerer med dagen! For en god venn jeg er som husker på bursdagen din + + Sender den 23. desember + Planlegg en melding + Planlagt melding + + Send nå + Copy text + Delete + + Utseende + Generelt + Raskt svar + Drakt + Nattmodus + Ren svart nattmodus + Starttid + Sluttidspunkt + Skriftstørrelse + Bruk systemskrift + Automatisk emoji + Varsler + Trykk for å tilpasse + Handlinger + Knapp 1 + Knapp 2 + Knapp 3 + Forhåndsvisninger av varsler + Vibrasjon + Lyd + Ingen + Raskt svar + Sprettopp-vindu ved nye meldinger + Trykk for å forkaste + Trykk hvor som helst utenfor sprettopp-vinduet for å lukke det + Forsinket sending + Sveipe-handlinger + Still inn sveipe-handlinger for samtaler + Høyre-sveip + Venstre-sveip + Endring + + Ingen + Arkivér + Slett + Ring + Marker som lest + Marker som ulest + + Leveringsbekreftelse + Bekreft at melding ble sendt + Signatur + Ta bort akutt-tegn + Ta bort akutt-tegn fra utgående meldinger + Kun mobilnummre + Vis kun mobilnummre når du skriver ny melding + Komprimer MMS-vedlegg + Synkroniser meldinger + Resynkroniser meldinger med Androids interne meldingslager + Om QKSMS + Versjon %s + Feillogg aktivert + Feillogg deaktivert + Legg inn forsinkelse (i sekunder) + Blocking + Forkast meldinger + Forkast meldinger fra blokkerte sendere istedenfor å skjule dem + Blokkerte samtaler + Blokkeringer + QKSMS + Innebygd blokkeringsfunksjon i QKSMS + Filtrer anrop og meldinger automatisk. Fellesskapets intelligens™ hjelper med å forhindre uønskede henvendelser fra plageånder + Filtrer meldinger fra ukjente numre automatisk med \"Skal jeg svare\"-appen + Kopier blokkerte numre + Fortsett med %s og kopier eksisterende blokkerte numre + Blokkerte numre + Blokkerte numre vil vises her + Blokker et nytt nummer + Blokker meldinger fra + Telefonnummer + Block + Blokkerte meldinger + Blokkerte meldinger vil vises her + Block + Unblock + + Fortsett til %s og blokker nummeret + Fortsett til %s og blokker numrene + + + Fortsett til %s og tillat nummeret + Fortsett til %s og tillat numrene + + Om + Versjon + Utvikler + Kildekode + Endringslogg + Kontakt + Lisens + Opphavsrett + Støtt utviklingen, aktiver alle funksjoner + Du kan redde en sultende programmerer for bare %s + + Oppdateringer på livstid for %1$s %2$s + + Aktiver + donér for %1$s %2$s + Takk for at du støtter QKSMS! + Du har nå tilgang til alle funksjoner i QKSMS+ + QKSMS+ er gratis for F-Droid-brukere. Om du ønsker å støtte utviklingen, kan du gjerne donere. + Donér med PayPal + Kommer snart + Premium drakter + Aktiver vakre draktfarger som ikke er tilgjengelig i Material-design-paletten + Egne auto-emoji + Lag egne auto-emoji snarveier + Sikkerhetskopi av meldinger + Automatisk sikkerhetskopiering av dine meldinger. Slutt på bekymringer om å miste dine meldinger når du bytter telefon eller mister den + Planlagte meldinger + Planlegg at meldinger sendes automatisk ved en tid og dato + Forsinket sending + Vent noen sekunder før meldingen sendes + Automatisk nattmodus + Aktiver nattmodus basert på tid på døgnet + Avansert blokkering + Blokker meldinger som inneholder visse stikkord eller mønstre + Auto-videresend + Videresend meldinger fra gitte avsendere automatisk + Auto-svar + Svar på innkommende meldinger automatisk med et forhåndsdefinert svar + Mer + QKSMS er i stadig utvikling og ditt kjøp inkluderer alle fremtidige QKSMS+ funksjoner! + Laster inn … + Last inn flere samtaler + Marker som lest + Ring + Slett + Vis mer + Vis mindre + Åpne samtale + Material + HEX + Bruk + + Ingen + Marker som lest + Svar + Ring + Slett + + Ja + Continue + Avbryt + Slett + Lagre + Avbryt + Mer + Angi + Angre + Kopiert + Arkiverte samtaler + Du må aktivere QKSMS+ for å bruke dette + + Ny melding + %s nye meldinger + + Meldingen ble ikke sendt + Meldingen til %s kunne ikke sendes + + System + Deaktivert + Aktivert + Automatisk + + + Vis navn og melding + Vis navn + Skjul innhold + + + Liten + Standard + Stor + Større + + + Ingen forsinkelse + Kort + Middels + Lang + + + 100 KB + 200 KB + 300 KB (anbefalt) + 600 KB + 1000 KB + 2000 KB + Ingen komprimering + + + Ok + Vent litt + Er på vei + Takk + Høres bra ut + Skjer\'a? + I orden + Nei + Glad i deg + Beklager + LOL + Det er greit + + diff --git a/presentation/src/main/res/values-ne/strings.xml b/presentation/src/main/res/values-ne/strings.xml index 1cf90b769a68f8de7d95a534bbdaba2aa29e8014..571b402936ee1bbdac04dd28d6880f9253897add 100644 --- a/presentation/src/main/res/values-ne/strings.xml +++ b/presentation/src/main/res/values-ne/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Appearance @@ -192,10 +200,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -207,10 +211,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -223,10 +229,35 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About Version Developer @@ -235,9 +266,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -248,18 +310,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -270,6 +333,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values/donottranslate.xml b/presentation/src/main/res/values-night-v23/themes.xml similarity index 72% rename from presentation/src/main/res/values/donottranslate.xml rename to presentation/src/main/res/values-night-v23/themes.xml index 0bbc7099017a374be8c76d2d1b569261ff45dafc..3adbc7c011078649b640b9a90ac4f4a3f522c016 100644 --- a/presentation/src/main/res/values/donottranslate.xml +++ b/presentation/src/main/res/values-night-v23/themes.xml @@ -1,6 +1,6 @@ - https://gitlab.e.foundation/e/apps/message - GNU General Public License v3.0 - © 2014–2019 + + - \ No newline at end of file + diff --git a/presentation/src/main/res/values-night-v27/themes.xml b/presentation/src/main/res/values-night-v27/themes.xml new file mode 100644 index 0000000000000000000000000000000000000000..b9c409bd88d5322aa5fed2407d7a4bf555da4fb1 --- /dev/null +++ b/presentation/src/main/res/values-night-v27/themes.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/presentation/src/main/res/values-night/themes.xml b/presentation/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000000000000000000000000000000000..ed26b3c64a99b52e147787a3cdb45ebc195d0eec --- /dev/null +++ b/presentation/src/main/res/values-night/themes.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/values-nl/strings.xml b/presentation/src/main/res/values-nl/strings.xml index cb099e77ab3e1bd518442ae14d86fb0543c465f6..c37520ee2608b40a80732d698fe326fb2a7093f4 100644 --- a/presentation/src/main/res/values-nl/strings.xml +++ b/presentation/src/main/res/values-nl/strings.xml @@ -1,7 +1,7 @@ Verzenden op 23 december Plan een bericht Gepland bericht Nu versturen - Verwijderen + Copy text + Delete Uiterlijk Algemeen @@ -194,10 +202,6 @@ Popup voor nieuwe berichten Tik om te sluiten Tik buiten de popup om deze te sluiten - Blokkeren - - Should I Answer? - Automatisch filteren van berichten van ongevraagde nummers met behulp van de app \"Should I Answer\" Vertraagd verzenden Swipe acties Pas de veeg-acties aan voor conversaties @@ -210,9 +214,11 @@ Verwijderen Bellen Markeer als gelezen + Markeer als ongelezen Afleverrapporten Bevestig dat berichten met succes zijn verzonden + Signature Verwijder accenten Verwijder accenten van karakters voor uitgaande berichten Alleen GSM-nummers @@ -225,10 +231,35 @@ Foutenregistratie ingeschakeld Foutenregistratie uitgeschakeld Voer duur (seconden) in - Blokkeren - Jouw geblokkeerde conversaties verschijnen hier - Blokkering opheffen - Wil je de blokkering voor deze conversatie opheffen? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatisch filteren van berichten van ongevraagde nummers met behulp van de app \"Should I Answer\" + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Over Versie Ontwikkelaar @@ -237,6 +268,37 @@ Contact Licentie Copyright + Ondersteun ontwikkeling, ontgrendel alles + Je kan de developer al helpen vanaf %s + + Levenslange upgrade voor %1$s %2$s + + Ontgrendel en doneer voor %1$s %2$s + Bedankt voor het onderhouden van QKSMS! + Je hebt nu toegang tot alle QKSMS+ functies + QKSMS+ is gratis voor F-Droid gebruikers. Als je verdere ontwikkelingen wilt helpen, kun je gerust een donatie overwegen. + Doneren via PayPal + Binnenkort + Premium thema\'s + Ontgrendel vele mooie kleuren die niet beschikbaar zijn in het Material Design kleurenschema + Aangepaste auto-emoji + Maak aangepaste auto-emoji sneltoetsen + Berichtenback-up + Automatische back-up van jouw berichten. Geen zorgen meer over het kwijtraken van jouw berichten bij het verlies van je telefoon of het overschakelen naar een nieuwe telefoon + Geplande berichten + Berichten worden automatisch verzonden op de ingestelde tijd + Vertraagd verzenden + Wacht enkele seconden voor het versturen van je bericht + Automatische nachtmodus + Schakel de nachtmodus in gebaseerd op de tijd van de dag + Geavanceerd blokkeren + Blokkeer berichten die specifieke woorden of patronen bevatten + Auto-doorsturen + Automatisch doorsturen van berichten van bepaalde afzenders + Auto-antwoorden + Automatisch reageren op inkomende berichten met een vooraf ingestelde reactie + Meer + QKSMS is in actieve ontwikkeling, uw aankoop bevat alle toekomstige nieuwigheden exclusief voor QKSMS+! Laden… Bekijk meer conversaties Markeer als gelezen @@ -255,13 +317,14 @@ Bellen Verwijderen + Yes + Continue Annuleren Verwijderen Opslaan Stop Meer Instellen - Blokkering opheffen Ongedaan maken Gekopieerd Conversatie gearchiveerd @@ -272,9 +335,10 @@ Bericht niet verzonden Het bericht naar %s is niet verzonden - Uitgeschakeld - Altijd aan - Automatisch + System + Disabled + Always on + Automatic Toon naam en bericht diff --git a/presentation/src/main/res/values-pl/strings.xml b/presentation/src/main/res/values-pl/strings.xml index 81912b9f582315b32a0843a05c48df98943cf388..ea368645ddb5acaf9da3c25712a10ab592ad4999 100644 --- a/presentation/src/main/res/values-pl/strings.xml +++ b/presentation/src/main/res/values-pl/strings.xml @@ -1,7 +1,7 @@ Do wysłania 23-go grudnia Zaplanuj wiadomość Zaplanowana wiadomość Wyślij teraz - Usuń + Copy text + Delete Wygląd Ogólne @@ -200,10 +208,6 @@ Powiadomienia o nowych wiadomościach Dotknij, aby odrzucić Dotknij gdziekolwiek poza okienkiem, aby zamknąć - Blokowanie - - Should I Answer? - Automatycznie filtruj wiadomości od niechcianych numerów za pomocą aplikacji \"Should I Answer?\" Opóźnione wysyłanie Akcje po przeciągnięciu Ustaw akcje po przeciągnięciu dla rozmów @@ -216,9 +220,11 @@ Usuń Zadzwoń Oznacz jako przeczytane + Oznacz jako nieprzeczytane Potwierdzenie dostarczenia Potwierdzaj dostarczenie każdej wysłanej wiadomości + Podpis Usuwaj ogonki Usuwaj ogonki ze znaków w wiadomościach Tylko numery komórkowe @@ -231,10 +237,39 @@ Logi włączone Logi wyłączone Wprowadź czas (w sekundach) - Blokowanie - Twoje zablokowane rozmowy będą wyświetlane tutaj - Odblokuj - Czy chcesz odblokować tę konwersację? + Blokowanie + Odrzuć wiadomości + Odrzuć przychodzące wiadomości od zablokowanych nadawców zamiast ich ukrywać + Zablokowane rozmowy + Menadżer Blokowania + QKSMS + Wbudowana funkcja blokowania w QKSMS + Automatycznie filtruj twoje rozmowy i wiadomości tekstowe w jednym wygodnym miejscu! Community IQ™ pozwala zapobiegać niechcianym wiadomościom od znanych spamerów + Automatycznie filtruj wiadomości od niechcianych numerów za pomocą aplikacji \"Should I Answer?\" + Skopiuj zablokowane numery + Kontynuuj do %s kopiowanie twoich blokowanych numerów + Zablokowane numery + Twoje blokowane numery się tu pojawią + Zablokuj nowy numer + Blokuj wiadomości od + Numer telefonu + Blokuj + Zablokowane wiadomości + Tutaj pojawią się twoje zablokowane wiadomości + Blokuj + Odblokuj + + Kontynuuj do %s i blokuj ten numer + Kontynuuj do %s i blokuj te numery + Continue to %s and block these numbers + Kontynuuj do %s i blokuj te numery + + + Kontynuuj do %s i odblokuj ten numer + Kontynuuj do %s i odblokuj te numery + Continue to %s and allow these numbers + Kontynuuj do %s i odblokuj te numery + O aplikacji Wersja Deweloper @@ -243,6 +278,37 @@ Kontakt Licencja Prawa autorskie + Wesprzyj rozwój, odblokuj wszystko + Możesz wesprzeć głodującego dewelopera za jedyne %s + + Dożywotnie aktualizacja za %1$s %2$s + + Odblokuj i wesprzyj (%1$s %2$s) + Dziękujemy za wsparcie aplikacji QKSMS! + Masz teraz dostęp do wszystkich funkcji QKSMS+ + Aplikacja QKSMS+ jest bezpłatna dla użytkowników F-Droid! Jeśli chcesz wesprzeć jej rozwój, rzuć pieniążka. + Wsparcie za pośrednictwem serwisu PayPal + Już wkrótce + Motywy premium + Odblokuj piękne kolory motywów, które nie są dostępne w palecie Material Design + Własne automatyczne emoji + Twórz niestandardowe skróty do automatycznych emoji + Kopie zapasowe wiadomości + Automatycznie wykonuj kopie zapasowe wiadomości. Nie martw się o ich utratę, jeśli zmienisz lub zgubisz urządzenie. + Zaplanowane wiadomości + Zaplanuj wysyłanie wiadomości na określony czas i dzień + Opóźnione wysyłanie + Poczekaj kilka sekund przed wysłaniem wiadomości + Automatyczny tryb nocny + Włączaj tryb nocny w oparciu o porę dnia. + Zaawansowane blokowanie + Blokuj wiadomości, które zawierają słowa kluczowe lub dopasowane wzorce. + Automatyczne przekazywanie + Automatyczne przekazuj wiadomości od określonych nadawców. + Automatyczne odpowiedzi + Automatyczne odpowiadaj na przychodzące wiadomości korzystając z gotowych odpowiedzi. + Więcej + QKSMS jest aktywnie rozwijany, twój zakup będzie zawierać wszelkie przyszłe fukcje QKSMS+ Ładowanie… Zobacz więcej konwersacji Oznacz jako przeczytane @@ -261,13 +327,14 @@ Zadzwoń Usuń + Tak + Kontynuuj Anuluj Usuń Zapisz Zatrzymaj Więcej Ustaw - Odblokuj Cofnij Skopiowano Zarchiwizowana rozmowa @@ -280,9 +347,10 @@ Wiadomość nie została wysłana Wiadomość %s nie została wysłana + Systemowy Wyłączony Zawsze włączony - Automatycznie + Automatyczny Pokazuj nadawcę i treść wiadomości @@ -293,7 +361,7 @@ Mały Normalny Duży - Większy + Bardzo duży Bez opóźnienia diff --git a/presentation/src/main/res/values-pt-rBR/strings.xml b/presentation/src/main/res/values-pt-rBR/strings.xml index e8a80eb9fce4ab34f8601ec4058b3593b834248d..3d932471118405917f8a13ad61a3898cdd0a1b94 100644 --- a/presentation/src/main/res/values-pt-rBR/strings.xml +++ b/presentation/src/main/res/values-pt-rBR/strings.xml @@ -1,7 +1,7 @@ Enviar no dia 23 de dezembro Agendar mensagem Mensagem agendada Enviar agora + Copiar texto Apagar Aparência Geral - QK Reply + Responder via QKSMS Tema Modo noturno Modo noturno preto puro Hora de início - Hora de fim - Tamanho do texto - Usar tipo de letra do sistema + Hora de término + Tamanho da fonte + Usar fonte do sistema Emoji automático Notificações Toque para personalizar @@ -192,58 +198,112 @@ Vibração Som Nenhum - QK Reply - Alerta para novas mensagens - Toque para fechar - Toque fora do alerta para o fechar - Bloqueio - - Should I Answer? - Automaticamente filtrar mensagens não solicitadas usando o aplicativo \"Should I Answer\" + Responder via QKSMS + Popup para novas mensagens + Toque para descartar + Toque fora do popup para fechá-lo Atraso de envio Ações de deslize - Configurar ações de deslize nas conversas - Deslize à direita - Deslize à esquerda - Alterar + Configurar ações de deslize para conversas + Deslizar para direita + Deslizar para esquerda + ALTERAR Nenhuma Arquivar - Apagar - Ligar + Excluir + Chamar Marcar como lida + Marcar como não lida Confirmações de entrega - Confirma se as mensagens foram enviadas com sucesso + Confirme que as mensagens foram enviadas com sucesso + Assinatura Remover acentos - Remover acentos dos caracteres nas mensagens enviadas - Apenas números de telemóvel - Ao escrever uma mensagem, mostrar apenas os números móveis + Remover acentos de caracteres em mensagens SMS enviadas + Somente números de celular + Ao escrever uma mensagem, mostrar apenas os números de celular Comprimir anexos MMS automaticamente Sincronizar mensagens - Sincronizar as mensagens com a base de dados nativa do Android - Acerca do Message + Re-sincronizar suas mensagens com o banco de dados de SMS nativo do Android + Sobre o QKSMS Versão %s - Depuração ativada - Depuração desativada + Log de depuração ativado + Log de depuração desativado Digite a duração (segundos) - Bloqueadas - As conversas bloqueadas aparecerão aqui - Desbloquear - Gostaria de desbloquear esta conversa? - Acerca + Bloqueio + Descartar mensagens + As mensagens enviadas pelos remetentes bloqueados serão descartadas e não ocultadas + Conversas bloqueadas + Gestor de bloqueios + QKSMS + Funcionalidade de bloqueio, nativa no QKSMS + Filtrar automaticamente as chamadas e as mensagens no local apropriado. Community IQ™ permite-lhe descartar as mensagens que forem enviadas pelo \'spammers\' reconhecidos pela comunidade. + Automaticamente filtrar mensagens não solicitadas usando o aplicativo \"Should I Answer\" + Copiar números bloqueados + Continuar para %s e substituir os números bloqueados + Números bloqueados + Os números bloqueados aparecerão aqui + Bloquear um novo número + Bloquear mensagens de + Número de telefone + Bloquear + Mensagens bloqueadas + As mensagens bloqueadas aparecerão aqui + Bloquear + Desbloquear + + Continuar para %s e bloquear este número + Continuar para %s e bloquear estes números + + + Continuar para %s e permitir este número + Continuar para %s e permitir estes números + + Sobre Versão - Programador - Código fonte - Alterações - Contactos + Desenvolvedor + Código-fonte + Changelog + Contato Licença Copyright + Apoie o desenvolvimento, desbloqueie tudo + Você pode salvar um desenvolvedor faminto por apenas %s + + Atualização vitalícia por %1$s %2$s + + Desbloqueie e doar por %1$s %2$s + Obrigado por apoiar o QKSMS! + Agora você tem acesso a todos os recursos do QKSMS+ + QKSMS+ é gratuito para usuários do F-Droid! Se você gostaria de apoiar o desenvolvimento, sinta-se livre para fazer uma doação. + Doar via PayPal + Em Breve + Temas premium + Desbloqueie belos temas não disponíveis na paleta do Material Design + Emoji personalizado + Criar atalhos personalizados para os emojis + Backup de mensagens + Backup automático de mensagens. Nunca mais terá que se preocupar com o histórico se trocar de telefone ou perdê-lo + Mensagens agendadas + Agendar mensagens para serem enviadas automaticamente em uma data e hora específicas + Atraso de envio + Aguarde alguns segundos antes de enviar sua mensagem + Modo noturno automático + Habilitar modo noturno automaticamente conforme a hora do dia + Bloqueio avançado + Bloquear mensagens através de palavras-chave ou padrões + Auto-encaminhar + Encaminha mensagens automaticamente de alguns remetentes + Auto-responder + Responde automaticamente às mensagens recebidas com uma resposta predefinida + Mais + O QKSMS está em desenvolvimento ativo e sua compra incluirá todos os futuros recursos do QKSMS+! Carregando… Ver mais conversas Marcar como lida Ligar - Apagar + Excluir Mostrar mais Mostrar menos Abrir conversa @@ -254,33 +314,36 @@ Nenhuma Marcar como lida Responder - Ligar - Apagar + Chamar + Excluir + Sim + Continuar Cancelar - Apagar - Guardar + Excluir + Salvar Parar Mais Definir - Desbloquear - Anular + Desfazer Copiada Conversa arquivada + Você deve desbloquear o QKSMS+ para isto - 1 nova mensagem + Nova mensagem %s novas mensagens Mensagem não enviada - Não foi possível enviar a mensagem para %s + A mensagem para %s falhou ao enviar + Sistema Desativado - Sempre ativo + Sempre Automático - Mostrar nome e mensagem - Mostrar nome + Exibir nome e mensagem + Exibir nome Ocultar conteúdo @@ -290,7 +353,7 @@ Enorme - Não + Sem atraso Curto Médio Longo @@ -298,7 +361,7 @@ 100KB 200KB - 300KB (recomendado) + 300KB (Recomendado) 600KB 1000KB 2000KB @@ -315,7 +378,7 @@ Não Te amo Desculpa - LOL + KKK Está bem diff --git a/presentation/src/main/res/values-pt/strings.xml b/presentation/src/main/res/values-pt/strings.xml index 7b283c543c4ec87855664114a7df04541aca6ca7..dee3fb55096f54e16efd556a4aa7036861ed5d2c 100644 --- a/presentation/src/main/res/values-pt/strings.xml +++ b/presentation/src/main/res/values-pt/strings.xml @@ -1,7 +1,7 @@ Enviar no dia 23 de dezembro Agendar mensagem Mensagem agendada Enviar agora + Copiar texto Apagar Aparência @@ -196,10 +204,6 @@ Alerta para novas mensagens Toque para fechar Toque fora do alerta para o fechar - Bloqueadas - - Should I Answer? - Filtrar automaticamente as mensagens de números utilizando a aplicação \"Should I Answer\" Atraso de envio Ações de deslize Configurar ações de deslize nas conversas @@ -212,9 +216,11 @@ Apagar Ligar Marcar como lida + Marcar como não lida Confirmações de entrega Confirma se as mensagens foram enviadas com sucesso + Assinatura Remover acentos Remover acentos dos caracteres nas mensagens enviadas Apenas números de telemóvel @@ -227,10 +233,35 @@ Depuração ativada Depuração desativada Digite a duração (segundos) - Bloqueadas - As conversas bloqueadas aparecerão aqui - Desbloquear - Gostaria de desbloquear esta conversa? + Bloqueio + Descartar mensagens + As mensagens enviadas pelos remetentes bloqueados serão descartadas e não ocultadas + Conversas bloqueadas + Gestor de bloqueios + QKSMS + Funcionalidade de bloqueio, nativa no QKSMS + Filtrar automaticamente as chamadas e as mensagens no local apropriado. Community IQ™ permite-lhe descartar as mensagens que forem enviadas pelo \'spammers\' reconhecidos pela comunidade. + Filtrar automaticamente as mensagens de números utilizando a aplicação \"Should I Answer\" + Copiar números bloqueados + Continuar para %s e substituir os números bloqueados + Números bloqueados + Os números bloqueados aparecerão aqui + Bloquear um novo número + Bloquear mensagens de + Número de telefone + Bloquear + Mensagens bloqueadas + As mensagens bloqueadas aparecerão aqui + Bloquear + Desbloquear + + Continuar para %s e bloquear este número + Continuar para %s e bloquear estes números + + + Continuar para %s e permitir este número + Continuar para %s e permitir estes números + Acerca Versão Programador @@ -239,6 +270,37 @@ Contactos Licença Copyright + Apoiar o desenvolvimento, desbloquear tudo + Pode ajudar um programador com apenas %s + + Atualização vitalícia para %1$s %2$s + + Desbloquear + com uma doação de %1$s %2$s + Obrigado por apoiar o QKSMS! + Agora, tem acesso a todas as funções do QKSMS+ + O QKSMS+ é gratuito para os utilizadores F-Droid! Se quiser ajudar no desenvolvimento, pode efetuar um donativo. + Donativos por PayPal + Brevemente + Temas premium + Desbloquear diversos temas não disponíveis na paleta Material Design + Emoji personalizado + Criar atalhos personalizados para os emoji + Backup de mensagens + Backup automático das mensagens. Nunca mais terá que se preocupar com o histórico caso troque de telefone. + Mensagens agendadas + Agendar mensagens para envio automático à hora/data especificada + Atraso de envio + Esperar alguns segundos antes de enviar a mensagem + Modo noturno automático + Ativar o modo noturno tendo por base a hora + Bloqueio avançado + Bloquear mensagens através de palavras-chave ou padrões + Reencaminhamento automático + Reencaminhar automaticamente as mensagens de alguns remetentes + Resposta automática + Enviar automaticamente um resposta tendo por base os modelos + Mais + Esta aplicação está a ser desenvolvida e a sua compra disponibiliza todas as futuras funcionalidades do QKSMS+! A carregar… Ver mais conversas Marcar como lida @@ -257,13 +319,14 @@ Ligar Apagar + Sim + Continuar Cancelar Apagar Guardar Parar Mais Definir - Desbloquear Anular Copiada Conversa arquivada @@ -274,8 +337,9 @@ Mensagem não enviada Não foi possível enviar a mensagem para %s + Sistema Desativado - Sempre ativo + Sempre Automático @@ -306,7 +370,7 @@ Ok - Espera um momento + Espera um pouco Estou a caminho Obrigado Parece-me bem diff --git a/presentation/src/main/res/values-ro/strings.xml b/presentation/src/main/res/values-ro/strings.xml index 5451b8cdfab3757d85a83fdbe5753d62ffcaa1b9..5cd55847dab5001095357d4e4aa83ce92a875a06 100644 --- a/presentation/src/main/res/values-ro/strings.xml +++ b/presentation/src/main/res/values-ro/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Appearance @@ -194,10 +202,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -209,10 +213,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -225,10 +231,37 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Continue to %s and allow these numbers + About Version Developer @@ -237,9 +270,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -250,18 +314,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -273,6 +338,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-ru/strings.xml b/presentation/src/main/res/values-ru/strings.xml index 9d038257d941363bb04ed9a8aca5e10f5e431eab..84c5b88b288cbc213781407ecb450c9768652bc8 100644 --- a/presentation/src/main/res/values-ru/strings.xml +++ b/presentation/src/main/res/values-ru/strings.xml @@ -1,7 +1,7 @@ Отправка 23 декабря  Запланировать сообщение Запланированное сообщение Отправить сейчас + Копировать текст Удалить Внешний вид Общее - QK Ответ + QK-ответ Тема Ночной режим - Полностью черный ночной режим + Полностью чёрный ночной режим Время начала Время окончания Размер шрифта @@ -194,19 +202,15 @@ Вибросигнал Звук Нет - QK Ответ + QK-ответ Всплывающее окно для новых сообщений Нажмите, чтобы отклонить Нажмите за пределами всплывающего окна, чтобы закрыть его - Блокировка - - Should I Answer? - Автоматически фильтровать сообщения от нежелательных номеров, используя приложение «Should I Answer» - Отложенная отправка - Действия при свайпе - Настройка свайп действий для бесед - Свайп вправо - Свайп влево + Задержка перед отправкой + Действия жестами + Настройка действий жестами для бесед + Жест вправо + Жест влево ИЗМЕНИТЬ Нет @@ -214,25 +218,56 @@ Удалить Позвонить Отметить прочитанным + Пометить непрочитанным Отчёты о доставке Подтверждение успешной отправки сообщений - Удалить акценты + Подпись + Удалять акценты Удаление акцентов с символов в исходящих SMS-сообщениях Только мобильные номера При составлении сообщения показывать только мобильные номера - Автоматически сжимать MMS вложения + Автоматически сжимать вложения MMS Синхронизация сообщений - Повторная синхронизация ваших сообщений с собственной базой данных SMS Android - О Message + Принудительная синхронизация сообщений с собственной базой данных SMS Android + О QKSMS Версия %s Ведение журнала отладки включено Ведение журнала отладки отключено Введите продолжительность (в секундах) - Блокировка - Ваши заблокированные беседы будут появляться здесь - Разблокировать - Хотите разблокировать эту беседу? + Блокировка + Удалять сообщения + Удалять входящие сообщения от заблокированных отправителей, а не скрывать их + Заблокированные беседы + Управление блокировкой + QKSMS + Встроенная функция блокировки QKSMS + Автоматическая фильтрация звонков и сообщений в одном удобном месте! Community IQ™ позволяет предотвратить получение нежелательных сообщений от известных сообществу спаммеров + Автоматически фильтровать сообщения от нежелательных номеров, используя приложение «Should I Answer» + Скопировать заблокированные номера + Продолжить с %s и записать их вместо существующих заблокированных номеров + Заблокированные номера + Заблокированные номера будут появляться здесь + Блокировать новый номер + Заблокировать отправителя + Номер телефона + Блокировать + Заблокированные сообщения + Заблокированные сообщения будут появляться здесь + Блокировать + Разблокировать + + Продолжить с %s и заблокировать этот номер + Продолжить с %s и заблокировать эти номера + Продолжить с %s и заблокировать эти номера + Продолжить с %s и заблокировать эти номера + + + Продолжить с %s и разрешить этот номер + Продолжить с %s и разрешить эти номера + Продолжить с %s и разрешить эти номера + Продолжить с %s и разрешить эти номера + О приложении Версия Разработчик @@ -241,12 +276,43 @@ Контакт Лицензия Авторское право + Поддержать развитие, разблокировать всё + Вы можете спасти голодающего разработчика всего за %s + + Пожизненное обновление за %1$s %2$s + + Разблокировать + пожертвовать %1$s %2$s + Спасибо за поддержку QKSMS! + Теперь у вас есть доступ ко всем функциям QKSMS+ + QKSMS+ является бесплатным для пользователей F-Droid! Если вы хотите поддержать развитие приложения, сделайте пожертвование. + Пожертвовать через PayPal + Скоро + Премиальные темы + Разблокируйте красивые цвета темы, недоступные в палитре Material Design + Пользовательские авто-emoji + Создайте ярлык пользовательских авто-emoji + Резервирование сообщений + Автоматически создавайте резервные копии сообщений. Не беспокойтесь о потере истории, если вы поменяете или потеряете телефон + Запланированные сообщения + Запланированные сообщения для автоматической отправки в определённое время и дату + Отложенная отправка + Выжидание несколько секунд перед отправкой сообщения + Автоматический ночной режим + Включать ночной режим, основываясь на времени суток + Расширенная блокировка + Блокировать сообщения, содержащие ключевые слова или соответствующие шаблонам + Автоматическая пересылка + Автоматическая пересылка сообщений от определённых отправителей + Автоматический ответ + Автоматически отвечать на входящие сообщения предустановленным ответом + Ещё + QKSMS находится в активной разработке и ваша покупка будет включать в себя все функции QKSMS+ в будущем! Загрузка… Посмотреть больше бесед Отметить прочитанным Позвонить Удалить - Показать больше + Показать ещё Показать меньше Открыть беседу Material @@ -254,21 +320,23 @@ Применить Нет - Отметить прочитанным + Прочитано Ответить Позвонить Удалить + Да + Продолжить Отмена Удалить Сохранить Остановить - Еще + Ещё Задать - Разблокировать Отмена Скопировано Архивированная беседа + Вы должны разблокировать QKSMS+, чтобы использовать Новое сообщение %s новых сообщения @@ -278,8 +346,9 @@ Сообщение не отправлено Не удалось отправить сообщение для %s + Как в системе Отключено - Всегда включен + Всегда включено Автоматически @@ -291,7 +360,7 @@ Маленький Обычный Крупный - Больше + Большой Без задержки @@ -300,12 +369,12 @@ Длинная - 100КБ - 200КБ + 100 КБ + 200 КБ 300 КБ (рекомендуется) - 600КБ - 1000КБ - 2000КБ + 600 КБ + 1000 КБ + 2000 КБ Без сжатия diff --git a/presentation/src/main/res/values-sk/strings.xml b/presentation/src/main/res/values-sk/strings.xml index 1ccaa6ecc4f2e5339cfc5aa51f2a2ae75742ac7d..a849c1c7d7f3041b0e5b0898283261c5e0985e82 100644 --- a/presentation/src/main/res/values-sk/strings.xml +++ b/presentation/src/main/res/values-sk/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Odoslať teraz - Odstrániť + Copy text + Delete Vzhľad Všeobecné @@ -198,10 +206,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blokované - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -213,10 +217,12 @@ Archivovať Odstrániť Zavolať - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -229,10 +235,39 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blokované - Blokované konverzácie sa zobrazia tu - Odblokovať - Chcete odblokovať túto konverzáciu? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + Continue to %s and block these numbers + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Continue to %s and allow these numbers + Continue to %s and allow these numbers + O aplikácii Verzia Vývojár @@ -241,6 +276,37 @@ Konktakt Licencia Autorské práva + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Už čoskoro + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatický nočný režim + Enable night mode based on the time of day + Rozšírené blokovanie + Blokujte správy, ktoré obsahujú kľúčové slová, alebo odpovedajú vzoru + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + Viac + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Načítavanie… View more conversations Označiť ako prečítané @@ -259,13 +325,14 @@ Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Odblokovať Undo Copied Archived conversation @@ -278,6 +345,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-sr/strings.xml b/presentation/src/main/res/values-sr/strings.xml index dbfa74b299768c2a82d63e32e189612686db6bb6..a7759f914c90f7afaa8acb86f08115c26eedd318 100644 --- a/presentation/src/main/res/values-sr/strings.xml +++ b/presentation/src/main/res/values-sr/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Appearance @@ -194,10 +202,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -209,10 +213,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -225,10 +231,37 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Continue to %s and allow these numbers + About Version Developer @@ -237,9 +270,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -250,18 +314,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -273,6 +338,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-sv/strings.xml b/presentation/src/main/res/values-sv/strings.xml index 0587957a8641e3ca79c5468f2ce497ffdd2d8db4..1903564c7d7256da29ecf26dc575cfbec063e526 100644 --- a/presentation/src/main/res/values-sv/strings.xml +++ b/presentation/src/main/res/values-sv/strings.xml @@ -1,7 +1,7 @@ Skickar den 23:e December  Schemalägg ett meddelande Schemalagt meddelande Skicka nu - Ta bort + Copy text + Delete Utseende Generellt @@ -196,10 +204,6 @@ Popup för nya meddelanden Tryck för att avfärda Tryck var som helst utanför popupen för att stänga den - Blockerar - - Borde jag svara? - Filtrera automatiskt meddelanden från oönskade nummer genom att använda appen \"Borde jag svara\" Fördröj skickande av meddelanden Svepåtgärder Ställ in svep-åtgärder för konversationer @@ -212,9 +216,11 @@ Ta bort Ring Markera som läst + Mark as unread Leveransrapporter Bekräfta att meddelanden har skickats + Signature Ta bort accenter Ta bort accenter från tecken i utåtgående SMS-meddelanden Endast mobilnummer @@ -227,10 +233,35 @@ Felsökningsloggning aktiverades Felsökningsloggning inaktiverad Fyll i varaktighet (sekunder) - Blockering - Dina blockerade konversationer kommer att dyka upp här - Avblockera - Vill du avblockera den här konversationen? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Filtrera automatiskt meddelanden från oönskade nummer genom att använda appen \"Borde jag svara\" + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + Om Version Utvecklare @@ -239,6 +270,37 @@ Kontakt Licens Kopieringsskydd + Ge stöd till utveckling, lås upp allting + Du kan hjälpa en svältande utvecklare för bara %s + + Livstidsuppgradering för %1$s %2$s + + Lås upp + donera för %1$s %2$s + Tack för att du stöttar QKSMS! + Du har nu tillgång till alla funktioner i QKSMS+ + QKSMS + är gratis för F-Droid användare! Om du vill stödja utvecklingen, Så får du gärna donationera. + Donera via PayPal + Kommer snart + Högkvalitativa teman + Lås upp vackra färger som inte finns tillgängliga på Material Design paletten + Anpassad auto-emoji + Skapa anpassade auto-emoji genvägar + Säkerhetskopiering av meddelande + Säkerhetskopiera automatiskt dina meddelanden. Du behöver aldrig mer oroa dig för att förlora dina konversationer om du byter telefon eller telefonen försvinner + Schemalagda meddelanden + Schemalägg meddelanden att automatiskt skickas vid en specifik tid och datum + Fördröj skickande av meddelanden + Vänta några sekunder innan du skickar ditt meddelande + Automatiskt nattläge + Aktivera nattläge baserat på tiden på dygnet + Avancerade blockering + Blockera meddelanden som innehåller specifika ord eller matchar mönster + Auto-forward + Automatiskt vidarebefordra meddelanden från vissa avsändare + Auto-svara + Svara automatiskt på inkommande meddelanden med ett förinställt svar + Mer + QKSMS är under aktiv utveckling, och dina köp kommer att inkludera alla kommande QKSMS+-funktioner! Laddar… Visa fler konversationer Markera som läst @@ -257,13 +319,14 @@ Ring Ta bort + Yes + Continue Avbryt Radera Spara Stoppa Mer Ställ in - Avblockera Ångra Kopierad Arkiverade konversationer @@ -274,9 +337,10 @@ Meddelandet skickades inte Meddelandet till %s misslyckades att skicka - Inaktivera - Alltid på - Automatisk + System + Disabled + Always on + Automatic Visa namn och meddelande diff --git a/presentation/src/main/res/values-th/strings.xml b/presentation/src/main/res/values-th/strings.xml index 360f8f6ed1bfd3145c2bb4545c093eaf6622be64..59d4b57aca50fafbb126e9d042e5aae57ce4e4a2 100644 --- a/presentation/src/main/res/values-th/strings.xml +++ b/presentation/src/main/res/values-th/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete ลักษณะที่ปรากฏ @@ -190,10 +198,6 @@ ป๊อปอัพข้อความใหม่ แตะเพื่อยกเลิก แตะนอกป๊อปอัพเพื่อปิด - การปิดกั้น - - ควรตอบกลับหรือไม่ - กรองข้อความจากหมายเลขที่ไม่พึงประสงค์ โดยใช้แอพควรตอบกลับหรือไม่ การส่งแบบล่าช้า Swipe actions Configure swipe actions for conversations @@ -205,10 +209,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread ยืนยันการส่ง ยืนยันว่า ส่งข้อความเรียบร้อยแล้ว + Signature ลบอักษรพิเศษ accents ลบอักษรพิเศษ accents ออกจากข้อความ SMS ที่กำลังจะส่งออก Mobile numbers only @@ -221,10 +227,33 @@ Debug logging enabled Debug logging disabled ระยะเวลาการป้อน (วินาที) - การปิดกั้น - สนทนาของคุณที่ถูกปิดกั้นจะปรากฏที่นี่ - ยกเลิกการปิดกั้น - คุณต้องการปิดกั้นการสนทนานี้หรือไม่ + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + กรองข้อความจากหมายเลขที่ไม่พึงประสงค์ โดยใช้แอพควรตอบกลับหรือไม่ + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block these numbers + + + Continue to %s and allow these numbers + เกี่ยวกับ เวอร์ชัน นักพัฒนา @@ -233,6 +262,37 @@ ติดต่อ สิทธิ์การใช้งาน ลิขสิทธิ์ + สนับสนุนการพัฒนา หรือปลดล็อคทุกอย่าง + คุณสามารถช่วยเหลือนักพัฒนาเหล่าได้เพียง %s + + อัพเกรดตลอดอายุการใช้งานสำหรับ %1$s %2$s + + ปลดล็อค + บริจาค %1$s %2$s + ขอบคุณสำหรับการสนับสนุน QKSMS + ตอนนี้คุณสามารถเข้าถึงคุณสมบัติทั้งหมดของ QKSMS+ ได้แล้ว + QKSMS+ เป็นบริการฟรีสำหรับผู้ใช้งาน F-Droid หากคุณต้องการสนับสนุนการพัฒนา สามารถบริจาคได้ + บริจาคผ่าน PayPal + เร็วๆ นี้ + รูปแบบพรีเมี่ยม + ปลดล็อครูปแบบการแสดงผลที่ไม่ได้อยู่ใน Material Design + อีโมจิอัตโนมัติที่กำหนดเอง + สร้างทางลัดอิโมจิอัตโนมัติแบบกำหนดเอง + สำรองข้อมูลข้อความ + สำรองข้อมูลข้อความของคุณโดยอัตโนมัติ ไม่ต้องกังวลว่าประวัติข้อความของคุณจะสูญหายถ้าคุณเปลี่ยนโทรศัพท์มือถือ หรือทำโทรศัพท์หาย + ข้อความที่กำหนดเวลา + ข้อความจะถูกส่งโดยอัตโนมัติตามวันและเวลาที่กำหนด + การส่งแบบล่าช้า + รอสักครู่ก่อนที่จะส่งข้อความของคุณ + โหมดกลางคืนอัตโนมัติ + เปิดใช้งานโหมดกลางคืนตามเวลาของวัน + ปิดกั้นขั้นสูง + ปิดกั้นข้อความที่ประกอบด้วยคำสำคัญ หรือตรงกับรูปแบบ + ส่งต่ออัตโนมัติ + ส่งต่อข้อความจากผู้ส่งที่ถูกระบุโดยอัตโนมัติ + ตอบกลับอัตโนมัติ + ตอบกลับข้อความที่เข้ามาอัตโนมัติ โดยใช้ข้อความที่จัดเตรียมไว้ + เพิ่มเติม + QKSMS is under active development, and your purchase will include all future QKSMS+ features! กำลังโหลด… ดูการสนทนาเพิ่มเติม ทำเครื่องหมายอ่าน @@ -246,18 +306,19 @@ ตกลง None - Mark read + Mark as read Reply Call Delete + Yes + Continue ยกเลิก ลบ Save Stop เพิ่มเติม ตั้งค่า - ยกเลิกการปิดกั้น ย้อนกลับ คัดลอกแล้ว จัดเก็บการสนทนาแล้ว @@ -267,9 +328,10 @@ ข้อความไม่ถูกส่ง ข้อความที่จะส่งถึง %s ไม่สามารถส่งได้ - ปิดการทำงาน - เปิดตลอด - อัตโนมัติ + System + Disabled + Always on + Automatic แสดงชื่อและข้อความ diff --git a/presentation/src/main/res/values-tl/strings.xml b/presentation/src/main/res/values-tl/strings.xml index 91692f9914948d928845cf4573b5b1798619e0ee..3f1dc3e393933ac01daa1f37a69f3d47a0c06ccf 100644 --- a/presentation/src/main/res/values-tl/strings.xml +++ b/presentation/src/main/res/values-tl/strings.xml @@ -1,7 +1,7 @@ - New conversation - Compose + Bagong usapan + Sumulat Shortcut disabled - Archived + Nakatabi Mga setting Notifications Theme - Search inbox… + Hanapin sa inbox… Sumulat - Skip - Continue + Laktawan + Magpatuloy Tawagan - Details + Mga detalye Save to gallery Open navigation drawer %d selected Clear - Archive - Unarchive - Delete - Pin to top + Itabi + Ilabas + Burahin + Add to contacts + I-ipit sa itaas Unpin - Mark read - Mark unread + Mark as read + Mark as unread Block Syncing messages… - You: %s + Ikaw + Draft: %s Results in messages %d messages - Your conversations will appear here - No results - Your archived conversations will appear here - Start new conversation + Ang iyong mga pakikipag-usap ay makikita rito + Walang nakita + Ang iyong mga isinantabing pakikipag-usap at makikita rito + Magsimula ng bagong usapan Love texting again Make Message your default SMS app Change @@ -60,13 +62,18 @@ Message needs permission to view your contacts Allow Inbox - Archived + Nakatabi Scheduled Blocking - More + Higit pa Settings Help & feedback Invite friends + Unlock amazing new features, and support development + Enjoying QKSMS? + Share some love and rate us on Google Play! + OKAY! + DISMISS Delete Are you sure you would like to delete this conversation? @@ -117,7 +124,6 @@ Failed to send. Tap to try again Details Conversation title - Enter title Notifications Theme Archive @@ -160,11 +166,13 @@ Hey! When was your birthday again? It\'s on December 23rd Happy birthday! Look at what a great friend I am, remembering your birthday + Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Appearance @@ -192,10 +200,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -207,10 +211,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -223,10 +229,35 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About Version Developer @@ -235,9 +266,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -248,18 +310,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -270,6 +333,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-tr/strings.xml b/presentation/src/main/res/values-tr/strings.xml index 00d5d345da4b8208679f9fd4a923c2d68425084a..5f8cd93d7fa4d229db5bca74cec23a9ed25a7f8a 100644 --- a/presentation/src/main/res/values-tr/strings.xml +++ b/presentation/src/main/res/values-tr/strings.xml @@ -1,7 +1,7 @@ 23 Aralık\'ta gönder Bir mesaj zamanla Zamanlanmış mesaj Şimdi gönder + Metni kopyala Sil Görünüm @@ -196,10 +204,6 @@ Yeni iletiler için açılan menü Kapatmak için dokunun Açılır pencereyi kapatmak için pencere dışındaki herhangi bir yere dokunun - Engellenmiş - - Ben cevap vereyim? - İstenmeyen numaralardan gelen iletileri otomatik olarak filtre Gecikmeli gönderme Kaydırma eylemleri Konuşmalar için kaydırma eylemlerini yapılandırma @@ -212,9 +216,11 @@ Sil Ara Okundu olarak işaretle + Okunmadı olarak işaretle İletim raporları Mesajınızın sorunsuz gönderildiğinin onayını alın + İmza Şeritli aksanlar Giden SMS mesajlarından aksanlı karakterleri kaldırın Sadece cep telefonu numaraları @@ -227,10 +233,37 @@ Hata ayıklama günlüğü etkin Hata ayıklama günlüğü devre dışı Süre (saniye olarak) girin - Engellenmiş - Engellenen konuşmalarınız burada görünür - Engelini kaldır - Bu konuşmanın engelini kaldırmak ister misiniz? + Engelleyici + Mesajları bırak + Engellenen gönderenlerden gelen mesajları gizlemek yerine bırakın + Engellenen konuşmalar + Engelleme Yöneticisi + QKSMS + QKSMS\'de yerleşik engelleme işlevi + Aramalarınızı ve mesajlarınızı uygun bir yerde otomatik olarak filtreleyin! Topluluk IQ™ bilinen spam istenmeyen mesajları önlemek için izin verir + +BAĞLAM İSTEĞİ + İstenmeyen numaralardan gelen iletileri otomatik olarak filtre + Engellenen numaraları kopyala + Mevcut engellenen numaralarınızın üzerine %s kopyalamaya devam edin + Engellenen numaralar + Engellenen numaralarınız burada görünecektir + Yeni bir numarayı engelle + Gelen metinleri engelle + Telefon numarası + Engelle + Engellenen mesajlar + Engellenen mesajlarınız burada görünecektir + Engelle + Engeli kaldır + + %s\'ye devam edin ve bu sayıyı engelleyin + %s\'ye devam edin ve bu sayıları engelleyin + + + %s\'ye devam edin ve bu sayıya izin verin + %s\'ye devam edin ve bu sayılara izin verin + Hakkında Sürüm Geliştirici @@ -239,6 +272,37 @@ İletişim Lisanslar Telif hakkı + Gelişimini desteklemek, her şey kilitsiz + Seni kurtarmak için sadece %s açlıktan ölen bir geliştirici + + Süresiz kullanmak 1$s %2$s + + Kilidi aç ve Satın Al %1$s %2$s + QKSMS desteği için teşekkür ederiz!! + Şimdi tüm QKSMS+ özellikleri açıldı! + QKSMS+ F-Droid kullanıcıları için ücretsizdir! Eğer gelişmeyi desteklemek istiyorsanız, bağış yapmakta özgürsünüz. + Paypal ile satın al + Yakında + Ücretli Temalar + Malzeme tasarım paleti üzerinde bulunmayan güzel tema renkleri aç + Özel otomatik ifadeler + Özel otomatik ifadeler kısayolları oluştur + Mesajları yedekle + İletileriniz otomatik olarak yedekleyin. Telefonunuzu değiştirir veya kaybolursa endişelenmeyin. + Zamanlanmış iletiler + Belirli bir saatte ve tarihte otomatik olarak gönderilecek mesajları zamanla + Gecikmeli gönderme + İletinizi göndermeden önce birkaç saniye bekleyin + Otomatik gece modu + Günün zamanına göre gece modunu etkinleştir + Gelişmiş engelleme + Belirli anahtar sözcükleri içeren veya desen eşleşen iletileri engelleme + Otomatik ilet + Belirli gönderenlerden gelen iletileri otomatik olarak ilet + Otomatik yanıtla + Önceden ayarlanmış bir yanıt ile gelen iletilere otomatik olarak yanıtla + Daha fazla + QKSMS etkin geliştirme altında ve satın alırsan tüm gelecek QKSMS+ özellikleri dahil olacaktır! Yükleniyor... Daha fazla konuşma göster Okundu olarak işaretle @@ -257,13 +321,14 @@ Ara Sil + Evet + Devam İptal Sil Kaydet Durdur Daha fazla Ayarla - Engelini kaldır Geri al Kopyalandı. Arşivlenmiş konuşmalar @@ -274,7 +339,8 @@ İleti gönderilemedi %s mesaj gönderilemedi - Devre dışı bırak + Sistem + Devre dışı Her zaman açık Otomatik diff --git a/presentation/src/main/res/values-uk/strings.xml b/presentation/src/main/res/values-uk/strings.xml index 5b37db649f137204c0b90af3cd1f1bdd15121305..dedc3e83cd96a5ca96674791d24c6fc931eadb72 100644 --- a/presentation/src/main/res/values-uk/strings.xml +++ b/presentation/src/main/res/values-uk/strings.xml @@ -1,7 +1,7 @@ Надіслано: 23 грудня  Запланувати повідомлення Заплановане повідомлення Надіслати зараз + Копіювати текст Видалити Зовнішній вигляд @@ -198,10 +206,6 @@ Виринаюче вікно для нових повідомлень Торкніться, щоб вимкнути Торкніться за межами виринаючого вікна, щоб закрити його - Блокування - - Should I Answer? - Автоматично фільтрувати повідомлення від небажаних номерів, використовуючи додаток «Should I Answer» Затримка відправлення Дії жестів Налаштуйте використання жестів для розмови @@ -214,9 +218,11 @@ Видалити Виклик Позначити як прочитане + Позначити як непрочитане Звіти про доставку Підтвердження успішного надсилання повідомлень + Підпис Видалити акценти Видалення акцентів з символів у вихідних SMS-повідомленнях Лише номери мобільних @@ -229,10 +235,39 @@ Ведення журналу зневаження увімкнуте Ведення журналу зневаження вимкнуте Введіть тривалість (у секундах) - Блокування - Ваші заблоковані розмови з\'являтимуться тут - Розблокувати - Бажаєте розблокувати цю бесіду? + Блокування + Видалити повідомлення + Видаляти вхідні повідомлення від заблокованих відправників, а не приховувати їх + Заблоковані бесіди + Керування блокуванням + QKSMS + Вбудована функція блокування QKSMS + Автоматична фільтрація дзвінків та повідомлень в одному зручному місці! Community IQ™ дозволить запобігти отримуванню небажаних повідомлень від відомих спільноті спамерів + Автоматично фільтрувати повідомлення від небажаних номерів, використовуючи додаток «Should I Answer» + Скопіювати заблоковані номери + Продовжити з %s і записати їх замість наявних заблокованих номерів + Заблоковані номери + Тут з\'являтимуться ваші заблоковані номери + Заблокувати новий номер + Блокувати SMS від + Номер телефону + Блокувати + Заблоковані повідомлення + Ваші заблоковані повідомлення будуть відображатися тут + Блокувати + Розблокувати + + Продовжити з %s і заблокувати цей номер + Продовжити з %s і заблокувати ці номери + Продовжити з %s і заблокувати ці номери + Продовжити з %s і заблокувати ці номери + + + Перейти до %s на дозволити цей номер + Перейти до %s на дозволити ці номери + Перейти до %s на дозволити ці номери + Перейти до %s на дозволити ці номери + Про додаток Версія Розробник @@ -241,6 +276,37 @@ Контакт Ліцензія Авторське право + Підтримати подальший розвиток, розблокувати все + Ви можете врятувати голодуючого розробника всього за %s + + Довічне оновлення %1$s %2$s + + Розблокувати + пожертвувати %1$s %2$s + Дякуємо вам за підтримку QKSMS! + Тепер у вас є доступ до всіх функцій QKSMS+ + QKSMS+ є безкоштовним для користувачів F-Droid! Якщо ви хочете підтримати подальший розвиток програми, зробіть пожертву. + Пожертвування через PayPal + Незабаром + Преміум теми + Розблокуйте гарні кольори теми, недоступні в палітрі Material Design + Власні авто-emoji + Створюйте ярлик власних авто-emoji + Резерв. копіювання + Автоматично створюйте резервні копії ваших повідомлень. Не переймайтеся, що втратите історію, якщо ви зміните або загубите телефон + Заплановані повідомлення + Заплановані повідомлення для надсилання у визначений час та дату + Відкладене надсилання + Зачекайте кілька секунд, перш ніж надсилати повідомлення + Автоматичний нічний режим + Увімкнення нічного режиму на основі часу доби + Розширене блокування + Блокувати повідомлення, які містять ключові слова або відповідають шаблонам + Автоматичне пересилання + Автоматичне пересилання повідомлень від певних відправників + Автоматична відповідь + Автоматично відповідати на вхідні повідомлення за допомогою попередньо налаштованих шаблонів + Ще + QKSMS перебуває у активній розробці, і Ваша покупка буде включати в себе всі майбутні можливості QKSMS +! Завантаження… Переглянути більше бесід Позначити як прочитане @@ -259,13 +325,14 @@ Зателефонувати Видалити + Так + Продовжити Скасувати Видалити Зберегти Зупинити Більше Задати - Розблокувати Скасувати Скопійовано Бесіду заархівовано @@ -278,8 +345,9 @@ Повідомлення не надіслано Не вдалося надіслати повідомлення для %s - Вимкнений - Завжди увімкнений + Система + Вимкнено + Завжди увімкнено Автоматично diff --git a/presentation/src/main/res/values-ur/strings.xml b/presentation/src/main/res/values-ur/strings.xml index 8a0504839fbeae0e2c6c3850ff4ec8862785d595..f05c9f6ed567efb363cc5561383eee5cc3a59424 100644 --- a/presentation/src/main/res/values-ur/strings.xml +++ b/presentation/src/main/res/values-ur/strings.xml @@ -1,7 +1,7 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete Appearance @@ -192,10 +200,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -207,10 +211,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -223,10 +229,35 @@ Debug logging enabled Debug logging disabled Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About Version Developer @@ -235,9 +266,40 @@ Contact License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -248,18 +310,19 @@ Apply None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied Archived conversation @@ -270,6 +333,7 @@ Message not sent The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values-v23/themes.xml b/presentation/src/main/res/values-v23/themes.xml index a320beab46a757f459a1fd16b46cf18b1c29cd8b..b789222d59319bc6b0567f360d98d33bfa5bf4ab 100644 --- a/presentation/src/main/res/values-v23/themes.xml +++ b/presentation/src/main/res/values-v23/themes.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + diff --git a/presentation/src/main/res/values-v27/themes.xml b/presentation/src/main/res/values-v27/themes.xml index 785601fd94ee30bdd0e8197e618f0effb8af48bd..6acad9c94216da846b72f886ec2718acf5fffdd8 100644 --- a/presentation/src/main/res/values-v27/themes.xml +++ b/presentation/src/main/res/values-v27/themes.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + diff --git a/presentation/src/main/res/values-vi/strings.xml b/presentation/src/main/res/values-vi/strings.xml index e1ebc83cf8d07a584c0ad35da06d1800be086ebf..0ae3c6a72257253b062ff5f201eff77b92403df2 100644 --- a/presentation/src/main/res/values-vi/strings.xml +++ b/presentation/src/main/res/values-vi/strings.xml @@ -1,7 +1,7 @@ Sẽ gửi vào ngày 23 tháng 12 Hẹn giờ tin nhắn Tin nhắn đã hẹn giờ Gửi ngay - Xoá + Copy text + Delete Giao diện Chung @@ -190,10 +198,6 @@ Hiển thị popup khi có tin nhắn mới Chạm để bỏ qua Chạm vào phía ngoài của hộp thoại để đóng - Chặn liên hệ - - Bộ lọc liên hệ giả mạo - Tự động lọc tin nhắn từ số điện thoại không mong muốn bằng cách sử dụng ứng dụng \"Should I Answer\" Trì hoãn gửi tin Các hành động vuốt Tuỳ chỉnh hành động vuốt cho cuộc trò chuyện @@ -206,9 +210,11 @@ Xoá Gọi điện Đánh dấu là đã đọc + Đánh dấu là chưa đọc Xác nhận đã gửi tin Xác nhận rằng các tin nhắn được gửi thành công + Chữ ký Bỏ dấu Bỏ dấu ký tự khi gửi SMS Lọc số di động @@ -221,10 +227,33 @@ Đã bật bản ghi gỡ lỗi Đã tắt bản ghi gỡ lỗi Nhập khoảng thời gian (giây) - Chặn liên hệ - Liên hệ bị chặn sẽ hiện ở đây - Bỏ chặn liên hệ - Bạn có muốn bỏ chặn liên hệ này ? + Đang chặn + Loại bỏ tin nhắn + Loại bỏ các tin nhắn được gửi tới từ người gửi đã chặn thay vì ẩn chúng + Các cuộc trò chuyện đã chặn + Quản lý danh sách chặn + QKSMS + Chức năng chặn tích hợp sẵn trong QKSMS + Tự động lọc các cuộc gọi và tin nhắn của bạn ở một nơi thuận tiện! Cộng đồng IQ™ cho phép bạn ngăn chặn các tin nhắn từ những kẻ gửi tin rác được đóng góp bởi cộng đồng + Tự động lọc tin nhắn từ số điện thoại không mong muốn bằng cách sử dụng ứng dụng \"Should I Answer\" + Sao chép số đã chặn + Tiếp tục %s sao chép vào các số đã chặn hiện có của bạn + Các số đã chặn + Các số bạn đã chặn sẽ xuất hiện ở đây + Chặn một số mới + Chặn văn bản từ + Số điện thoại + Chặn + Các tin nhắn đã chặn + Các tin nhắn bạn đã chặn sẽ xuất hiện ở đây + Chặn + Bỏ chặn + + Tiếp tục %s và chặn các số này + + + Tiếp tục %s và cho phép các số này + Giới thiệu Phiên bản Nhà phát triển @@ -233,6 +262,37 @@ Liên hệ Giấy phép Bản quyền + Hỗ trợ phát triển, mở khóa mọi thứ + Bạn có thể ủng hộ chúng tôi chỉ với %s + + Nâng cấp trọn đời với %1$s %2$s + + Mở khoá và tài trợ với %1$s %2$s + Cảm ơn bạn đã ủng hộ QKSMS! + Bây giờ bạn có thể sử dụng tất cả các chức năng của QKSMS+ + QKSMS+ miễn phí cho người sử dụng F-Droid! Bạn có thể trả phí nếu muốn hỗ trợ phát triển. + Tài trợ bằng PayPal + Sắp có + Chủ đề đặc biệt + Mở khóa các màu sắc đẹp không có sẵn trong bản thiết kế Material + Tuỳ biến Emoji tự động + Tạo phím tắt Emoji tự động + Sao lưu tin nhắn + Tự động sao lưu tin nhắn của bạn. Không bao giờ lo lắng về việc mất đi lịch sử của bạn nếu bạn thay đổi điện thoại hoặc điện thoại của bạn bị mất + Hẹn giờ tin nhắn + Tin nhắn hẹn giờ sẽ được tự động gửi vào thời gian cụ thể + Trì hoãn gửi tin nhắn + Chờ một vài giây trước khi gửi tin nhắn để bạn có thể hoàn tác nó + Chế độ ban đêm tự động + Kích hoạt chế độ ban đêm dựa trên thời gian trong ngày + Chặn nâng cao + Chặn tin nhắn có chứa các từ khóa hoặc phù hợp với các mẫu + Tự động chuyển tiếp + Tự động chuyển tiếp tin nhắn từ người gửi + Tự động trả lời + Tự động trả lời các tin nhắn với nội dung cài sẵn + Nhiều tính năng khác + QKSMS đang trong giai đoạn phát triển, và việc thanh toán của bạn sẽ bao gồm tất cả tính năng QKSMS+ tương lai! Đang tải… Xem thêm các cuộc trò chuyện Đánh dấu đã đọc @@ -251,13 +311,14 @@ Gọi Xoá + Đồng ý + Tiếp tục Hủy Xóa Lưu Dừng Xem thêm Chọn - Bỏ chặn Undo Đã sao chép Cuộc trò chuyện đã được lưu trữ @@ -267,7 +328,8 @@ Các tin không gửi Các tin gửi đến %s không thành công - Đã bị vô hiệu hóa + Hệ thống + Vô hiệu hoá Luôn bật Tự động diff --git a/presentation/src/main/res/values-zh-rCN/strings.xml b/presentation/src/main/res/values-zh-rCN/strings.xml index 33342f3626f3cb01231495003db3fde123d3c6b9..ea4cd9606f20854ac610a1c19a65a18a8259fb22 100644 --- a/presentation/src/main/res/values-zh-rCN/strings.xml +++ b/presentation/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,7 @@ 在12月23日发送  创建预约短信 预约短信 现在发送 - 删除 + Copy text + Delete 外观 通用 @@ -190,10 +198,6 @@ 弹出新消息 点击忽略 点击弹窗外部以关闭 - 拦截 - - Should I Answer? - 使用\"Should I Answer\"应用过滤未知号码的消息 延迟发送 滑动操作 配置对话的滑动操作 @@ -206,9 +210,11 @@ 删除 呼叫 标记为已读 + 标记为未读 送达确认 确认短信已成功送达 + 签名 删除读音符号 删除短信中字母上的读音符号 仅限手机号码 @@ -221,10 +227,33 @@ 启用了调试日志记录 调试日志记录已禁用 输入时长 (秒) - 拦截 - 被拦截的对话将出现在这里 - 取消拦截 - 确定不再拦截这个对话吗? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + 使用\"Should I Answer\"应用过滤未知号码的消息 + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + Blocked messages + Your blocked messages will appear here + Block + Unblock + + Continue to %s and block these numbers + + + Continue to %s and allow these numbers + 关于 版本 开发人员 @@ -233,6 +262,37 @@ 联系人 许可证 版权 + 支持开发,解锁全部功能 + 你可以支付%s来拯救一个饥渴的程序猿 + + 终身升级只需 %1$s %2$s + + 解锁并捐赠只需 %1$s %2$s + 感谢你对QKSMS的支持! + 现在你可以使用QKSMS+的全部功能! + QKSMS+对F-Droid用户免费!如果您想支持开发,可以随意捐款。 + 使用PayPal捐赠 + 即将推出 + 高级主题 + 解锁 \"Material Design\" 调色板中不可用的美丽彩色主题 + 自定义自动颜文字 + 创建自定义自动颜文字的快捷方式 + 消息备份 + 自动备份短信,再也不用担心更换或丢失手机后会丢掉短信记录。 + 定时消息 + 安排一个时间来自动发送短信 + 延迟发送 + 发送短信前,稍等几秒 + 自动夜间模式 + 根据时间自动启用夜间模式 + 高级拦截选项 + 根据关键字或匹配模式拦截消息 + 自动转发 + 自动转发特定发件人的短信 + 自动回复 + 使用预设文本自动回复短信 + 更多 + QKSMS积极更新中,您的购买将包含以后QKSMS+的所有功能 载入中... 查看更多对话 标记为已读 @@ -251,13 +311,14 @@ 呼叫 删除 + Yes + Continue 取消 删除 保存 停止 更多 设置 - 取消拦截 撤销 已复制 已存档的对话 @@ -267,9 +328,10 @@ 消息未发送 给%s的短信发送失败 - 禁用 - 保持打开 - 自动 + System + Disabled + Always on + Automatic 显示姓名和消息 diff --git a/presentation/src/main/res/values-zh/strings.xml b/presentation/src/main/res/values-zh/strings.xml index 8d783147607305b4e188692847f3fe8a4d494ceb..4e04f74a61b56d062b012c5ae1a2f5a114a0cab6 100644 --- a/presentation/src/main/res/values-zh/strings.xml +++ b/presentation/src/main/res/values-zh/strings.xml @@ -1,7 +1,7 @@ - New conversation - Compose - Shortcut disabled + 新對話 + 撰寫 + 捷徑已停用 封存 設定 通知 @@ -39,13 +39,15 @@ 封存 取消封存 刪除 - Pin to top - Unpin + 新增聯絡人 + 置頂至頂部 + 取消置頂 標記為已讀 標記為未讀 攔截 正在同步... - 你:%s + 您:%s + 草稿: %s 信息中的結果 %d 條信息 對話會在這裡顯示 @@ -62,29 +64,35 @@ 收件箱 已存檔 已計劃 - Blocking + 封鎖 更多 設定 幫助與反饋 邀請朋友 + 解鎖驚人的新特性並支持開發 + 喜歡 QKSMS? + 在 Google Play 分享您的感受,並給我們評分! + 好! + 放棄 刪除 - Are you sure you would like to delete %d conversations? + 您確定要刪除這 %d 段對話嗎? 複製文本 轉寄 刪除 - 已選取%d - 已顯示%2$d條中的%1$d條結果 + 已選取 %d 個項目 + 已顯示 %2$d 條中的 %1$d 條結果 群發訊息 收件人和回復對每個人都是可見的 - 這是你談話的開始。說點好聽的吧! + 這是您談話的開始。說點好聽的吧! 連絡人卡片 - Scheduled for - Selected time must be in the future! - Added to scheduled messages + 排程於 + 所選時間必須是未來時間 + 您必須解鎖 QKSMS+ 以使用排程簡訊 + 已新增至排程訊息 寫一條訊息... 複製文本 轉寄 @@ -106,17 +114,16 @@ 添加附件 附加照片 拍攝照片 - Schedule message - Attach a contact - Error reading contact - %s selected, change SIM card + 排程訊息 + 附加聯絡人 + 讀取聯絡人時出錯 + %s 已選擇,變更 SIM 卡 發送訊息 正在發送... 已傳遞 %s 發送失敗,點擊重試 詳細資訊 會話標題 - 輸入標題 通知 主題 存檔 @@ -124,45 +131,48 @@ 攔截 取消攔截 刪除對話 - Couldn\'t load media + 無法載入媒體 保存到圖庫 - Backup and restore - Backing up messages - Restoring from backup - Last backup - Loading… - Never - Restore - Select a backup - Backup in progress… - Restore in progress… - Restore from backup - Are you sure you would like to restore your messages from this backup? - Stop restore - Messages that have already been restored will remain on your device - Backups - No backups found + 備份和還原 + 正在備份訊息 + 正在從備份還原 + 上次備份 + 載入中… + 從未備份 + 還原 + 選擇備份 + 請解鎖 QKSMS+ 以使用備份和還原功能 + 正在進行備份… + 正在進行還原… + 從備份還原 + 您確定要從此備份還原訊息嗎? + 停止還原 + 已還原的訊息將保留在您的裝置上 + 備份 + 未能找到備份 - %d messages + %d 條訊息 - Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! - Backup now - Parsing backup… - %d/%d messages - Saving backup… - Syncing messages… - Finished! - Backup and restore - Scheduled - Automatically send a message, at the exact moment you\'d like - Hey! When was your birthday again? - It\'s on December 23rd - Happy birthday! Look at what a great friend I am, remembering your birthday - Sending on December 23rd  - Schedule a message - Scheduled message + 目前只有簡訊支援備份和還原,多媒體和排程簡訊備份將在不久後加入! + 立即備份 + 正在解析備份… + %d/%d 條訊息 + 正在儲存備份… + 正在同步訊息… + 完成! + 備份和還原 + 排程 + 在您指定的時間自動傳送訊息 + 嘿,您的生日是什麼時候? + 是 12 月 23 日 + 生日快樂!看看我是一個多麼棒的朋友,我還記着你的生日! + + 在 12 月 23 日傳送 + 新增排程訊息 + 排程訊息 立即發送 + 複製文字 刪除 外觀 @@ -177,8 +187,8 @@ 使用系統字體 自動 emoji 表情 通知 - Tap to customize - Actions + 輕觸以跳轉至系統設定 + 操作 按鈕1 按鈕2 按鈕3 @@ -190,41 +200,62 @@ 彈出新訊息 點擊忽略 在快顯視窗外點擊以關閉它 - 攔截 - - Should I Answer? - 使用\"Should I Answer\"應用過濾未知號碼的消息 延遲發送 滑動操作 配置對話的滑動操作 向右滑動 向左滑動 - CHANGE + 變更 - None - Archive - Delete - Call - Mark read + + 封存 + 刪除 + 呼叫 + 標示已讀 + 標示未讀 送達確認 確認訊息已成功送達 + 簽名 刪除讀音符號 刪除訊息中字母上的讀音符號 - Mobile numbers only - When composing a message, only show mobile numbers + 僅限手機號碼 + 撰寫訊息時,只顯示手機號碼 自動壓縮MMS附件 同步消息 重新與安卓原生訊息數據庫進行同步 關於Message 版本 %s - Debug logging enabled - Debug logging disabled + 除錯日誌已啓用 + 出錯日誌已停用 輸入時長 (秒) - 攔截 - 被攔截的對話將出現在這裡 - 取消攔截 - 確定不再攔截這個對話嗎? + 封鎖 + 直接刪除 + 直接刪除封鎖的簡訊,而非只有隱藏簡訊 + 封鎖的對話 + 封鎖管理器 + QKSMS + QKSMS內建封鎖功能 + 自動過濾來電與簡訊!群眾智慧Community IQ™能讓你遠離不想要的垃圾簡訊 + 使用\"Should I Answer\"應用過濾未知號碼的消息 + 複製封鎖號碼 + 正在複製你現有的封鎖號碼%s + 封鎖號碼 + 封鎖號碼會顯示在這裡 + 封鎖新號碼 + 輸入封鎖號碼 + 電話號碼 + 封鎖 + 封鎖的訊息 + 封鎖的訊息會顯示在這裡 + 封鎖 + 解除封鎖 + + 正在封鎖這些號碼 %s + + + 正在解鎖這些號碼%s + 關於 版本 開發者 @@ -233,11 +264,42 @@ 聯絡資訊 授權 版權 + 支持開發,解鎖全部功能 + 您可以支付 %s 來拯救一個飢餓的開發者 + + 終身升級只需 %1$s %2$s + + 解鎖並捐贈只需 %1$s %2$s + 感謝您對 QKSMS 的支持! + 現在您可以使用 QKSMS+ 的全部功能! + QKSMS+對F-Droid用戶免費!如果您想支持開發,可以隨意捐款。 + 使用PayPal捐贈 + 即將推出 + 高級主題 + 解鎖 \"Material Design\" 調色板中不可用的美麗彩色主題 + 自定義自動emoji + 創建自定義自動emoji的快捷方式 + 訊息備份 + 自動備份訊息,再也不用擔心更換或丟失手機後會丟掉訊息記錄。 + 计划訊息 + 安排一個時間來自動計劃訊息 + 延遲發送 + 發送訊息前,稍等幾秒 + 自動夜間模式 + 根據時間自動啟用夜間模式 + 高級攔截選項 + 根據關鍵字或匹配模式攔截消息 + 自動轉寄 + 自動轉寄特定發件人的訊息 + 自動回覆 + 使用預設回應自動回覆傳入郵件 + 更多 + QKSMS 正在積極開發中,您的購買將包含後續 QKSMS+ 的所有功能! 載入中... 查看更多對話 標記為已讀 致電 - Delete + 刪除 顯示更多 顯示較少 打開對話 @@ -245,19 +307,20 @@ HEX 應用 - None - Mark read - Reply - Call - Delete + + 標示已讀 + 回覆 + 呼叫 + 刪除 + 確定 + 繼續 取消 刪除 儲存 - Stop + 停止 更多 設置 - 取消攔截 復原 已複製 存檔的會話 @@ -267,8 +330,9 @@ 消息未發送 給%s的訊息發送失敗 - 禁用 - 保持打開 + 跟隨系統 + 停用 + 啓用 自動 diff --git a/presentation/src/main/res/values/attrs.xml b/presentation/src/main/res/values/attrs.xml index 9677ad8f1fa47268e0d0f2b4cf2419c1e8b7a970..3d79ccfe300cf492cd54510eb8ebfab337a9b1c7 100644 --- a/presentation/src/main/res/values/attrs.xml +++ b/presentation/src/main/res/values/attrs.xml @@ -1,6 +1,6 @@ + + + + - - - - - - - + + + + @@ -34,6 +35,8 @@ + + @@ -47,11 +50,16 @@ - - - - + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/colors.xml b/presentation/src/main/res/values/colors.xml index fa99d77c98ae43f2c048594cd12c8c5ad6add82e..11813531934dcd5037aad3e96d66f599001ba7ff 100644 --- a/presentation/src/main/res/values/colors.xml +++ b/presentation/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ Sending on December 23rd  Schedule a message Scheduled message Send now + Copy text Delete @@ -210,10 +221,6 @@ Popup for new messages Tap to dismiss Tap outside of the popup to close it - Blocking - - Should I Answer? - Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app Delayed sending Swipe actions Configure swipe actions for conversations @@ -225,10 +232,12 @@ Archive Delete Call - Mark read + Mark as read + Mark as unread Delivery confirmations Confirm that messages were sent successfully + Signature Strip accents Remove accents from characters in outgoing SMS messages Mobile numbers only @@ -243,10 +252,41 @@ Enter duration (seconds) - Blocking - Your blocked conversations will appear here - Unblock - Would you like to unblock this conversation? + Blocking + Drop messages + Drop incoming messages from blocked senders instead of hiding them + Blocked conversations + + Blocking Manager + QKSMS + Built-in blocking functionality in QKSMS + Call Control + Automatically filter your calls and messages in one convenient place! Community IQ™ allows you to prevent unwanted messages from community known spammers + Should I Answer? + Automatically filter messages from unsolicited numbers by using the \"Should I Answer\" app + Copy blocked numbers + Continue to %s and copy over your existing blocked numbers + + Blocked numbers + Your blocked numbers will appear here + Block a new number + Block texts from + Phone number + Block + + Blocked messages + Your blocked messages will appear here + + Block + Unblock + + Continue to %s and block this number + Continue to %s and block these numbers + + + Continue to %s and allow this number + Continue to %s and allow these numbers + About Version @@ -255,10 +295,45 @@ License Copyright + Support development, unlock everything + You can save a starving developer for just %s + + Lifetime upgrade for %1$s %2$s + Unlock for %1$s %2$s + Unlock forever for %1$s %2$s + One-time purchase of %1$s %2$s + + Unlock + donate for %1$s %2$s + Thank you for supporting QKSMS! + You now have access to all QKSMS+ features + QKSMS+ is free for F-Droid users! If you\'d like to support development, a donation would be highly appreciated. + Donate via PayPal + Coming soon + Premium themes + Unlock beautiful theme colors not available on the Material Design palette + Custom auto-emoji + Create custom auto-emoji shortcuts + Message backup + Automatically back up your messages. Never again worry about losing your history if you change phones or your phone gets lost + Scheduled messages + Schedule messages to automatically be sent at a specific time and date + Delayed sending + Wait a few seconds before sending your message + Automatic night mode + Enable night mode based on the time of day + Advanced blocking + Block messages that contain keywords or match patterns + Auto-forward + Automatically forward messages from certain senders + Auto-respond + Automatically respond to incoming messages with a preset response + More + QKSMS is under active development, and your purchase will include all future QKSMS+ features! + Loading… View more conversations - Mark read + Mark as read Call Delete Show more @@ -266,25 +341,41 @@ Open conversation Material - Color Picker + QKSMS+ HEX Apply + Moez Bhatti + https://github.com/moezbhatti/qksms + https://github.com/moezbhatti/qksms/releases + moez@qklabs.com + GNU General Public License v3.0 + © 2014–2019 + + New in QKSMS + Version %s + Added + Improved + Fixed + More + Dismiss + You must unlock QKSMS+ to use this None - Mark read + Mark as read Reply Call Delete + Yes + Continue Cancel Delete Save Stop More Set - Unblock Undo Copied @@ -298,6 +389,7 @@ The message to %s failed to send + System Disabled Always on Automatic diff --git a/presentation/src/main/res/values/styles.xml b/presentation/src/main/res/values/styles.xml index 830df313e712b29d964356ffd6b47e3534e1c493..c8e1eab028f12eb3fd685ca8f1f540254cd18fa4 100644 --- a/presentation/src/main/res/values/styles.xml +++ b/presentation/src/main/res/values/styles.xml @@ -1,5 +1,5 @@ - - - - - - - - - - - - - - - - + +