カスタム通知リスナの設定を追加

This commit is contained in:
tateisu 2017-05-26 12:48:06 +09:00
parent ea6d423c4a
commit 30147b9ed5
14 changed files with 712 additions and 59 deletions

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 71
versionName "0.7.1"
versionCode 72
versionName "0.7.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@ -76,6 +76,8 @@ dependencies {
compile 'com.github.bumptech.glide:glide:3.8.0'
// annotationProcessor 'com.github.bumptech.glide:compiler:3.8.0'
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
compile 'org.hjson:hjson:2.1.1'
}
apply plugin: 'com.google.gms.google-services'

View File

@ -147,6 +147,13 @@
android:label="@string/nickname_and_color"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
/>
<activity
android:name=".ActCustomStreamListener"
android:label="@string/custom_stream_listener"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
/>
<activity
android:name=".ActText"
android:label="@string/select_and_copy"

View File

@ -38,7 +38,9 @@ import org.apache.commons.io.output.FileWriterWithEncoding;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
@ -108,6 +110,8 @@ public class ActAppSetting extends AppCompatActivity
EditText etColumnWidth;
EditText etMediaThumbHeight;
TextView tvTimelineFontUrl;
String timeline_font;
@ -228,6 +232,9 @@ public class ActAppSetting extends AppCompatActivity
findViewById( R.id.btnTimelineFontReset ).setOnClickListener( this );
findViewById( R.id.btnSettingExport ).setOnClickListener( this );
findViewById( R.id.btnSettingImport ).setOnClickListener( this );
findViewById( R.id.btnCustomStreamListenerEdit ).setOnClickListener( this );
findViewById( R.id.btnCustomStreamListenerReset ).setOnClickListener( this );
ivFooterToot = (ImageView) findViewById( R.id.ivFooterToot );
ivFooterMenu = (ImageView) findViewById( R.id.ivFooterMenu );
@ -242,7 +249,6 @@ public class ActAppSetting extends AppCompatActivity
etColumnWidth.addTextChangedListener( this );
etMediaThumbHeight.addTextChangedListener( this );
}
boolean load_busy;
@ -319,9 +325,10 @@ public class ActAppSetting extends AppCompatActivity
.putInt( Pref.KEY_FOOTER_TAB_BG_COLOR, footer_tab_bg_color )
.putInt( Pref.KEY_FOOTER_TAB_DIVIDER_COLOR, footer_tab_divider_color )
.putString( Pref.KEY_TIMELINE_FONT, timeline_font )
.putString( Pref.KEY_COLUMN_WIDTH, etColumnWidth.getText().toString().trim() )
.putString( Pref.KEY_MEDIA_THUMB_HEIGHT, etMediaThumbHeight.getText().toString().trim() )
.putString( Pref.KEY_TIMELINE_FONT, timeline_font )
.apply();
@ -411,6 +418,22 @@ public class ActAppSetting extends AppCompatActivity
case R.id.btnSettingImport:
importAppData();
break;
case R.id.btnCustomStreamListenerEdit:
ActCustomStreamListener.open( this );
break;
case R.id.btnCustomStreamListenerReset:
pref
.edit()
.remove(Pref.KEY_STREAM_LISTENER_CONFIG_URL)
.remove( Pref.KEY_STREAM_LISTENER_SECRET)
.remove( Pref.KEY_STREAM_LISTENER_CONFIG_DATA)
.apply();
SavedAccount.clearRegistrationCache();
AlarmService.startCheck( this,false );
Utils.showToast( this,false,getString(R.string.custom_stream_listener_was_reset) );
break;
}
}
@ -704,5 +727,4 @@ public class ActAppSetting extends AppCompatActivity
finish();
}
}

View File

@ -0,0 +1,350 @@
package jp.juggler.subwaytooter;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import org.hjson.JsonObject;
import org.hjson.JsonValue;
import java.util.regex.Pattern;
import jp.juggler.subwaytooter.table.AcctColor;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.util.Utils;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import static jp.juggler.subwaytooter.R.id.large;
import static jp.juggler.subwaytooter.R.id.tvAcct;
public class ActCustomStreamListener extends AppCompatActivity implements View.OnClickListener, TextWatcher {
static final LogCategory log = new LogCategory("ActCustomStreamListener");
static final String EXTRA_ACCT = "acct";
public static void open( Activity activity ){
Intent intent = new Intent( activity, ActCustomStreamListener.class );
activity.startActivity( intent );
}
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
App1.setActivityTheme( this, false );
initUI();
if( savedInstanceState != null ){
stream_config_json = savedInstanceState.getString(STATE_STREAM_CONFIG_JSON);
}else{
load();
}
showButtonState();
}
static final String STATE_STREAM_CONFIG_JSON = "stream_config_json" ;
@Override protected void onSaveInstanceState( Bundle outState ){
super.onSaveInstanceState( outState );
outState.putString( STATE_STREAM_CONFIG_JSON,stream_config_json );
}
EditText etStreamListenerConfigurationUrl;
EditText etStreamListenerSecret;
TextView tvLog;
View btnDiscard;
View btnTest;
View btnSave;
String stream_config_json;
private void initUI(){
setContentView( R.layout.act_custom_stream_listener );
Styler.fixHorizontalPadding(findViewById( R.id.llContent ));
etStreamListenerConfigurationUrl= (EditText) findViewById( R.id.etStreamListenerConfigurationUrl );
etStreamListenerSecret= (EditText) findViewById( R.id.etStreamListenerSecret );
etStreamListenerConfigurationUrl.addTextChangedListener( this );
etStreamListenerSecret.addTextChangedListener( this );
tvLog= (TextView) findViewById( R.id.tvLog );
btnDiscard= findViewById( R.id.btnDiscard );
btnTest= findViewById( R.id.btnTest );
btnSave= findViewById( R.id.btnSave );
btnDiscard.setOnClickListener( this );
btnTest.setOnClickListener( this );
btnSave.setOnClickListener( this );
}
boolean bLoading = false;
private void load(){
bLoading = true;
SharedPreferences pref = Pref.pref( this );
etStreamListenerConfigurationUrl.setText( pref.getString( Pref.KEY_STREAM_LISTENER_CONFIG_URL, "" ) );
etStreamListenerSecret.setText( pref.getString( Pref.KEY_STREAM_LISTENER_SECRET, "" ) );
stream_config_json = null;
tvLog.setText( getString( R.string.input_url_and_secret_then_test ) );
bLoading = false;
}
@Override public void beforeTextChanged( CharSequence s, int start, int count, int after ){
}
@Override public void onTextChanged( CharSequence s, int start, int before, int count ){
}
@Override public void afterTextChanged( Editable s ){
tvLog.setText( getString( R.string.input_url_and_secret_then_test ) );
stream_config_json = null;
showButtonState();
}
private void showButtonState(){
btnSave.setEnabled( stream_config_json != null );
btnTest.setEnabled( ! isTestRunning() );
}
private boolean isTestRunning(){
return last_task != null && ! last_task.isCancelled();
}
@Override public void onClick( View v ){
switch( v.getId() ){
case R.id.btnDiscard:
Utils.hideKeyboard( this, etStreamListenerConfigurationUrl );
finish();
break;
case R.id.btnTest:
Utils.hideKeyboard( this, etStreamListenerConfigurationUrl );
startTest();
break;
case R.id.btnSave:
Utils.hideKeyboard( this, etStreamListenerConfigurationUrl );
if( save() ){
SavedAccount.clearRegistrationCache();
AlarmService.startCheck( this,false );
finish();
}
break;
}
}
private boolean save(){
if( stream_config_json == null ){
Utils.showToast( this,false,"please test before save." );
return false;
}
Pref.pref( this ).edit()
.putString(Pref.KEY_STREAM_LISTENER_CONFIG_URL,etStreamListenerConfigurationUrl.getText().toString().trim() )
.putString(Pref.KEY_STREAM_LISTENER_SECRET,etStreamListenerSecret.getText().toString().trim() )
.putString(Pref.KEY_STREAM_LISTENER_CONFIG_DATA,stream_config_json)
.apply();
return true;
}
AsyncTask<Void,Void,String> last_task;
static final Pattern reInstanceURL = Pattern.compile( "\\Ahttps://[a-z0-9.-_:]+\\z" );
static final Pattern reUpperCase = Pattern.compile( "[A-Z]" );
static final Pattern reUrl = Pattern.compile( "\\Ahttps?://[\\w\\-?&#%~!$'()*+,/:;=@._\\[\\]]+\\z" );
void addLog(final String line){
Utils.runOnMainThread( new Runnable() {
@Override public void run(){
String sv = tvLog.getText().toString();
if( sv.isEmpty() ){
sv = line;
}else{
sv = sv +"\n" + line;
}
tvLog.setText( sv);
}
} );
}
void startTest(){
final String strSecret = etStreamListenerSecret.getText().toString().trim();
final String strUrl = etStreamListenerConfigurationUrl.getText().toString().trim();
stream_config_json = null;
showButtonState();
last_task = new AsyncTask< Void, Void, String >() {
@Override protected String doInBackground( Void... params ){
try{
for(;;){
if( TextUtils.isEmpty( strSecret ) ){
addLog( "Secret is empty. Custom Listener is not used." );
break;
}else if( TextUtils.isEmpty( strUrl ) ){
addLog( "Configuration URL is empty. Custom Listener is not used." );
break;
}
addLog( "try to loading Configuration data from URL…" );
Request.Builder builder = new Request.Builder()
.url( strUrl );
Call call = App1.ok_http_client.newCall( builder.build() );
Response response = call.execute();
if( ! response.isSuccessful() ){
addLog( "Can't get configuration from URL. " + response );
break;
}
String json;
try{
//noinspection ConstantConditions
json = response.body().string();
}catch( Throwable ex ){
ex.printStackTrace();
addLog( "Can't get content body" );
break;
}
if( json == null ){
addLog( "content body is null" );
break;
}
JsonValue jv;
try{
jv = JsonValue.readHjson( json );
}catch( Throwable ex ){
ex.printStackTrace();
addLog( Utils.formatError( ex, "Can't parse configuration data." ) );
break;
}
if( ! jv.isObject() ){
addLog( "configuration data is not JSON Object." );
break;
}
JsonObject root = jv.asObject();
boolean has_wildcard = false;
boolean has_error = false;
for( JsonObject.Member member : root ){
String strInstance = member.getName();
if( "*".equals( strInstance ) ){
has_wildcard = true;
}else if( reUpperCase.matcher( strInstance ).find() ){
addLog( strInstance + " : instance URL must be lower case." );
has_error = true;
continue;
}else if( strInstance.charAt( strInstance.length() - 1 ) == '/' ){
addLog( strInstance + " : instance URL must not be trailed with '/'." );
has_error = true;
continue;
}else if( ! reInstanceURL.matcher( strInstance ).find() ){
addLog( strInstance + " : instance URL is not like https://....." );
has_error = true;
continue;
}
JsonValue entry_value = member.getValue();
if( ! entry_value.isObject() ){
addLog( strInstance + " : value for this instance is not JSON Object." );
has_error = true;
continue;
}
JsonObject entry = entry_value.asObject();
String[] keys = new String[]{ "urlStreamingListenerRegister", "urlStreamingListenerUnregister", "appId" };
for( String key : keys ){
JsonValue v = entry.get( key );
if( ! v.isString() ){
addLog( strInstance + "." + key + " : missing parameter, or data type is not string." );
has_error = true;
continue;
}
String sv = v.asString();
if( TextUtils.isEmpty( sv ) ){
addLog( strInstance + "." + key + " : empty parameter." );
has_error = true;
}else if( sv.contains( " " ) ){
addLog( strInstance + "." + key + " : contains whitespace." );
has_error = true;
}
if( ! "appId".equals( key ) ){
if(! reUrl.matcher( sv ).find() ){
addLog( strInstance + "." + key + " : not like Url." );
has_error = true;
}
}
}
}
if( ! has_wildcard ){
addLog( "Warning: This configuration has no wildcard entry." );
if(! has_error){
for( SavedAccount sa : SavedAccount.loadAccountList( log )){
String instanceUrl = ("https://" + sa.host ).toLowerCase();
JsonValue v = root.get( instanceUrl );
if( ! v.isObject() ){
addLog( instanceUrl + " : is not found in configuration data." );
has_error = true;
}
}
}
}
if( has_error ){
addLog( "This configuration has error. " );
break;
}
return json;
}
}catch(Throwable ex){
ex.printStackTrace();
addLog( Utils.formatError( ex,"Can't read configuration from URL." ));
}
return null;
}
@Override protected void onCancelled( String s ){
super.onPostExecute( s );
}
@Override protected void onPostExecute( String s ){
last_task = null;
if( s!= null ){
stream_config_json = s;
addLog( "seems configuration is ok." );
}else{
addLog( "error detected." );
}
showButtonState();
}
};
last_task.executeOnExecutor( App1.task_executor );
}
}

View File

@ -17,10 +17,11 @@ import android.support.v4.content.ContextCompat;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.text.TextUtils;
import org.hjson.JsonObject;
import org.hjson.JsonValue;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -113,19 +114,35 @@ public class AlarmService extends IntentService {
}
String install_id;
boolean bStreamListenerTest;
String mCustomStreamListenerSecret;
String mCustomStreamListenerSettingString;
JsonObject mCustomStreamListenerSetting;
void loadCustomStreamListenerSetting( ){
mCustomStreamListenerSetting = null;
mCustomStreamListenerSecret = null;
mCustomStreamListenerSettingString = pref.getString(Pref.KEY_STREAM_LISTENER_CONFIG_DATA ,null);
if(! TextUtils.isEmpty( mCustomStreamListenerSettingString ) ){
try{
mCustomStreamListenerSetting = JsonValue.readHjson( mCustomStreamListenerSettingString ).asObject();
mCustomStreamListenerSecret = pref.getString(Pref.KEY_STREAM_LISTENER_SECRET ,null);
}catch(Throwable ex){
ex.printStackTrace();
}
}
}
// IntentService onHandleIntent をワーカースレッドから呼び出す
// 同期処理を行って良い
@Override protected void onHandleIntent( @Nullable Intent intent ){
bStreamListenerTest =false;
// クラッシュレポートによると App1.onCreate より前にここを通る場合がある
// データベースへアクセスできるようにする
App1.prepareDB( this.getApplicationContext() );
install_id = getInstallId();
if( intent != null ){
String action = intent.getAction();
log.d( "onHandleIntent action=%s", action );
@ -157,8 +174,7 @@ public class AlarmService extends IntentService {
if( ACTION_DEVICE_TOKEN.equals( action ) ){
// デバイストークンが更新された
// TODO 過去に中継サーバに登録したものは登録解除して登録しなおす必要がある
// アプリサーバへの登録をやり直す
}else if( ACTION_RESET_LAST_LOAD.equals( action ) ){
NotificationTracking.resetLastLoad();
@ -207,6 +223,8 @@ public class AlarmService extends IntentService {
}
}
loadCustomStreamListenerSetting();
final AtomicBoolean bAlarmRequired = new AtomicBoolean( false );
final HashSet< String > muted_app = MutedApp.getNameSet();
final WordTrieTree muted_word = MutedWord.getNameSet();
@ -280,6 +298,10 @@ public class AlarmService extends IntentService {
}
}
private void testLog( String s ){
// TODO アプリ設定画面に進捗表示を追加する
}
String getInstallId(){
String sv = pref.getString(Pref.KEY_INSTALL_ID,null);
if( ! TextUtils.isEmpty( sv ) ) return sv;
@ -387,7 +409,13 @@ public class AlarmService extends IntentService {
account.saveNotificationTag();
}
String reg_key = tag + access_token + device_token ;
String reg_key = Utils.digestSHA256(
tag
+ access_token
+ device_token
+ (mCustomStreamListenerSecret==null? "" :mCustomStreamListenerSecret)
+ (mCustomStreamListenerSettingString==null? "" :mCustomStreamListenerSettingString)
);
long now = System.currentTimeMillis();
if( reg_key.equals( account.register_key ) && now - account.register_time < 3600000 * 3 ){
// タグやトークンが同一なら前回登録に成功してから一定時間は再登録しない
@ -396,27 +424,41 @@ public class AlarmService extends IntentService {
}
// サーバ情報APIを使う
String post_data = "instance_url=" +
Uri.encode( "https://" + account.host ) +
"&app_id=" +
Uri.encode( getPackageName() ) +
"&tag=" +
tag +
"&access_token=" +
Utils.optStringX( account.token_info, "access_token" ) +
"&device_token=" +
device_token;
StringBuilder post_data = new StringBuilder( );
post_data.append("instance_url=").append(Uri.encode( "https://" + account.host ));
post_data.append("&app_id=").append(Uri.encode( getPackageName() ));
post_data.append("&tag=").append(tag);
post_data.append("&access_token=").append(Utils.optStringX( account.token_info, "access_token" ));
post_data.append("&device_token=").append(device_token);
if( ! TextUtils.isEmpty( mCustomStreamListenerSettingString )
&& ! TextUtils.isEmpty( mCustomStreamListenerSecret )
){
post_data.append("&user_config=").append(Uri.encode(mCustomStreamListenerSettingString));
post_data.append("&app_secret=").append(Uri.encode(mCustomStreamListenerSecret));
}
Request request = new Request.Builder()
.url( APP_SERVER + "/register" )
.post( RequestBody.create( TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data ) )
.post( RequestBody.create( TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data.toString() ) )
.build();
Call call = App1.ok_http_client.newCall( request );
Call call = App1.ok_http_client.newCall( request );
Response response = call.execute();
log.e( "registerDeviceToken:%s", response );
String body=null;
try{
body =response.body().string();
}catch(Throwable ignored){
}
log.e( "registerDeviceToken: %s (%s)",response,(body==null?"":body) );
int code = response.code();
@ -428,7 +470,6 @@ public class AlarmService extends IntentService {
}
}catch( Throwable ex ){
ex.printStackTrace();
}
return false;
@ -688,6 +729,8 @@ public class AlarmService extends IntentService {
context.startService( intent );
}
private static class InjectData {
long account_db_id;

View File

@ -315,6 +315,9 @@ public class AppDataExporter {
// string
case Pref.KEY_COLUMN_WIDTH:
case Pref.KEY_MEDIA_THUMB_HEIGHT:
case Pref.KEY_STREAM_LISTENER_CONFIG_URL:
case Pref.KEY_STREAM_LISTENER_SECRET:
case Pref.KEY_STREAM_LISTENER_CONFIG_DATA:
String sv = reader.nextString();
e.putString( k, sv );
break;

View File

@ -49,8 +49,10 @@ public class Pref {
static final String KEY_DONT_CROP_MEDIA_THUMBNAIL = "DontCropMediaThumb";
static final String KEY_DEVICE_TOKEN = "device_token";
static final String KEY_INSTALL_ID = "install_id";
static final String KEY_STREAM_LISTENER_SECRET = "stream_listener_secret";
static final String KEY_STREAM_LISTENER_CONFIG_URL = "stream_listener_config_url";
static final String KEY_STREAM_LISTENER_CONFIG_DATA = "stream_listener_config_data";
// 項目を追加したらAppDataExporter#importPref のswitch文も更新すること
}

View File

@ -312,6 +312,12 @@ public class SavedAccount extends TootAccount implements LinkClickContext {
App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString( db_id ) } );
}
public static void clearRegistrationCache(){
ContentValues cv = new ContentValues();
cv.put( COL_REGISTER_TIME, 0L );
App1.getDB().update( table, cv, null, null);
}
// onResumeの時に設定を読み直す
public void reloadSetting(){
if( db_id == INVALID_ID )

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="TooManyViews"
android:id="@+id/svContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbarStyle="outsideOverlay"
android:id="@+id/svContent"
android:paddingTop="12dp"
android:paddingBottom="128dp"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="128dp"
android:paddingTop="12dp"
android:scrollbarStyle="outsideOverlay"
tools:ignore="TooManyViews"
>
<LinearLayout
@ -41,6 +41,7 @@
/>
</LinearLayout>
<View style="@style/setting_divider"/>
@ -58,6 +59,7 @@
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
@ -79,8 +81,8 @@
<TextView
style="@style/setting_row_label"
android:text="@string/minimum_column_width"
android:labelFor="@+id/etColumnWidth"
android:text="@string/minimum_column_width"
/>
<LinearLayout style="@style/setting_row_form">
@ -93,12 +95,13 @@
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/media_thumbnail_height"
android:labelFor="@+id/etMediaThumbHeight"
android:text="@string/media_thumbnail_height"
/>
<LinearLayout style="@style/setting_row_form">
@ -364,17 +367,17 @@
android:id="@+id/llFooterBG"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:background="?attr/colorColumnStripBackground"
android:orientation="horizontal"
>
<ImageView
android:id="@+id/ivFooterMenu"
app:srcCompat="?attr/ic_hamburger"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/btn_bg_ddd"
android:importantForAccessibility="no"
app:srcCompat="?attr/ic_hamburger"
/>
<View
@ -383,11 +386,13 @@
android:layout_height="match_parent"
android:background="?attr/colorImageButton"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
<HorizontalScrollView
android:id="@+id/svColumnStrip"
android:layout_width="0dp"
@ -417,6 +422,7 @@
android:layout_height="0dp"
android:layout_weight="1"
/>
<View
android:id="@+id/vFooterDivider2"
android:layout_width="1dp"
@ -426,11 +432,11 @@
<ImageView
android:id="@+id/ivFooterToot"
app:srcCompat="?attr/ic_edit"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/btn_bg_ddd"
android:contentDescription="@string/toot"
app:srcCompat="?attr/ic_edit"
/>
</LinearLayout>
@ -467,7 +473,7 @@
<LinearLayout style="@style/setting_row_form"
>
>
<TextView
style="@style/setting_row_label"
@ -583,6 +589,7 @@
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button
@ -611,7 +618,6 @@
/>
<LinearLayout style="@style/setting_row_form">
<Button
@ -624,6 +630,7 @@
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<Button
android:id="@+id/btnSettingImport"
android:layout_width="wrap_content"
@ -634,6 +641,7 @@
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -643,6 +651,40 @@
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:text="@string/custom_stream_listener"
/>
<LinearLayout style="@style/setting_row_form">
<Button
android:id="@+id/btnCustomStreamListenerEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btnCustomStreamListenerReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset"
android:textAllCaps="false"
/>
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<TextView
style="@style/setting_horizontal_stretch"
android:text="@string/custom_stream_listener_desc"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<!--<TextView-->
<!--style="@style/setting_row_label"-->
<!--android:text="@string/actions"-->

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/llContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:labelFor="@+id/etStreamListenerConfigurationUrl"
android:text="@string/configuration_url"
/>
<LinearLayout style="@style/setting_row_form">
<EditText
android:id="@+id/etStreamListenerConfigurationUrl"
style="@style/setting_horizontal_stretch"
android:inputType="textUri"
android:maxLines="1"
/>
</LinearLayout>
<View style="@style/setting_divider"/>
<TextView
style="@style/setting_row_label"
android:labelFor="@+id/etStreamListenerSecret"
android:text="@string/secret"
/>
<LinearLayout style="@style/setting_row_form">
<EditText
android:id="@+id/etStreamListenerSecret"
style="@style/setting_horizontal_stretch"
android:inputType="textPassword"
android:maxLines="1"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/custom_stream_listener_desc"
android:textSize="12sp"
/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fadeScrollbars="false"
android:fillViewport="true"
>
<TextView
android:id="@+id/tvLog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
/>
</ScrollView>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureWithLargestChild="true"
>
<Button
android:id="@+id/btnDiscard"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/discard"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btnTest"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/test"
android:textAllCaps="false"
/>
<Button
android:id="@+id/btnSave"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/save"
android:textAllCaps="false"
/>
</LinearLayout>
</LinearLayout>

View File

@ -28,22 +28,6 @@ EmojiOne Non-Artwork
- License: MIT
- Complete Legal Terms: http://opensource.org/licenses/MIT
====================================================
google/volley
https://github.com/google/volley
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.
====================================================
square/okhttp
https://github.com/square/okhttp
@ -179,3 +163,61 @@ 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.
====================================================
bumptech/glide
https://github.com/bumptech/glide
Copyright 2014 Google, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Google, Inc.
====================================================
hjson/hjson-java
https://github.com/hjson/hjson-java
The MIT License (MIT)
Copyright (c) 2013, 2014 EclipseSource
Copyright (c) 2015-2016 Christian Zangl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -331,6 +331,13 @@
<string name="draft_deleted">Brouillon effacé !</string>
<string name="draft_picker_desc">[Appuie long, pour effacer]</string>
<string name="dont_crop_media_thumbnail">Ne pas recadrer les aperçus de pièces jointes\n(redémarrage nécessaire)</string>
<string name="configuration_url">Configuration URL</string>
<string name="custom_stream_listener">Custom notification listener</string>
<string name="secret">Secret</string>
<string name="test">Test</string>
<string name="custom_stream_listener_desc">This setting is for advanced users. If you are not sure, please don\'t edit.</string>
<string name="custom_stream_listener_was_reset">Custom notification listener was reset.</string>
<string name="input_url_and_secret_then_test">Input URL and Secret, then tap Test button.</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->

View File

@ -618,5 +618,12 @@
<string name="draft_deleted">下書きを削除しました</string>
<string name="draft_picker_desc">長押しで削除</string>
<string name="dont_crop_media_thumbnail">添付メディアのサムネイルをクロップしない(アプリ再起動が必要)</string>
<string name="configuration_url">設定情報URL</string>
<string name="custom_stream_listener">カスタム通知リスナ</string>
<string name="custom_stream_listener_desc">この設定は上級者向けです。よく分からない場合は編集しないでしてください。失敗するとリアルタイム通知が動作しなくなります。</string>
<string name="secret">シークレット</string>
<string name="test">テスト</string>
<string name="custom_stream_listener_was_reset">カスタム通知リスナを使わないようにしました</string>
<string name="input_url_and_secret_then_test">URLとシークレットを入力してテストボタンを押してください</string>
</resources>

View File

@ -327,4 +327,11 @@
<string name="draft_picker_desc">Long tap to delete.</string>
<string name="draft_deleted">Draft deleted.</string>
<string name="dont_crop_media_thumbnail">Don\'t crop media thumbnail (app restart required)</string>
<string name="custom_stream_listener">Custom notification listener</string>
<string name="configuration_url">Configuration URL</string>
<string name="secret">Secret</string>
<string name="test">Test</string>
<string name="custom_stream_listener_desc">This setting is for advanced users. If you are not sure, please don\'t edit.</string>
<string name="custom_stream_listener_was_reset">Custom notification listener was reset.</string>
<string name="input_url_and_secret_then_test">Input URL and Secret, then tap Test button.</string>
</resources>