added launch presentation related code
This commit is contained in:
parent
5afe8c3a37
commit
7c19db15d3
|
@ -32,7 +32,7 @@ allprojects {
|
|||
subprojects {
|
||||
buildscript {
|
||||
ext {
|
||||
kotlinVersion = '1.1.3-2'
|
||||
kotlinVersion = '1.1.4-2'
|
||||
pluginVersions = [
|
||||
AndroidSvgDrawable: '3.0.0',
|
||||
Fabric : '1.22.1',
|
||||
|
@ -40,10 +40,10 @@ subprojects {
|
|||
PlayServices : '3.1.0',
|
||||
]
|
||||
libVersions = [
|
||||
Kotlin : '1.1.3-2',
|
||||
Kotlin : '1.1.4-2',
|
||||
SupportLib : '26.0.1',
|
||||
SupportTest : '1.0.0',
|
||||
MariotakuCommons : '0.9.15',
|
||||
MariotakuCommons : '0.9.17',
|
||||
RestFu : '0.9.57',
|
||||
ObjectCursor : '0.9.20',
|
||||
PlayServices : '11.0.4',
|
||||
|
|
|
@ -198,7 +198,7 @@ dependencies {
|
|||
implementation "com.github.mariotaku.CommonsLibrary:io:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:text:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:text-kotlin:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:emojione:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:emojione-android:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:objectcursor:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku.CommonsLibrary:logansquare:${libVersions['MariotakuCommons']}"
|
||||
implementation "com.github.mariotaku:KPreferences:${libVersions['KPreferences']}"
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.Choreographer;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/20.
|
||||
*/
|
||||
|
||||
public abstract class ChoreographerCompat {
|
||||
|
||||
private ChoreographerCompat() {
|
||||
|
||||
}
|
||||
|
||||
public abstract void postFrameCallback(ChoreographerCompat.FrameCallback callback);
|
||||
|
||||
public abstract void postFrameCallbackDelayed(ChoreographerCompat.FrameCallback callback, long delayMillis);
|
||||
|
||||
public abstract void removeFrameCallback(ChoreographerCompat.FrameCallback callback);
|
||||
|
||||
public static ChoreographerCompat getInstance() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return ChoreographerCompatImpl.getCompatInstance();
|
||||
}
|
||||
return ChoreographerNativeDelegate.getNativeInstance();
|
||||
}
|
||||
|
||||
public interface FrameCallback {
|
||||
void doFrame(long frameTimeNanos);
|
||||
}
|
||||
|
||||
private static class ChoreographerCompatImpl extends ChoreographerCompat {
|
||||
|
||||
private static final Map<Looper, ChoreographerCompat> sInstances = new WeakHashMap<>();
|
||||
|
||||
private final Handler handler;
|
||||
|
||||
ChoreographerCompatImpl(Looper looper) {
|
||||
handler = new Handler(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postFrameCallback(FrameCallback callback) {
|
||||
handler.postDelayed(CompatFrameCallbackWrapper.wrap(callback), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
|
||||
handler.postDelayed(CompatFrameCallbackWrapper.wrap(callback), delayMillis + 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrameCallback(FrameCallback callback) {
|
||||
handler.removeCallbacks(CompatFrameCallbackWrapper.wrap(callback));
|
||||
}
|
||||
|
||||
static ChoreographerCompat getCompatInstance() {
|
||||
final Looper looper = Looper.myLooper();
|
||||
ChoreographerCompat instance = sInstances.get(looper);
|
||||
if (instance != null) return instance;
|
||||
instance = new ChoreographerCompatImpl(looper);
|
||||
sInstances.put(looper, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private static class CompatFrameCallbackWrapper implements Runnable {
|
||||
private static final Map<FrameCallback, Runnable> sInstances = new WeakHashMap<>();
|
||||
private final FrameCallback callback;
|
||||
|
||||
private CompatFrameCallbackWrapper(ChoreographerCompat.FrameCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
callback.doFrame(System.nanoTime());
|
||||
}
|
||||
|
||||
static Runnable wrap(FrameCallback callback) {
|
||||
Runnable wrapper = sInstances.get(callback);
|
||||
if (wrapper != null) return wrapper;
|
||||
wrapper = new CompatFrameCallbackWrapper(callback);
|
||||
sInstances.put(callback, wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
private static class ChoreographerNativeDelegate extends ChoreographerCompat {
|
||||
|
||||
private static final Map<Choreographer, ChoreographerCompat> sInstances = new WeakHashMap<>();
|
||||
|
||||
private final Choreographer implementation;
|
||||
|
||||
ChoreographerNativeDelegate(Choreographer implementation) {
|
||||
this.implementation = implementation;
|
||||
}
|
||||
|
||||
public void postFrameCallback(ChoreographerCompat.FrameCallback callback) {
|
||||
implementation.postFrameCallback(NativeFrameCallbackWrapper.wrap(callback));
|
||||
}
|
||||
|
||||
public void postFrameCallbackDelayed(ChoreographerCompat.FrameCallback callback, long delayMillis) {
|
||||
implementation.postFrameCallbackDelayed(NativeFrameCallbackWrapper.wrap(callback), delayMillis);
|
||||
}
|
||||
|
||||
public void removeFrameCallback(ChoreographerCompat.FrameCallback callback) {
|
||||
implementation.removeFrameCallback(NativeFrameCallbackWrapper.wrap(callback));
|
||||
}
|
||||
|
||||
static ChoreographerCompat getNativeInstance() {
|
||||
final Choreographer implementation = Choreographer.getInstance();
|
||||
ChoreographerCompat instance = sInstances.get(implementation);
|
||||
if (instance != null) return instance;
|
||||
instance = new ChoreographerNativeDelegate(implementation);
|
||||
sInstances.put(implementation, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static class NativeFrameCallbackWrapper implements Choreographer.FrameCallback {
|
||||
private static final Map<FrameCallback, Choreographer.FrameCallback> sInstances = new WeakHashMap<>();
|
||||
private final FrameCallback callback;
|
||||
|
||||
private NativeFrameCallbackWrapper(ChoreographerCompat.FrameCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
callback.doFrame(frameTimeNanos);
|
||||
}
|
||||
|
||||
static Choreographer.FrameCallback wrap(ChoreographerCompat.FrameCallback callback) {
|
||||
Choreographer.FrameCallback wrapper = sInstances.get(callback);
|
||||
if (wrapper != null) return wrapper;
|
||||
wrapper = new NativeFrameCallbackWrapper(callback);
|
||||
sInstances.put(callback, wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
@ -54,13 +55,10 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("@t_deyarmin @nixcraft @mariotaku Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
assertExcludedMatches(emptyArray(), statusUpdate)
|
||||
activity.finish()
|
||||
|
@ -76,13 +74,10 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("@t_deyarmin Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
assertExcludedMatches(arrayOf("17484680", "57610574"), statusUpdate)
|
||||
activity.finish()
|
||||
|
@ -98,13 +93,10 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
Assert.assertEquals("https://twitter.com/t_deyarmin/status/847950697987493888",
|
||||
statusUpdate.attachment_url)
|
||||
|
@ -122,13 +114,10 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("@TwidereProject @mariotaku Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
assertExcludedMatches(emptyArray(), statusUpdate)
|
||||
activity.finish()
|
||||
|
@ -144,13 +133,10 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("@TwidereProject Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
assertExcludedMatches(arrayOf("57610574"), statusUpdate)
|
||||
activity.finish()
|
||||
|
@ -166,18 +152,23 @@ class ComposeActivityTest {
|
|||
intent.putExtra(EXTRA_STATUS, status)
|
||||
intent.putExtra(EXTRA_SAVE_DRAFT, true)
|
||||
val activity = activityRule.launchActivity(intent)
|
||||
val getStatusUpdate = activity.javaClass.getDeclaredMethod("getStatusUpdate").apply {
|
||||
isAccessible = true
|
||||
}
|
||||
activityRule.runOnUiThread {
|
||||
activity.editText.setText("Test Reply")
|
||||
}
|
||||
val statusUpdate = getStatusUpdate(activity) as ParcelableStatusUpdate
|
||||
val statusUpdate = activity.getStatusUpdateTest(false)
|
||||
Assert.assertEquals("Test Reply", statusUpdate.text)
|
||||
assertExcludedMatches(arrayOf("583328497", "57610574"), statusUpdate)
|
||||
activity.finish()
|
||||
}
|
||||
|
||||
private fun Activity.getStatusUpdateTest(checkLength: Boolean): ParcelableStatusUpdate {
|
||||
val getStatusUpdate = javaClass.getDeclaredMethod("getStatusUpdate",
|
||||
kotlin.Boolean::class.java).apply {
|
||||
isAccessible = true
|
||||
}
|
||||
return getStatusUpdate(this, checkLength) as ParcelableStatusUpdate
|
||||
}
|
||||
|
||||
private fun assertExcludedMatches(expectedIds: Array<String>, statusUpdate: ParcelableStatusUpdate): Boolean {
|
||||
return statusUpdate.excluded_reply_user_ids?.all { excludedId ->
|
||||
expectedIds.any { expectation ->
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/19.
|
||||
*/
|
||||
class CronExpressionTest {
|
||||
|
||||
@Test
|
||||
fun testMatches() {
|
||||
// @daily (0:00 every day)
|
||||
val cal0h0m = Calendar.getInstance().apply {
|
||||
set(Calendar.HOUR_OF_DAY, 0)
|
||||
set(Calendar.MINUTE, 0)
|
||||
}
|
||||
// Every 1:00, 15th day of month
|
||||
val cal15dom1h0m = Calendar.getInstance(TimeZone.getTimeZone("JST")).apply {
|
||||
set(Calendar.HOUR_OF_DAY, 1)
|
||||
set(Calendar.MINUTE, 0)
|
||||
set(Calendar.DAY_OF_MONTH, 15)
|
||||
}
|
||||
Assert.assertTrue(CronExpression.valueOf("0 0 * * *").matches(cal0h0m))
|
||||
Assert.assertFalse(CronExpression.valueOf("0 0 * * *").matches(cal15dom1h0m))
|
||||
|
||||
// Here comes the timezone related part
|
||||
Assert.assertTrue(CronExpression.valueOf("0 1 15 * *").matches(cal15dom1h0m))
|
||||
}
|
||||
|
||||
class FieldTest {
|
||||
@Test
|
||||
fun testParseBasic() {
|
||||
Assert.assertSame(CronExpression.AnyField.INSTANCE, CronExpression.FieldType.MINUTE.parseField("*"))
|
||||
Assert.assertArrayEquals(arrayOf(CronExpression.Range.single(0), CronExpression.Range.single(1)),
|
||||
(CronExpression.FieldType.DAY_OF_WEEK.parseField("SUN,MON") as CronExpression.BasicField).ranges)
|
||||
Assert.assertArrayEquals(arrayOf(CronExpression.Range.single(0), CronExpression.Range(1, 5)),
|
||||
(CronExpression.FieldType.DAY_OF_WEEK.parseField("SUN,MON-FRI") as CronExpression.BasicField).ranges)
|
||||
}
|
||||
}
|
||||
|
||||
class RangeTest {
|
||||
|
||||
@Test
|
||||
fun testParse() {
|
||||
Assert.assertEquals(CronExpression.Range(0, 6), CronExpression.Range.parse("0-6", CronExpression.Range(0, 10), null))
|
||||
Assert.assertEquals(CronExpression.Range.single(0), CronExpression.Range.parse("SUN", CronExpression.Range(0, 7), CronExpression.FieldType.DAY_OF_WEEK.textRepresentations))
|
||||
Assert.assertEquals(CronExpression.Range(0, 3), CronExpression.Range.parse("SUN-WED", CronExpression.Range(0, 7), CronExpression.FieldType.DAY_OF_WEEK.textRepresentations))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package org.mariotaku.twidere.util
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2016/12/15.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AnalyzerTest {
|
||||
@Test
|
||||
fun testGetInstance() {
|
||||
if (BuildConfig.FLAVOR.contains("google")) {
|
||||
Assert.assertNotNull(Analyzer.implementation)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import android.support.test.InstrumentationRegistry
|
|||
import android.support.test.runner.AndroidJUnit4
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mariotaku.twidere.model.filter.UrlFiltersSubscriptionProviderArguments
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/11.
|
||||
|
@ -20,7 +21,7 @@ class UrlFiltersSubscriptionProviderTest {
|
|||
if (!(cm.activeNetworkInfo?.isConnected ?: false)) return
|
||||
|
||||
val url = "https://raw.githubusercontent.com/mariotaku/wtb/master/twidere/bots.xml"
|
||||
val arguments = UrlFiltersSubscriptionProvider.Arguments().apply {
|
||||
val arguments = UrlFiltersSubscriptionProviderArguments().apply {
|
||||
this.url = url
|
||||
}
|
||||
val provider = UrlFiltersSubscriptionProvider(context, arguments)
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Twidere.NoActionBar"
|
||||
android:theme="@style/Theme.Twidere.Launcher"
|
||||
android:windowSoftInputMode="adjustNothing">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
@ -181,7 +181,7 @@
|
|||
android:icon="@mipmap/ic_launcher_hondajojo"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.Twidere.NoActionBar"
|
||||
android:theme="@style/Theme.Twidere.Launcher"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
|
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Single-file Cron expression parser
|
||||
* <p>
|
||||
* Supports POSIX standard syntax only
|
||||
*/
|
||||
public class CronExpression {
|
||||
|
||||
/**
|
||||
* Equivalent to {@code 0 0 1 1 *}
|
||||
*/
|
||||
public static final CronExpression YEARLY = new CronExpression(new Field[]{
|
||||
BasicField.zero(FieldType.MINUTE),
|
||||
BasicField.zero(FieldType.HOUR_OF_DAY),
|
||||
BasicField.one(FieldType.DAY_OF_MONTH),
|
||||
BasicField.one(FieldType.MONTH),
|
||||
AnyField.INSTANCE,
|
||||
null,
|
||||
});
|
||||
|
||||
public static final CronExpression ANNUALLY = YEARLY;
|
||||
|
||||
/**
|
||||
* Equivalent to {@code 0 0 1 * *}
|
||||
*/
|
||||
public static final CronExpression MONTHLY = new CronExpression(new Field[]{
|
||||
BasicField.zero(FieldType.MINUTE),
|
||||
BasicField.zero(FieldType.HOUR_OF_DAY),
|
||||
BasicField.one(FieldType.DAY_OF_MONTH),
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Equivalent to {@code 0 0 * * 0}
|
||||
*/
|
||||
public static final CronExpression WEEKLY = new CronExpression(new Field[]{
|
||||
BasicField.zero(FieldType.MINUTE),
|
||||
BasicField.zero(FieldType.HOUR_OF_DAY),
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
BasicField.zero(FieldType.DAY_OF_WEEK),
|
||||
null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Equivalent to {@code 0 0 * * *}
|
||||
*/
|
||||
public static final CronExpression DAILY = new CronExpression(new Field[]{
|
||||
BasicField.zero(FieldType.MINUTE),
|
||||
BasicField.zero(FieldType.HOUR_OF_DAY),
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
null,
|
||||
});
|
||||
|
||||
/**
|
||||
* Equivalent to {@code 0 * * * *}
|
||||
*/
|
||||
public static final CronExpression HOURLY = new CronExpression(new Field[]{
|
||||
BasicField.zero(FieldType.MINUTE),
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
AnyField.INSTANCE,
|
||||
null,
|
||||
});
|
||||
|
||||
private Field[] fields;
|
||||
|
||||
private CronExpression(@NonNull Field[] fields) {
|
||||
if (fields.length < 5) throw new IllegalArgumentException("Fields count must >= 5");
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static CronExpression valueOf(@NonNull String string) throws ParseException {
|
||||
if (string.length() == 0) {
|
||||
throw new ParseException("Cron expression is empty", -1);
|
||||
}
|
||||
if (string.charAt(0) == '@') {
|
||||
// Parse predefined
|
||||
final String substr = string.substring(1);
|
||||
switch (substr) {
|
||||
case "yearly":
|
||||
return YEARLY;
|
||||
case "annually":
|
||||
return ANNUALLY;
|
||||
case "monthly":
|
||||
return MONTHLY;
|
||||
case "weekly":
|
||||
return WEEKLY;
|
||||
case "daily":
|
||||
return DAILY;
|
||||
case "hourly":
|
||||
return HOURLY;
|
||||
}
|
||||
throw new ParseException("Unknown pre-defined value " + substr, 1);
|
||||
}
|
||||
final String[] segments = StringUtils.split(string, ' ');
|
||||
if (segments.length > 6) {
|
||||
throw new ParseException("Unrecognized segments " + string, -1);
|
||||
}
|
||||
|
||||
// Parse minute field
|
||||
Field[] fields = new Field[6];
|
||||
fields[0] = FieldType.MINUTE.parseField(segments[0]);
|
||||
// Parse hour field
|
||||
fields[1] = FieldType.HOUR_OF_DAY.parseField(segments[1]);
|
||||
// Parse day-of-month field
|
||||
fields[2] = FieldType.DAY_OF_MONTH.parseField(segments[2]);
|
||||
// Parse month field
|
||||
fields[3] = FieldType.MONTH.parseField(segments[3]);
|
||||
// Parse day-of-week field
|
||||
fields[4] = FieldType.DAY_OF_WEEK.parseField(segments[4]);
|
||||
return new CronExpression(fields);
|
||||
}
|
||||
|
||||
public boolean matches(Calendar cal) {
|
||||
for (Field field : fields) {
|
||||
if (field == null) continue;
|
||||
if (!field.contains(cal)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toExpression() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, j = fields.length; i < j; i++) {
|
||||
Field field = fields[i];
|
||||
if (field == null) continue;
|
||||
if (i != 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(field.toExpression());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
interface Field {
|
||||
boolean contains(Calendar cal);
|
||||
|
||||
String toExpression();
|
||||
}
|
||||
|
||||
public enum FieldType {
|
||||
|
||||
MINUTE(Calendar.MINUTE, 0, new Range(0, 59), null),
|
||||
HOUR_OF_DAY(Calendar.HOUR_OF_DAY, 0, new Range(0, 23), null),
|
||||
DAY_OF_MONTH(Calendar.DAY_OF_MONTH, 0, new Range(1, 31), null),
|
||||
MONTH(Calendar.MONTH, 1, new Range(1, 12), new String[]{"JAN", "FEB", "MAR", "APR", "JUN", "JUL",
|
||||
"AUG", "SEP", "OCT", "NOV", "DEC"}),
|
||||
DAY_OF_WEEK(Calendar.DAY_OF_WEEK, -1, new Range(0, 6), new String[]{"SUN", "MON", "TUE", "WED", "THU",
|
||||
"FRI", "SAT"}),
|
||||
/* Used in nncron, not decided whether to implement */
|
||||
YEAR(Calendar.YEAR, 0, new Range(1970, 2099), null);
|
||||
|
||||
final int calendarField;
|
||||
final int calendarOffset;
|
||||
final Range allowedRange;
|
||||
final String[] textRepresentations;
|
||||
|
||||
FieldType(int calendarField, int calendarOffset, Range allowedRange,
|
||||
@Nullable String[] textRepresentations) {
|
||||
this.calendarField = calendarField;
|
||||
this.calendarOffset = calendarOffset;
|
||||
this.allowedRange = allowedRange;
|
||||
this.textRepresentations = textRepresentations;
|
||||
}
|
||||
|
||||
Field parseField(@NonNull String text) throws ParseException {
|
||||
if ("*".equals(text)) return AnyField.INSTANCE;
|
||||
return BasicField.parse(text, this);
|
||||
}
|
||||
}
|
||||
|
||||
enum AnyField implements Field {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public boolean contains(Calendar cal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExpression() {
|
||||
return "*";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* POSIX-compliant CRON field
|
||||
*/
|
||||
final static class BasicField implements Field {
|
||||
|
||||
@NonNull
|
||||
final Range[] ranges;
|
||||
final int calendarField;
|
||||
final int calendarOffset;
|
||||
|
||||
BasicField(@NonNull final Range[] ranges, int calendarField, int calendarOffset) {
|
||||
this.ranges = ranges;
|
||||
this.calendarField = calendarField;
|
||||
this.calendarOffset = calendarOffset;
|
||||
}
|
||||
|
||||
public static Field parse(String text, FieldType fieldType) throws ParseException {
|
||||
final Range allowedRange = fieldType.allowedRange;
|
||||
final String[] rangeStrings = StringUtils.split(text, ',');
|
||||
final Range[] ranges = new Range[rangeStrings.length];
|
||||
final String[] textRepresentations = fieldType.textRepresentations;
|
||||
for (int i = 0, l = rangeStrings.length; i < l; i++) {
|
||||
String rangeString = rangeStrings[i];
|
||||
ranges[i] = Range.parse(rangeString, allowedRange, textRepresentations);
|
||||
if (!allowedRange.contains(ranges[i])) {
|
||||
throw new ParseException(ranges[i] + " out of range " + allowedRange, -1);
|
||||
}
|
||||
}
|
||||
return new BasicField(ranges, fieldType.calendarField, fieldType.calendarOffset);
|
||||
}
|
||||
|
||||
public static Field zero(FieldType fieldType) {
|
||||
final Range[] ranges = {Range.single(0)};
|
||||
return new BasicField(ranges, fieldType.calendarField, fieldType.calendarOffset);
|
||||
}
|
||||
|
||||
public static Field one(FieldType fieldType) {
|
||||
final Range[] ranges = {Range.single(1)};
|
||||
return new BasicField(ranges, fieldType.calendarField, fieldType.calendarOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Calendar cal) {
|
||||
final int cmp = cal.get(calendarField) + calendarOffset;
|
||||
for (Range range : ranges) {
|
||||
if (range.contains(cmp)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExpression() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, j = ranges.length; i < j; i++) {
|
||||
Range range = ranges[i];
|
||||
if (i != 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(range.toExpression());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
final static class DayOfMonthField implements Field {
|
||||
static Field parse(String text, @NonNull Range allowedRange,
|
||||
@Nullable String[] textRepresentations) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Calendar cal) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExpression() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
final static class DayOfWeekField implements Field {
|
||||
|
||||
public static Field parse(String text, @NonNull Range allowedRange,
|
||||
@Nullable String[] textRepresentations) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Calendar cal) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toExpression() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
final static class Range {
|
||||
final int start, endInclusive;
|
||||
|
||||
Range(int start, int endInclusive) {
|
||||
if (endInclusive < start) {
|
||||
throw new IllegalArgumentException("endInclusive < start");
|
||||
}
|
||||
this.start = start;
|
||||
this.endInclusive = endInclusive;
|
||||
}
|
||||
|
||||
public boolean contains(int num) {
|
||||
return num >= start && num <= endInclusive;
|
||||
}
|
||||
|
||||
public boolean contains(Range that) {
|
||||
return this.start <= that.start && this.endInclusive >= that.endInclusive;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return endInclusive - start + 1;
|
||||
}
|
||||
|
||||
public int valueAt(int index) {
|
||||
if (index >= size()) {
|
||||
throw new IndexOutOfBoundsException("Range index out of bounds");
|
||||
}
|
||||
return start + index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Range range = (Range) o;
|
||||
|
||||
if (start != range.start) return false;
|
||||
return endInclusive == range.endInclusive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = start;
|
||||
result = 31 * result + endInclusive;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Range{" +
|
||||
"start=" + start +
|
||||
", endInclusive=" + endInclusive +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static Range single(int num) {
|
||||
return new Range(num, num);
|
||||
}
|
||||
|
||||
public static Range parse(String string, Range allowedRange,
|
||||
@Nullable String[] textRepresentations) throws ParseException {
|
||||
int dashIdx = string.indexOf('-');
|
||||
if (dashIdx == -1) {
|
||||
return single(parseNumber(string, allowedRange, textRepresentations));
|
||||
}
|
||||
final int start = parseNumber(string.substring(0, dashIdx), allowedRange,
|
||||
textRepresentations);
|
||||
final int endInclusive = parseNumber(string.substring(dashIdx + 1, string.length()),
|
||||
allowedRange, textRepresentations);
|
||||
return new Range(start, endInclusive);
|
||||
}
|
||||
|
||||
private static int parseNumber(String input, Range allowedRange,
|
||||
@Nullable String[] textRepresentations) throws ParseException {
|
||||
if (textRepresentations != null) {
|
||||
int textRepresentationIndex = ArrayUtils.indexOf(textRepresentations, input);
|
||||
if (textRepresentationIndex != ArrayUtils.INDEX_NOT_FOUND) {
|
||||
return allowedRange.valueAt(textRepresentationIndex);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(input);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ParseException(e.getMessage(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
public String toExpression() {
|
||||
if (endInclusive == start) return Integer.toString(start);
|
||||
return start + "-" + endInclusive;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model.filter;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/20.
|
||||
*/
|
||||
@JsonObject
|
||||
public class UrlFiltersSubscriptionProviderArguments {
|
||||
@JsonField(name = "url")
|
||||
private String url;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model.presentation;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.mariotaku.commons.logansquare.StringBasedListTypeConverter;
|
||||
import org.mariotaku.twidere.model.CronExpression;
|
||||
import org.mariotaku.twidere.model.util.CronExpressionConverter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/16.
|
||||
*/
|
||||
@JsonObject
|
||||
public class LaunchPresentation {
|
||||
|
||||
@JsonField(name = "images")
|
||||
List<Image> images;
|
||||
|
||||
@JsonField(name = "locales", typeConverter = Locale.ListConverter.class)
|
||||
List<Locale> locales;
|
||||
|
||||
@JsonField(name = "url")
|
||||
String url;
|
||||
|
||||
@JsonField(name = "schedule")
|
||||
Schedule schedule;
|
||||
|
||||
@JsonField(name = "is_promotion")
|
||||
boolean isPromotion;
|
||||
|
||||
public List<Image> getImages() {
|
||||
return images;
|
||||
}
|
||||
|
||||
public List<Locale> getLocales() {
|
||||
return locales;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Schedule getSchedule() {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public boolean isPromotion() {
|
||||
return isPromotion;
|
||||
}
|
||||
|
||||
@JsonObject
|
||||
public static class Schedule {
|
||||
@JsonField(name = "cron", typeConverter = CronExpressionConverter.class)
|
||||
CronExpression cron;
|
||||
@JsonField(name = "local")
|
||||
boolean local;
|
||||
|
||||
public CronExpression getCron() {
|
||||
return cron;
|
||||
}
|
||||
|
||||
public boolean isLocal() {
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Locale {
|
||||
@NonNull
|
||||
final String language;
|
||||
@Nullable
|
||||
final String country;
|
||||
|
||||
public Locale(@NonNull String language, @Nullable String country) {
|
||||
this.language = language;
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (country == null) return language;
|
||||
return language + "-" + country;
|
||||
}
|
||||
|
||||
public static Locale valueOf(@NonNull String str) {
|
||||
int dashIndex = str.indexOf('-');
|
||||
if (dashIndex == -1) return new Locale(str, null);
|
||||
return new Locale(str.substring(0, dashIndex),
|
||||
str.substring(dashIndex + 1, str.length()));
|
||||
}
|
||||
|
||||
static class ListConverter extends StringBasedListTypeConverter<Locale> {
|
||||
|
||||
@Override
|
||||
public Locale getItemFromString(String str) {
|
||||
if (str == null) return null;
|
||||
return Locale.valueOf(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertItemToString(Locale item) {
|
||||
if (item == null) return null;
|
||||
return item.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonObject
|
||||
public static class Image {
|
||||
@JsonField(name = "url")
|
||||
String url;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model.util;
|
||||
|
||||
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import org.mariotaku.twidere.model.CronExpression;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/19.
|
||||
*/
|
||||
public class CronExpressionConverter implements TypeConverter<CronExpression> {
|
||||
|
||||
@Override
|
||||
public CronExpression parse(JsonParser jsonParser) throws IOException {
|
||||
final String string = jsonParser.getValueAsString(null);
|
||||
if (string == null) return null;
|
||||
try {
|
||||
return CronExpression.valueOf(string);
|
||||
} catch (ParseException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(CronExpression object, String fieldName, boolean writeFieldNameForObject,
|
||||
JsonGenerator jsonGenerator) throws IOException {
|
||||
if (object == null) {
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeNullField(fieldName);
|
||||
} else {
|
||||
jsonGenerator.writeNull();
|
||||
}
|
||||
} else {
|
||||
if (fieldName != null) {
|
||||
jsonGenerator.writeStringField(fieldName, object.toExpression());
|
||||
} else {
|
||||
jsonGenerator.writeString(object.toExpression());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
package org.mariotaku.ktextension
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.v4.content.ContextCompat
|
||||
|
||||
/**
|
||||
|
@ -25,4 +27,10 @@ fun Context.unregisterReceiverSafe(receiver: BroadcastReceiver?): Boolean {
|
|||
} catch (e: IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Context.componentIcon: Drawable?
|
||||
get() {
|
||||
val info = packageManager.getActivityInfo(ComponentName(this, javaClass), 0)
|
||||
return info.loadIcon(packageManager)
|
||||
}
|
|
@ -145,6 +145,9 @@ open class BaseActivity : ChameleonActivity(), IBaseActivity<BaseActivity>, IThe
|
|||
override val themeBackgroundOption: String
|
||||
get() = themePreferences[themeBackgroundOptionKey]
|
||||
|
||||
open val themeNavigationStyle: String
|
||||
get() = themePreferences[navbarStyleKey]
|
||||
|
||||
private var isNightBackup: Int = TwilightManagerAccessor.UNSPECIFIED
|
||||
|
||||
private val actionHelper = IBaseActivity.ActionHelper(this)
|
||||
|
@ -237,13 +240,12 @@ open class BaseActivity : ChameleonActivity(), IBaseActivity<BaseActivity>, IThe
|
|||
StrictModeUtils.detectAllVmPolicy()
|
||||
StrictModeUtils.detectAllThreadPolicy()
|
||||
}
|
||||
val prefs = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
val themeColor = prefs[themeColorKey]
|
||||
val themeResource = getThemeResource(prefs, prefs[themeKey], themeColor)
|
||||
val themeColor = themePreferences[themeColorKey]
|
||||
val themeResource = getThemeResource(themePreferences, themePreferences[themeKey], themeColor)
|
||||
if (themeResource != 0) {
|
||||
setTheme(themeResource)
|
||||
}
|
||||
onApplyNavigationStyle(prefs[navbarStyleKey], themeColor)
|
||||
onApplyNavigationStyle(themeNavigationStyle, themeColor)
|
||||
super.onCreate(savedInstanceState)
|
||||
ActivitySupport.setTaskDescription(this, TaskDescriptionCompat(title.toString(), null,
|
||||
ColorUtils.setAlphaComponent(overrideTheme.colorToolbar, 0xFF)))
|
||||
|
|
|
@ -20,27 +20,177 @@
|
|||
package org.mariotaku.twidere.activity
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.annotation.StyleRes
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.support.v7.app.TwilightManagerAccessor
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.bumptech.glide.Glide
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import nl.komponents.kovenant.Promise
|
||||
import org.mariotaku.chameleon.Chameleon
|
||||
import org.mariotaku.chameleon.ChameleonActivity
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.componentIcon
|
||||
import org.mariotaku.ktextension.contains
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
|
||||
import org.mariotaku.twidere.activity.iface.IBaseActivity
|
||||
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT
|
||||
import org.mariotaku.twidere.constant.themeColorKey
|
||||
import org.mariotaku.twidere.constant.themeKey
|
||||
import org.mariotaku.twidere.extension.model.hasInvalidAccount
|
||||
import org.mariotaku.twidere.extension.model.shouldShow
|
||||
import org.mariotaku.twidere.model.presentation.LaunchPresentation
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.task.filter.RefreshLaunchPresentationsTask
|
||||
import org.mariotaku.twidere.util.DeviceUtils
|
||||
import org.mariotaku.twidere.util.OnLinkClickHandler
|
||||
import org.mariotaku.twidere.util.StrictModeUtils
|
||||
import org.mariotaku.twidere.util.ThemeUtils
|
||||
import org.mariotaku.twidere.util.cache.JsonCache
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import org.mariotaku.twidere.util.theme.getCurrentThemeResource
|
||||
import javax.inject.Inject
|
||||
|
||||
open class MainActivity : BaseActivity() {
|
||||
open class MainActivity : ChameleonActivity(), IBaseActivity<MainActivity> {
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val launchLaterRunnable: Runnable = Runnable { launchMain() }
|
||||
private val actionHelper = IBaseActivity.ActionHelper(this)
|
||||
|
||||
private var isNightBackup: Int = TwilightManagerAccessor.UNSPECIFIED
|
||||
|
||||
private val themePreferences by lazy {
|
||||
getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private val userTheme: Chameleon.Theme by lazy {
|
||||
return@lazy ThemeUtils.getUserTheme(this, themePreferences)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var restHttpClient: RestHttpClient
|
||||
|
||||
@Inject
|
||||
lateinit var preferences: SharedPreferences
|
||||
|
||||
@Inject
|
||||
lateinit var jsonCache: JsonCache
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictModeUtils.detectAllVmPolicy()
|
||||
StrictModeUtils.detectAllThreadPolicy()
|
||||
}
|
||||
val themeColor = themePreferences[themeColorKey]
|
||||
val themeResource = getThemeResource(themePreferences, themePreferences[themeKey], themeColor)
|
||||
if (themeResource != 0) {
|
||||
setTheme(themeResource)
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
GeneralComponent.get(this).inject(this)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
main.visibility = View.VISIBLE
|
||||
appIcon.setImageDrawable(componentIcon)
|
||||
skipPresentation.setOnClickListener {
|
||||
launchDirectly()
|
||||
}
|
||||
controlOverlay.setOnClickListener {
|
||||
val presentation = controlOverlay.tag as? LaunchPresentation ?: return@setOnClickListener
|
||||
val uri = presentation.url?.let(Uri::parse) ?: return@setOnClickListener
|
||||
OnLinkClickHandler.openLink(this, preferences, uri)
|
||||
}
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(main) lambda@ { _, insets ->
|
||||
main.setPadding(0, 0, 0, insets.systemWindowInsetBottom)
|
||||
|
||||
controlOverlay.setPadding(insets.systemWindowInsetLeft, insets.systemWindowInsetTop,
|
||||
insets.systemWindowInsetRight, 0)
|
||||
return@lambda insets.consumeSystemWindowInsets()
|
||||
}
|
||||
|
||||
val presentation = jsonCache.getList(RefreshLaunchPresentationsTask.JSON_CACHE_KEY,
|
||||
LaunchPresentation::class.java)?.firstOrNull {
|
||||
it.shouldShow()
|
||||
}
|
||||
if (presentation != null) {
|
||||
displayPresentation(presentation)
|
||||
launchLater()
|
||||
} else {
|
||||
launchDirectly()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
actionHelper.dispatchOnPause()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateNightMode()
|
||||
}
|
||||
|
||||
override fun onResumeFragments() {
|
||||
super.onResumeFragments()
|
||||
actionHelper.dispatchOnResumeFragments()
|
||||
}
|
||||
|
||||
override fun executeAfterFragmentResumed(useHandler: Boolean, action: (MainActivity) -> Unit): Promise<Unit, Exception> {
|
||||
return actionHelper.executeAfterFragmentResumed(useHandler, action)
|
||||
}
|
||||
|
||||
override fun getOverrideTheme(): Chameleon.Theme? {
|
||||
return userTheme
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
protected open fun getThemeResource(preferences: SharedPreferences, theme: String, themeColor: Int): Int {
|
||||
return getCurrentThemeResource(this, theme)
|
||||
}
|
||||
|
||||
private fun updateNightMode() {
|
||||
val nightState = TwilightManagerAccessor.getNightState(this)
|
||||
if (isNightBackup != TwilightManagerAccessor.UNSPECIFIED && nightState != isNightBackup) {
|
||||
recreate()
|
||||
return
|
||||
}
|
||||
isNightBackup = nightState
|
||||
}
|
||||
|
||||
private fun displayPresentation(presentation: LaunchPresentation) {
|
||||
skipPresentation.visibility = View.VISIBLE
|
||||
controlOverlay.tag = presentation
|
||||
Glide.with(this).load(presentation.images.first().url).into(presentationView)
|
||||
}
|
||||
|
||||
private fun launchDirectly() {
|
||||
handler.removeCallbacks(launchLaterRunnable)
|
||||
launchMain()
|
||||
}
|
||||
|
||||
private fun launchLater() {
|
||||
handler.postDelayed(launchLaterRunnable, 5000L)
|
||||
}
|
||||
|
||||
private fun launchMain() {
|
||||
if (isFinishing) return
|
||||
executeAfterFragmentResumed { performLaunch() }
|
||||
}
|
||||
|
||||
private fun performLaunch() {
|
||||
val am = AccountManager.get(this)
|
||||
if (!DeviceUtils.checkCompatibility()) {
|
||||
startActivity(Intent(this, IncompatibleAlertActivity::class.java))
|
||||
|
|
|
@ -59,6 +59,7 @@ import org.mariotaku.twidere.service.StreamingService
|
|||
import org.mariotaku.twidere.util.*
|
||||
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import org.mariotaku.twidere.util.emoji.EmojioneTranslator
|
||||
import org.mariotaku.twidere.util.kovenant.startKovenant
|
||||
import org.mariotaku.twidere.util.kovenant.stopKovenant
|
||||
import org.mariotaku.twidere.util.media.MediaPreloader
|
||||
|
@ -130,6 +131,7 @@ class TwidereApplication : Application(), Constants, OnSharedPreferenceChangeLis
|
|||
StrictModeUtils.detectAllVmPolicy()
|
||||
}
|
||||
super.onCreate()
|
||||
EmojioneTranslator.init(this)
|
||||
applyLanguageSettings()
|
||||
startKovenant()
|
||||
initializeAsyncTask()
|
||||
|
|
|
@ -4,10 +4,10 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.model.FiltersSubscription
|
||||
import org.mariotaku.twidere.model.filter.UrlFiltersSubscriptionProviderArguments
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import org.mariotaku.twidere.util.filter.FiltersSubscriptionProvider
|
||||
import org.mariotaku.twidere.util.filter.LocalFiltersSubscriptionProvider
|
||||
import org.mariotaku.twidere.util.filter.UrlFiltersSubscriptionProvider
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/1/9.
|
||||
|
@ -38,7 +38,7 @@ fun FiltersSubscription.getComponentLabel(context: Context): CharSequence {
|
|||
|
||||
fun FiltersSubscription.setupUrl(url: String) {
|
||||
this.component = ":url"
|
||||
this.arguments = JsonSerializer.serialize(UrlFiltersSubscriptionProvider.Arguments().apply {
|
||||
this.arguments = JsonSerializer.serialize(UrlFiltersSubscriptionProviderArguments().apply {
|
||||
this.url = url
|
||||
})
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.extension.model
|
||||
|
||||
import org.mariotaku.twidere.model.presentation.LaunchPresentation
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/20.
|
||||
*/
|
||||
|
||||
fun LaunchPresentation.shouldShow(): Boolean {
|
||||
// Check language
|
||||
val locale = Locale.getDefault()
|
||||
if (locales != null && locales.none { it.matches(locale) }) {
|
||||
return false
|
||||
}
|
||||
// Check date/time
|
||||
val date = Date()
|
||||
if (schedule != null && !schedule.matches(date)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun LaunchPresentation.Schedule.matches(date: Date): Boolean {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = date
|
||||
return cron.matches(cal)
|
||||
}
|
||||
|
||||
|
||||
fun LaunchPresentation.Locale.matches(locale: Locale): Boolean {
|
||||
if (language != locale.language) return false
|
||||
if (country == null) {
|
||||
return locale.country.isNullOrEmpty()
|
||||
}
|
||||
return country == locale.country
|
||||
}
|
|
@ -85,6 +85,7 @@ class JobTaskService : JobService() {
|
|||
const val JOB_ID_REFRESH_NOTIFICATIONS = 2
|
||||
const val JOB_ID_REFRESH_DIRECT_MESSAGES = 3
|
||||
const val JOB_ID_REFRESH_FILTERS_SUBSCRIPTIONS = 19
|
||||
const val JOB_ID_REFRESH_LAUNCH_PRESENTATIONS = 18
|
||||
const val JOB_ID_SYNC_DRAFTS = 21
|
||||
const val JOB_ID_SYNC_FILTERS = 22
|
||||
const val JOB_ID_SYNC_USER_NICKNAMES = 23
|
||||
|
@ -108,6 +109,7 @@ class JobTaskService : JobService() {
|
|||
JOB_ID_REFRESH_NOTIFICATIONS -> TaskServiceRunner.ACTION_REFRESH_NOTIFICATIONS
|
||||
JOB_ID_REFRESH_DIRECT_MESSAGES -> TaskServiceRunner.ACTION_REFRESH_DIRECT_MESSAGES
|
||||
JOB_ID_REFRESH_FILTERS_SUBSCRIPTIONS -> TaskServiceRunner.ACTION_REFRESH_FILTERS_SUBSCRIPTIONS
|
||||
JOB_ID_REFRESH_LAUNCH_PRESENTATIONS -> TaskServiceRunner.ACTION_REFRESH_LAUNCH_PRESENTATIONS
|
||||
JOB_ID_SYNC_DRAFTS -> TaskServiceRunner.ACTION_SYNC_DRAFTS
|
||||
JOB_ID_SYNC_FILTERS -> TaskServiceRunner.ACTION_SYNC_FILTERS
|
||||
JOB_ID_SYNC_USER_NICKNAMES -> TaskServiceRunner.ACTION_SYNC_USER_NICKNAMES
|
||||
|
|
|
@ -6,11 +6,13 @@ import com.squareup.otto.Bus
|
|||
import com.twitter.Extractor
|
||||
import org.mariotaku.abstask.library.AbstractTask
|
||||
import org.mariotaku.kpreferences.KPreferences
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.twidere.model.DefaultFeatures
|
||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper
|
||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||
import org.mariotaku.twidere.util.ReadStateManager
|
||||
import org.mariotaku.twidere.util.UserColorNameManager
|
||||
import org.mariotaku.twidere.util.cache.JsonCache
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
import org.mariotaku.twidere.util.media.MediaPreloader
|
||||
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
|
||||
|
@ -46,6 +48,8 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
|
|||
@Inject
|
||||
lateinit var extraFeaturesService: ExtraFeaturesService
|
||||
@Inject
|
||||
lateinit var restHttpClient: RestHttpClient
|
||||
@Inject
|
||||
lateinit var defaultFeatures: DefaultFeatures
|
||||
@Inject
|
||||
lateinit var scheduleProviderFactory: StatusScheduleProvider.Factory
|
||||
|
@ -55,6 +59,8 @@ abstract class BaseAbstractTask<Params, Result, Callback>(val context: Context)
|
|||
lateinit var syncPreferences: SyncPreferences
|
||||
@Inject
|
||||
lateinit var timelineSyncManagerFactory: TimelineSyncManager.Factory
|
||||
@Inject
|
||||
lateinit var jsonCache: JsonCache
|
||||
|
||||
val scheduleProvider: StatusScheduleProvider?
|
||||
get() = scheduleProviderFactory.newInstance(context)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.task.filter
|
||||
|
||||
import android.content.Context
|
||||
import org.mariotaku.restfu.annotation.method.GET
|
||||
import org.mariotaku.restfu.http.HttpRequest
|
||||
import org.mariotaku.twidere.model.presentation.LaunchPresentation
|
||||
import org.mariotaku.twidere.task.BaseAbstractTask
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/8/22.
|
||||
*/
|
||||
class RefreshLaunchPresentationsTask(context: Context) : BaseAbstractTask<Unit?, Boolean, (Boolean) -> Unit>(context) {
|
||||
override fun doLongOperation(params: Unit?): Boolean {
|
||||
val request = HttpRequest.Builder()
|
||||
.method(GET.METHOD)
|
||||
.url("https://twidere.mariotaku.org/assets/data/launch_presentations.json")
|
||||
.build()
|
||||
try {
|
||||
val presentations = restHttpClient.newCall(request).execute().use {
|
||||
return@use JsonSerializer.parseList(it.body.stream(), LaunchPresentation::class.java)
|
||||
}
|
||||
jsonCache.saveList(JSON_CACHE_KEY, presentations, LaunchPresentation::class.java)
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val JSON_CACHE_KEY = "launch_presentations"
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.mariotaku.twidere.model.pagination.SinceMaxPagination
|
|||
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
||||
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||
import org.mariotaku.twidere.task.filter.RefreshFiltersSubscriptionsTask
|
||||
import org.mariotaku.twidere.task.filter.RefreshLaunchPresentationsTask
|
||||
import org.mariotaku.twidere.task.twitter.GetActivitiesAboutMeTask
|
||||
import org.mariotaku.twidere.task.twitter.GetHomeTimelineTask
|
||||
import org.mariotaku.twidere.task.twitter.message.GetMessagesTask
|
||||
|
@ -41,7 +42,8 @@ class TaskServiceRunner(
|
|||
Log.d(LOGTAG, "TaskServiceRunner run task $action")
|
||||
when (action) {
|
||||
ACTION_REFRESH_HOME_TIMELINE, ACTION_REFRESH_NOTIFICATIONS,
|
||||
ACTION_REFRESH_DIRECT_MESSAGES, ACTION_REFRESH_FILTERS_SUBSCRIPTIONS -> {
|
||||
ACTION_REFRESH_DIRECT_MESSAGES, ACTION_REFRESH_FILTERS_SUBSCRIPTIONS,
|
||||
ACTION_REFRESH_LAUNCH_PRESENTATIONS -> {
|
||||
val task = createRefreshTask(action) ?: return false
|
||||
task.callback = callback
|
||||
TaskStarter.execute(task)
|
||||
|
@ -97,13 +99,16 @@ class TaskServiceRunner(
|
|||
ACTION_REFRESH_FILTERS_SUBSCRIPTIONS -> {
|
||||
return RefreshFiltersSubscriptionsTask(context)
|
||||
}
|
||||
ACTION_REFRESH_LAUNCH_PRESENTATIONS -> {
|
||||
return RefreshLaunchPresentationsTask(context)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@StringDef(ACTION_REFRESH_HOME_TIMELINE, ACTION_REFRESH_NOTIFICATIONS, ACTION_REFRESH_DIRECT_MESSAGES,
|
||||
ACTION_REFRESH_FILTERS_SUBSCRIPTIONS, ACTION_SYNC_DRAFTS, ACTION_SYNC_FILTERS,
|
||||
ACTION_SYNC_USER_NICKNAMES, ACTION_SYNC_USER_COLORS)
|
||||
ACTION_REFRESH_FILTERS_SUBSCRIPTIONS, ACTION_REFRESH_LAUNCH_PRESENTATIONS,
|
||||
ACTION_SYNC_DRAFTS, ACTION_SYNC_FILTERS, ACTION_SYNC_USER_NICKNAMES, ACTION_SYNC_USER_COLORS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Action
|
||||
|
||||
|
@ -139,6 +144,8 @@ class TaskServiceRunner(
|
|||
@Action
|
||||
const val ACTION_REFRESH_FILTERS_SUBSCRIPTIONS = INTENT_PACKAGE_PREFIX + "REFRESH_FILTERS_SUBSCRIPTIONS"
|
||||
@Action
|
||||
const val ACTION_REFRESH_LAUNCH_PRESENTATIONS = INTENT_PACKAGE_PREFIX + "REFRESH_LAUNCH_PRESENTATIONS"
|
||||
@Action
|
||||
const val ACTION_SYNC_DRAFTS = INTENT_PACKAGE_PREFIX + "SYNC_DRAFTS"
|
||||
@Action
|
||||
const val ACTION_SYNC_FILTERS = INTENT_PACKAGE_PREFIX + "SYNC_FILTERS"
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.mariotaku.twidere.util.cache
|
|||
|
||||
import com.bumptech.glide.disklrucache.DiskLruCache
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.util.DebugLog
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -29,22 +30,24 @@ import java.io.IOException
|
|||
* Created by mariotaku on 2017/3/1.
|
||||
*/
|
||||
|
||||
class JsonCache(val cacheDir: File) {
|
||||
class JsonCache(cacheDir: File) {
|
||||
|
||||
private val cache = try {
|
||||
private val cache: DiskLruCache? = try {
|
||||
DiskLruCache.open(cacheDir, BuildConfig.VERSION_CODE, 1, 100 * 1048576)
|
||||
} catch (e: IOException) {
|
||||
DebugLog.w(tr = e)
|
||||
null
|
||||
}
|
||||
|
||||
fun <T> getList(key: String, cls: Class<T>): List<T>? {
|
||||
val value = cache?.get(key) ?: return null
|
||||
try {
|
||||
return value.getFile(0)?.inputStream()?.use {
|
||||
return try {
|
||||
value.getFile(0)?.inputStream()?.use {
|
||||
JsonSerializer.parseList(it, cls)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
DebugLog.w(tr = e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +58,8 @@ class JsonCache(val cacheDir: File) {
|
|||
JsonSerializer.serialize(list, it, cls)
|
||||
}
|
||||
editor.commit()
|
||||
} catch (e: IOException) {
|
||||
DebugLog.w(tr = e)
|
||||
} finally {
|
||||
editor.abortUnlessCommitted()
|
||||
}
|
||||
|
|
|
@ -22,10 +22,7 @@ package org.mariotaku.twidere.util.dagger
|
|||
import android.content.Context
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import dagger.Component
|
||||
import org.mariotaku.twidere.activity.BaseActivity
|
||||
import org.mariotaku.twidere.activity.ComposeActivity
|
||||
import org.mariotaku.twidere.activity.MediaViewerActivity
|
||||
import org.mariotaku.twidere.activity.PremiumDashboardActivity
|
||||
import org.mariotaku.twidere.activity.*
|
||||
import org.mariotaku.twidere.adapter.*
|
||||
import org.mariotaku.twidere.app.TwidereApplication
|
||||
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||
|
@ -150,9 +147,11 @@ interface GeneralComponent {
|
|||
|
||||
fun inject(service: BaseService)
|
||||
|
||||
companion object {
|
||||
fun inject(activity: MainActivity)
|
||||
|
||||
companion object {
|
||||
private var instance: GeneralComponent? = null
|
||||
|
||||
fun get(context: Context): GeneralComponent {
|
||||
return instance ?: run {
|
||||
val helper = DaggerGeneralComponent.builder().applicationModule(ApplicationModule.get(context)).build()
|
||||
|
|
|
@ -19,9 +19,26 @@
|
|||
|
||||
package org.mariotaku.twidere.util.emoji
|
||||
|
||||
import android.content.Context
|
||||
import org.apache.commons.text.translate.CharSequenceTranslator
|
||||
import org.mariotaku.commons.emojione.ShortnameToUnicodeTranslator
|
||||
import java.io.Writer
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/26.
|
||||
*/
|
||||
object EmojioneTranslator: ShortnameToUnicodeTranslator()
|
||||
object EmojioneTranslator: CharSequenceTranslator() {
|
||||
|
||||
private var implementation: ShortnameToUnicodeTranslator? = null
|
||||
|
||||
fun init(context: Context) {
|
||||
if (implementation != null)return
|
||||
implementation = ShortnameToUnicodeTranslator(context)
|
||||
}
|
||||
|
||||
override fun translate(input: CharSequence?, index: Int, out: Writer?): Int {
|
||||
val translator = implementation ?: return 0
|
||||
return translator.translate(input, index, out)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.mariotaku.twidere.util.filter
|
||||
|
||||
import android.content.Context
|
||||
import org.mariotaku.twidere.model.filter.UrlFiltersSubscriptionProviderArguments
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -15,7 +16,7 @@ abstract class LocalFiltersSubscriptionProvider(val context: Context) : FiltersS
|
|||
"url" -> {
|
||||
if (arguments == null) return null
|
||||
val argsObj = try {
|
||||
JsonSerializer.parse(arguments, UrlFiltersSubscriptionProvider.Arguments::class.java)
|
||||
JsonSerializer.parse(arguments, UrlFiltersSubscriptionProviderArguments::class.java)
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package org.mariotaku.twidere.util.filter
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import org.mariotaku.restfu.annotation.method.GET
|
||||
import org.mariotaku.restfu.http.HttpRequest
|
||||
import org.mariotaku.restfu.http.MultiValueMap
|
||||
|
@ -12,6 +10,7 @@ import org.mariotaku.restfu.http.mime.Body
|
|||
import org.mariotaku.twidere.extension.model.parse
|
||||
import org.mariotaku.twidere.extension.newPullParser
|
||||
import org.mariotaku.twidere.model.FiltersData
|
||||
import org.mariotaku.twidere.model.filter.UrlFiltersSubscriptionProviderArguments
|
||||
import org.mariotaku.twidere.util.ETagCache
|
||||
import org.mariotaku.twidere.util.JsonSerializer
|
||||
import org.mariotaku.twidere.util.dagger.GeneralComponent
|
||||
|
@ -22,7 +21,10 @@ import javax.inject.Inject
|
|||
* Created by mariotaku on 2017/1/9.
|
||||
*/
|
||||
|
||||
class UrlFiltersSubscriptionProvider(context: Context, val arguments: Arguments) : LocalFiltersSubscriptionProvider(context) {
|
||||
class UrlFiltersSubscriptionProvider(
|
||||
context: Context,
|
||||
val arguments: UrlFiltersSubscriptionProviderArguments
|
||||
) : LocalFiltersSubscriptionProvider(context) {
|
||||
@Inject
|
||||
internal lateinit var restHttpClient: RestHttpClient
|
||||
@Inject
|
||||
|
@ -110,9 +112,4 @@ class UrlFiltersSubscriptionProvider(context: Context, val arguments: Arguments)
|
|||
}
|
||||
}
|
||||
|
||||
@JsonObject
|
||||
class Arguments {
|
||||
@JsonField(name = arrayOf("url"))
|
||||
lateinit var url: String
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.mariotaku.twidere.annotation.AutoRefreshType
|
|||
import org.mariotaku.twidere.constant.refreshIntervalKey
|
||||
import org.mariotaku.twidere.service.JobTaskService
|
||||
import org.mariotaku.twidere.service.JobTaskService.Companion.JOB_ID_REFRESH_FILTERS_SUBSCRIPTIONS
|
||||
import org.mariotaku.twidere.service.JobTaskService.Companion.JOB_ID_REFRESH_LAUNCH_PRESENTATIONS
|
||||
import java.util.concurrent.TimeUnit
|
||||
import android.Manifest.permission as AndroidPermissions
|
||||
|
||||
|
@ -23,7 +24,7 @@ class JobSchedulerAutoRefreshController(
|
|||
context: Context,
|
||||
kPreferences: KPreferences
|
||||
) : AutoRefreshController(context, kPreferences) {
|
||||
val scheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||
private val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||
|
||||
override fun appStarted() {
|
||||
val allJobs = scheduler.allPendingJobs
|
||||
|
@ -37,6 +38,9 @@ class JobSchedulerAutoRefreshController(
|
|||
if (allJobs.none { job -> job.id == JOB_ID_REFRESH_FILTERS_SUBSCRIPTIONS }) {
|
||||
scheduleJob(JOB_ID_REFRESH_FILTERS_SUBSCRIPTIONS, TimeUnit.HOURS.toMillis(4))
|
||||
}
|
||||
if (allJobs.none { job -> job.id == JOB_ID_REFRESH_LAUNCH_PRESENTATIONS }) {
|
||||
scheduleJob(JOB_ID_REFRESH_LAUNCH_PRESENTATIONS, TimeUnit.HOURS.toMillis(6))
|
||||
}
|
||||
}
|
||||
|
||||
override fun schedule(@AutoRefreshType type: String) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.mariotaku.twidere.constant.refreshIntervalKey
|
|||
import org.mariotaku.twidere.service.JobTaskService.Companion.JOB_IDS_REFRESH
|
||||
import org.mariotaku.twidere.service.LegacyTaskService
|
||||
import org.mariotaku.twidere.util.TaskServiceRunner.Companion.ACTION_REFRESH_FILTERS_SUBSCRIPTIONS
|
||||
import org.mariotaku.twidere.util.TaskServiceRunner.Companion.ACTION_REFRESH_LAUNCH_PRESENTATIONS
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LegacyAutoRefreshController(
|
||||
|
@ -37,6 +38,7 @@ class LegacyAutoRefreshController(
|
|||
override fun appStarted() {
|
||||
rescheduleAll()
|
||||
rescheduleFiltersSubscriptionsRefresh()
|
||||
rescheduleLaunchPresentationsRefresh()
|
||||
}
|
||||
|
||||
override fun rescheduleAll() {
|
||||
|
@ -69,6 +71,15 @@ class LegacyAutoRefreshController(
|
|||
PendingIntent.getService(context, 0, intent, 0))
|
||||
}
|
||||
|
||||
private fun rescheduleLaunchPresentationsRefresh() {
|
||||
val interval = TimeUnit.HOURS.toMillis(6)
|
||||
val triggerAt = SystemClock.elapsedRealtime() + interval
|
||||
val intent = Intent(context, LegacyTaskService::class.java)
|
||||
intent.action = ACTION_REFRESH_LAUNCH_PRESENTATIONS
|
||||
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, interval,
|
||||
PendingIntent.getService(context, 0, intent, 0))
|
||||
}
|
||||
|
||||
companion object {
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun removeAllJobs(context: Context, jobIds: IntArray) {
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Twidere - Twitter client for Android
|
||||
~
|
||||
~ Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
~
|
||||
~ This program 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.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/presentationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/logoContainer"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/logoContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:gravity="center"
|
||||
android:minHeight="108dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/appIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
tools:src="@mipmap/ic_launcher"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/element_spacing_normal"
|
||||
android:layout_marginStart="@dimen/element_spacing_normal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="?android:textAppearanceMedium"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_description"
|
||||
android:textAppearance="?android:textAppearanceSmall"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/controlOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/skipPresentation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginEnd="@dimen/element_spacing_normal"
|
||||
android:layout_marginRight="@dimen/element_spacing_normal"
|
||||
android:layout_marginTop="@dimen/element_spacing_normal"
|
||||
android:text="@string/action_skip"
|
||||
android:visibility="gone"/>
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Twidere - Twitter client for Android
|
||||
~
|
||||
~ Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
~
|
||||
~ This program 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.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Twidere.Launcher" parent="Theme.Twidere.Dark.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
|
||||
<item name="darkThemeResource">@style/Theme.Twidere.Dark.Launcher</item>
|
||||
<item name="lightThemeResource">@style/Theme.Twidere.Light.Launcher</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Twidere - Twitter client for Android
|
||||
~
|
||||
~ Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
~
|
||||
~ This program 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.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Twidere.Dark.Launcher" parent="Theme.Twidere.Dark.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Twidere - Twitter client for Android
|
||||
~
|
||||
~ Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
~
|
||||
~ This program 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.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Twidere.Light.Launcher" parent="Theme.Twidere.Light.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -57,4 +57,9 @@
|
|||
<item name="colorPrimary">@color/nyan_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Launcher" parent="Theme.Twidere.Dark.NoActionBar">
|
||||
<item name="darkThemeResource">@style/Theme.Twidere.Dark.Launcher</item>
|
||||
<item name="lightThemeResource">@style/Theme.Twidere.Light.Launcher</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -78,7 +78,6 @@
|
|||
<style name="Theme.Twidere.Dark.NoActionBar" parent="Theme.Twidere.Dark">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Dark.ActionBar" parent="Theme.Twidere.Dark">
|
||||
|
@ -119,4 +118,6 @@
|
|||
<item name="android:windowActionModeOverlay">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Dark.Launcher" parent="Theme.Twidere.Dark.NoActionBar"/>
|
||||
|
||||
</resources>
|
|
@ -125,4 +125,7 @@
|
|||
<item name="android:windowActionBarOverlay">false</item>
|
||||
<item name="android:windowActionModeOverlay">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Light.Launcher" parent="Theme.Twidere.Light.NoActionBar"/>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue