アカウント設定をつけた。アカウント削除をつけた
|
@ -1,13 +1,18 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="tateisu">
|
||||
<words>
|
||||
<w>dont</w>
|
||||
<w>emojione</w>
|
||||
<w>favourited</w>
|
||||
<w>noto</w>
|
||||
<w>nsfw</w>
|
||||
<w>reblog</w>
|
||||
<w>reblogged</w>
|
||||
<w>reblogs</w>
|
||||
<w>subwaytooter</w>
|
||||
<w>timelines</w>
|
||||
<w>unfavourite</w>
|
||||
<w>unreblog</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
|
@ -30,8 +30,10 @@ dependencies {
|
|||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||
compile 'com.android.volley:volley:1.0.0'
|
||||
compile 'com.android.support:customtabs:24.2.0'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.7.0'
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'uk.co.chrisjenx:calligraphy:2.2.0'
|
||||
|
||||
}
|
||||
|
|
|
@ -18,20 +18,43 @@
|
|||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ActPost"
|
||||
android:label="@string/act_post"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
>
|
||||
</activity>
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActAccountSetting"
|
||||
android:label="@string/account_setting"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActAppSetting"
|
||||
android:label="@string/app_setting"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActColumnList"
|
||||
android:label="@string/column_list"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
/>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,184 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
||||
public class ActAccountSetting extends AppCompatActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
|
||||
static final LogCategory log = new LogCategory( "ActAccountSetting" );
|
||||
|
||||
static final String KEY_ACCOUNT_DB_ID = "account_db_id";
|
||||
|
||||
public static void open( Context context, SavedAccount ai ){
|
||||
Intent intent = new Intent( context, ActAccountSetting.class );
|
||||
intent.putExtra( KEY_ACCOUNT_DB_ID, ai.db_id );
|
||||
context.startActivity( intent );
|
||||
}
|
||||
|
||||
SavedAccount account;
|
||||
|
||||
@Override
|
||||
protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
initUI();
|
||||
account = SavedAccount.loadAccount( log, getIntent().getLongExtra( KEY_ACCOUNT_DB_ID, - 1L ) );
|
||||
if( account == null ) finish();
|
||||
loadUIFromData( account );
|
||||
|
||||
}
|
||||
|
||||
TextView tvInstance;
|
||||
TextView tvUser;
|
||||
View btnAccessToken;
|
||||
View btnAccountRemove;
|
||||
Button btnVisibility;
|
||||
Switch swConfirmBeforeBoost;
|
||||
Switch swNSFWOpen;
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_account_setting );
|
||||
tvInstance = (TextView) findViewById( R.id.tvInstance );
|
||||
tvUser = (TextView) findViewById( R.id.tvUser );
|
||||
btnAccessToken = findViewById( R.id.btnAccessToken );
|
||||
btnAccountRemove = findViewById( R.id.btnAccountRemove );
|
||||
btnVisibility = (Button) findViewById( R.id.btnVisibility );
|
||||
swConfirmBeforeBoost = (Switch) findViewById( R.id.swConfirmBeforeBoost );
|
||||
swNSFWOpen = (Switch) findViewById( R.id.swNSFWOpen );
|
||||
|
||||
btnAccessToken.setOnClickListener( this );
|
||||
btnAccountRemove.setOnClickListener( this );
|
||||
btnVisibility.setOnClickListener( this );
|
||||
|
||||
swNSFWOpen.setOnCheckedChangeListener( this );
|
||||
swConfirmBeforeBoost.setOnCheckedChangeListener( this );
|
||||
}
|
||||
|
||||
private void loadUIFromData( SavedAccount a ){
|
||||
tvInstance.setText( a.host );
|
||||
tvUser.setText( a.user );
|
||||
|
||||
String sv = a.visibility;
|
||||
if( sv != null ){
|
||||
visibility = sv;
|
||||
}
|
||||
swConfirmBeforeBoost.setChecked( a.confirm_boost );
|
||||
swNSFWOpen.setChecked( a.dont_hide_nsfw );
|
||||
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
private void saveUIToData(){
|
||||
account.visibility = visibility;
|
||||
account.confirm_boost = swConfirmBeforeBoost.isChecked();
|
||||
account.dont_hide_nsfw = swNSFWOpen.isChecked();
|
||||
account.saveSetting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
|
||||
saveUIToData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnAccessToken:
|
||||
performAccessToken();
|
||||
break;
|
||||
case R.id.btnAccountRemove:
|
||||
performAccountRemove();
|
||||
break;
|
||||
case R.id.btnVisibility:
|
||||
performVisibility();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
String visibility = TootStatus.VISIBILITY_PUBLIC;
|
||||
|
||||
private void updateVisibility(){
|
||||
btnVisibility.setText( Styler.getVisibilityString( this, visibility ) );
|
||||
}
|
||||
|
||||
private void performVisibility(){
|
||||
final String[] caption_list = new String[]{
|
||||
getString( R.string.visibility_public ),
|
||||
getString( R.string.visibility_unlisted ),
|
||||
getString( R.string.visibility_private ),
|
||||
getString( R.string.visibility_direct ),
|
||||
};
|
||||
|
||||
// public static final String VISIBILITY_PUBLIC ="public";
|
||||
// public static final String VISIBILITY_UNLISTED ="unlisted";
|
||||
// public static final String VISIBILITY_PRIVATE ="private";
|
||||
// public static final String VISIBILITY_DIRECT ="direct";
|
||||
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.choose_visibility )
|
||||
.setItems( caption_list, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
switch( which ){
|
||||
case 0:
|
||||
visibility = TootStatus.VISIBILITY_PUBLIC;
|
||||
break;
|
||||
case 1:
|
||||
visibility = TootStatus.VISIBILITY_UNLISTED;
|
||||
break;
|
||||
case 2:
|
||||
visibility = TootStatus.VISIBILITY_PRIVATE;
|
||||
break;
|
||||
case 3:
|
||||
visibility = TootStatus.VISIBILITY_DIRECT;
|
||||
break;
|
||||
}
|
||||
updateVisibility();
|
||||
saveUIToData();
|
||||
}
|
||||
} )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.show();
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
private void performAccountRemove(){
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.confirm )
|
||||
.setMessage( R.string.confirm_account_remove )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
account.delete();
|
||||
finish();
|
||||
}
|
||||
} )
|
||||
.show();
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
private void performAccessToken(){
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
/**
|
||||
* Created by tateisu on 2017/04/22.
|
||||
*/
|
||||
|
||||
public class ActAppSetting extends AppCompatActivity {
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
/**
|
||||
* Created by tateisu on 2017/04/22.
|
||||
*/
|
||||
|
||||
public class ActColumnList extends AppCompatActivity {
|
||||
}
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter;
|
|||
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
|
@ -11,6 +12,7 @@ import android.support.design.widget.FloatingActionButton;
|
|||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.os.AsyncTaskCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.support.design.widget.NavigationView;
|
||||
|
@ -29,21 +31,34 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringBufferInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.dialog.AccountPicker;
|
||||
import jp.juggler.subwaytooter.dialog.LoginForm;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
public class ActMain extends AppCompatActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener {
|
||||
public static final LogCategory log = new LogCategory( "ActMain" );
|
||||
|
||||
static boolean update_at_resume = false;
|
||||
|
||||
// @Override
|
||||
// protected void attachBaseContext(Context newBase) {
|
||||
// super.attachBaseContext( CalligraphyContextWrapper.wrap(newBase));
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void onCreate( Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
|
@ -62,6 +77,26 @@ public class ActMain extends AppCompatActivity
|
|||
protected void onResume(){
|
||||
super.onResume();
|
||||
HTMLDecoder.link_callback = link_click_listener;
|
||||
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
int size =pager_adapter.getCount();
|
||||
for(int i=size-1;i>=0;--i){
|
||||
Column column = pager_adapter.getColumn( i );
|
||||
SavedAccount sa = SavedAccount.loadAccount( log, column.access_info.db_id );
|
||||
if( sa == null ){
|
||||
pager_adapter.removeColumn( pager,column );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(update_at_resume){
|
||||
update_at_resume = false;
|
||||
// TODO: 各カラムを更新する
|
||||
}
|
||||
|
||||
if( pager_adapter.getCount() == 0){
|
||||
llEmpty.setVisibility( View.VISIBLE );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,6 +111,8 @@ public class ActMain extends AppCompatActivity
|
|||
DrawerLayout drawer = (DrawerLayout) findViewById( R.id.drawer_layout );
|
||||
if( drawer.isDrawerOpen( GravityCompat.START ) ){
|
||||
drawer.closeDrawer( GravityCompat.START );
|
||||
}else if( ! pager_adapter.column_list.isEmpty() ){
|
||||
performColumnClose( false,pager_adapter.getColumn( pager.getCurrentItem() ) );
|
||||
}else{
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
@ -127,6 +164,13 @@ public class ActMain extends AppCompatActivity
|
|||
}else if( id == R.id.nav_add_notifications ){
|
||||
performAddTimeline( Column.TYPE_TL_NOTIFICATIONS );
|
||||
|
||||
}else if( id == R.id.nav_app_setting ){
|
||||
performAppSetting( );
|
||||
}else if( id == R.id.nav_account_setting ){
|
||||
performAccountSetting();
|
||||
}else if( id == R.id.nav_column_list ){
|
||||
performColumnList();
|
||||
|
||||
// Handle the camera action
|
||||
// }else if( id == R.id.nav_gallery ){
|
||||
//
|
||||
|
@ -180,8 +224,8 @@ public class ActMain extends AppCompatActivity
|
|||
fabMenu.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( View view ){
|
||||
if( ! drawer.isDrawerOpen( Gravity.LEFT ) ){
|
||||
drawer.openDrawer( Gravity.LEFT );
|
||||
if( ! drawer.isDrawerOpen( Gravity.START ) ){
|
||||
drawer.openDrawer( Gravity.START );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
@ -202,22 +246,18 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
final AsyncTask< Void, String, TootApiResult > task = new AsyncTask< Void, String, TootApiResult >() {
|
||||
|
||||
boolean __isCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
long row_id;
|
||||
|
||||
@Override
|
||||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient api_client = new TootApiClient( ActMain.this, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isCancelled(){
|
||||
return __isCancelled();
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishProgress( final String s ){
|
||||
public void publishApiProgress( final String s ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
|
@ -229,11 +269,11 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
api_client.setUserInfo( instance, user_mail, password );
|
||||
|
||||
TootApiResult result = api_client.get( "/api/v1/accounts/verify_credentials" );
|
||||
TootApiResult result = api_client.request( "/api/v1/accounts/verify_credentials" );
|
||||
if( result != null && result.object != null ){
|
||||
TootAccount ta = TootAccount.parse( log, result.object );
|
||||
String user = ta.username +"@" + instance;
|
||||
this.row_id = SavedAccount.insert( log, instance, user, result.object ,result.token_info );
|
||||
this.row_id = SavedAccount.insert( instance, user, result.object ,result.token_info );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -281,13 +321,39 @@ public class ActMain extends AppCompatActivity
|
|||
pager.setCurrentItem( idx );
|
||||
}
|
||||
|
||||
public void performColumnClose( Column column ){
|
||||
public void performColumnClose( boolean bConfirm,final Column column ){
|
||||
if(! bConfirm ){
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.confirm )
|
||||
.setMessage( R.string.close_column )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
performColumnClose( true,column );
|
||||
}
|
||||
} )
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
int page_showing = pager.getCurrentItem();
|
||||
int page_delete = pager_adapter.column_list.indexOf( column );
|
||||
pager_adapter.removeColumn( pager, column );
|
||||
if( pager_adapter.getCount() == 0 ){
|
||||
llEmpty.setVisibility( View.VISIBLE );
|
||||
}else if( page_showing > 0 && page_showing == page_delete ){
|
||||
pager.setCurrentItem( page_showing-1 ,true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void performOpenUser(SavedAccount access_info,TootAccount user){
|
||||
llEmpty.setVisibility( View.GONE );
|
||||
//
|
||||
Column col = new Column( ActMain.this, access_info, Column.TYPE_TL_STATUSES, user.id );
|
||||
pager.setCurrentItem( pager_adapter.addColumn( pager, col ) ,true);
|
||||
}
|
||||
|
||||
private void performAddTimeline( final int type, final Object... params ){
|
||||
AccountPicker.pick( this, new AccountPicker.AccountPickerCallback() {
|
||||
@Override
|
||||
|
@ -296,7 +362,7 @@ public class ActMain extends AppCompatActivity
|
|||
//
|
||||
Column col = new Column( ActMain.this, ai, type, ai.id, params );
|
||||
int idx = pager_adapter.addColumn( pager, col );
|
||||
pager.setCurrentItem( idx );
|
||||
pager.setCurrentItem( idx ,true);
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
@ -316,8 +382,8 @@ public class ActMain extends AppCompatActivity
|
|||
customTabsIntent.launchUrl( this, Uri.parse( url ) );
|
||||
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
log.e( ex, "openChromeTab failed." );
|
||||
// ex.printStackTrace();
|
||||
log.e( ex, "openChromeTab failed. url=%s",url );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,8 +459,281 @@ public class ActMain extends AppCompatActivity
|
|||
|
||||
Column c = pager_adapter.getColumn( pager.getCurrentItem() );
|
||||
if( c != null && c.access_info != null ){
|
||||
ActPost.open( this, c.access_info.db_id );
|
||||
ActPost.open( this, c.access_info.db_id ,null );
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void showColumnMatchAccount( SavedAccount account ){
|
||||
for( Column column : pager_adapter.column_list ){
|
||||
if( account.user.equals( column.access_info.user ) ){
|
||||
column.fireVisualCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// favourite
|
||||
|
||||
final HashSet<String> map_busy_fav = new HashSet<>( );
|
||||
|
||||
boolean isBusyFav(SavedAccount account,TootStatus status){
|
||||
String busy_key = account.host+":"+ status.id;
|
||||
return map_busy_fav.contains(busy_key);
|
||||
}
|
||||
|
||||
public void performFavourite( final SavedAccount account, final TootStatus status ){
|
||||
//
|
||||
final String busy_key = account.host+":"+ status.id;
|
||||
//
|
||||
if( map_busy_fav.contains(busy_key) ){
|
||||
Utils.showToast( this,false,R.string.wait_previous_operation );
|
||||
return;
|
||||
}
|
||||
//
|
||||
map_busy_fav.add( busy_key );
|
||||
//
|
||||
new AsyncTask<Void,Void,TootApiResult>(){
|
||||
final boolean new_state = ! status.favourited;
|
||||
TootStatus new_status;
|
||||
|
||||
@Override
|
||||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishApiProgress( final String s ){
|
||||
}
|
||||
} );
|
||||
client.setAccount( account );
|
||||
|
||||
Request.Builder request_builder = new Request.Builder()
|
||||
.post( RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
|
||||
,""
|
||||
));
|
||||
|
||||
TootApiResult result = client.request(
|
||||
(new_state
|
||||
? "/api/v1/statuses/"+status.id+"/favourite"
|
||||
: "/api/v1/statuses/"+status.id+"/unfavourite"
|
||||
)
|
||||
, request_builder );
|
||||
if( result.object != null ){
|
||||
new_status = TootStatus.parse( log,result.object );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled( TootApiResult result ){
|
||||
super.onPostExecute( result );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute( TootApiResult result ){
|
||||
map_busy_fav.remove( busy_key);
|
||||
if( new_status != null ){
|
||||
// カウント数は遅延があるみたい
|
||||
if( new_state && new_status.favourites_count <= status.favourites_count ){
|
||||
// 星つけたのにカウントが上がらないのは違和感あるので、表示をいじる
|
||||
new_status.favourites_count = status.favourites_count +1;
|
||||
}else if( !new_state && new_status.favourites_count >= status.favourites_count ){
|
||||
// 星外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
||||
new_status.favourites_count = status.favourites_count -1;
|
||||
if( new_status.favourites_count < 0 ){
|
||||
new_status.favourites_count = 0;
|
||||
}
|
||||
}
|
||||
for( Column column : pager_adapter.column_list ){
|
||||
column.findStatus( account, new_status.id, new Column.StatusEntryCallback() {
|
||||
@Override
|
||||
public void onIterate( TootStatus status ){
|
||||
status.favourited = new_status.favourited;
|
||||
status.favourites_count = new_status.favourites_count;
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
if( result != null) Utils.showToast( ActMain.this,true,result.error );
|
||||
}
|
||||
showColumnMatchAccount(account);
|
||||
}
|
||||
|
||||
}.execute();
|
||||
showColumnMatchAccount(account);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// boost
|
||||
final HashSet<String> map_busy_boost = new HashSet<>( );
|
||||
|
||||
boolean isBusyBoost(SavedAccount account,TootStatus status){
|
||||
String busy_key = account.host+":"+ status.id;
|
||||
return map_busy_boost.contains( busy_key);
|
||||
}
|
||||
|
||||
public void performBoost( final SavedAccount account, final TootStatus status ,boolean bConfirmed){
|
||||
//
|
||||
final String busy_key = account.host + ":" + status.id;
|
||||
//
|
||||
if(map_busy_boost.contains( busy_key ) ){
|
||||
Utils.showToast( this, false, R.string.wait_previous_operation );
|
||||
return;
|
||||
}
|
||||
|
||||
if( status.reblogged ){
|
||||
// FAVがついているか、FAV操作中はBoostを外せない
|
||||
if( isBusyFav( account, status ) || status.favourited ){
|
||||
Utils.showToast( this, false, R.string.cant_remove_boost_while_favourited );
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
if(!bConfirmed && account.confirm_boost ){
|
||||
// TODO: アカウント設定でスキップさせたい
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.confirm)
|
||||
.setMessage(R.string.confirm_boost)
|
||||
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
performBoost( account, status ,true);
|
||||
}
|
||||
} )
|
||||
.setNegativeButton( R.string.cancel,null )
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
map_busy_boost.add( busy_key);
|
||||
//
|
||||
new AsyncTask<Void,Void,TootApiResult>(){
|
||||
final boolean new_state = ! status.reblogged;
|
||||
TootStatus new_status;
|
||||
|
||||
@Override
|
||||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient client = new TootApiClient( ActMain.this, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishApiProgress( final String s ){
|
||||
}
|
||||
} );
|
||||
client.setAccount( account );
|
||||
|
||||
Request.Builder request_builder = new Request.Builder()
|
||||
.post( RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
|
||||
,""
|
||||
));
|
||||
|
||||
TootApiResult result = client.request(
|
||||
"/api/v1/statuses/"+status.id+(new_state ? "/reblog" : "/unreblog")
|
||||
, request_builder );
|
||||
|
||||
if( result.object != null ){
|
||||
// reblog,unreblog のレスポンスは信用ならんのでステータスを再取得する
|
||||
result = client.request("/api/v1/statuses/"+status.id);
|
||||
if( result.object != null ){
|
||||
new_status = TootStatus.parse( log, result.object );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled( TootApiResult result ){
|
||||
super.onPostExecute( result );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute( TootApiResult result ){
|
||||
map_busy_boost.remove( busy_key);
|
||||
if( new_status != null ){
|
||||
// カウント数は遅延があるみたい
|
||||
if( new_status.reblogged && new_status.reblogs_count <= status.reblogs_count ){
|
||||
// 星つけたのにカウントが上がらないのは違和感あるので、表示をいじる
|
||||
new_status.reblogs_count = status.reblogs_count +1;
|
||||
}else if( !new_status.reblogged && new_status.reblogs_count >= status.reblogs_count ){
|
||||
// 星外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
||||
new_status.reblogs_count = status.reblogs_count -1;
|
||||
if( new_status.reblogs_count < 0 ){
|
||||
new_status.reblogs_count = 0;
|
||||
}
|
||||
}
|
||||
for( Column column : pager_adapter.column_list ){
|
||||
column.findStatus( account, new_status.id, new Column.StatusEntryCallback() {
|
||||
@Override
|
||||
public void onIterate( TootStatus status ){
|
||||
status.reblogged = new_status.reblogged;
|
||||
status.reblogs_count = new_status.reblogs_count;
|
||||
}
|
||||
});
|
||||
}
|
||||
}else{
|
||||
if( result != null) Utils.showToast( ActMain.this,true,result.error );
|
||||
}
|
||||
showColumnMatchAccount(account);
|
||||
}
|
||||
|
||||
}.execute();
|
||||
|
||||
showColumnMatchAccount(account);
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
public void performMore( SavedAccount account, TootStatus status ){
|
||||
// open menu
|
||||
// Expand this status
|
||||
// Mute user
|
||||
// Block user
|
||||
// report user
|
||||
Utils.showToast( this,false,"not implemented. toot="+status.decoded_content );
|
||||
}
|
||||
|
||||
public void performReply( SavedAccount account, TootStatus status ){
|
||||
Utils.showToast( this,false,"not implemented. toot="+status.decoded_content );
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
private void performColumnList(){
|
||||
|
||||
Utils.showToast( this,false,"not implemented." );
|
||||
|
||||
}
|
||||
|
||||
private void performAccountSetting(){
|
||||
AccountPicker.pick( this, new AccountPicker.AccountPickerCallback() {
|
||||
@Override
|
||||
public void onAccountPicked( SavedAccount ai ){
|
||||
ActAccountSetting.open( ActMain.this, ai);
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private void performAppSetting(){
|
||||
Utils.showToast( this,false,"not implemented." );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,44 +1,122 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.os.AsyncTaskCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
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.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.volley.toolbox.NetworkImageView;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootApiClient;
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.BufferedSink;
|
||||
|
||||
public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
||||
static final LogCategory log = new LogCategory( "ActPost" );
|
||||
|
||||
static final String KEY_ACCOUNT_DB_ID = "account_db_id";
|
||||
static final String KEY_VISIBILITY = "visibility";
|
||||
static final String KEY_ATTACHMENT_LIST = "attachment_list";
|
||||
|
||||
public static void open( Context context, long account_db_id ){
|
||||
public static void open( Context context, long account_db_id, String visibility ){
|
||||
Intent intent = new Intent( context, ActPost.class );
|
||||
intent.putExtra( KEY_ACCOUNT_DB_ID, account_db_id );
|
||||
if( visibility != null ) intent.putExtra( KEY_VISIBILITY, visibility );
|
||||
context.startActivity( intent );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnAccount:
|
||||
performAccountChooser();
|
||||
break;
|
||||
|
||||
case R.id.btnVisibility:
|
||||
performVisibility();
|
||||
break;
|
||||
|
||||
case R.id.btnAttachment:
|
||||
performAttachment();
|
||||
break;
|
||||
|
||||
case R.id.ivMedia1:
|
||||
performAttachmentDelete( 0 );
|
||||
break;
|
||||
case R.id.ivMedia2:
|
||||
performAttachmentDelete( 1 );
|
||||
break;
|
||||
case R.id.ivMedia3:
|
||||
performAttachmentDelete( 2 );
|
||||
break;
|
||||
case R.id.ivMedia4:
|
||||
performAttachmentDelete( 3 );
|
||||
break;
|
||||
|
||||
case R.id.btnPost:
|
||||
performPost();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final int REQUEST_CODE_ATTACHMENT = 1;
|
||||
|
||||
@Override
|
||||
protected void onActivityResult( int requestCode, int resultCode, Intent data ){
|
||||
if( resultCode == RESULT_OK ){
|
||||
if( requestCode == REQUEST_CODE_ATTACHMENT ){
|
||||
if( data != null ){
|
||||
Uri uri = data.getData();
|
||||
if( uri != null ){
|
||||
String type = data.getType();
|
||||
if( TextUtils.isEmpty( type ) ){
|
||||
type = getContentResolver().getType( uri );
|
||||
}
|
||||
addAttachment( uri, type );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onActivityResult( requestCode, resultCode, data );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,34 +132,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
}
|
||||
|
||||
if( savedInstanceState != null ){
|
||||
|
||||
}else{
|
||||
Intent intent = getIntent();
|
||||
long account_db_id = intent.getLongExtra( KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_ID );
|
||||
if( account_db_id != SavedAccount.INVALID_ID ){
|
||||
for( int i = 0, ie = account_list.size() ; i < ie ; ++ i ){
|
||||
SavedAccount a = account_list.get( i );
|
||||
if( a.db_id == account_db_id ){
|
||||
setAccount( a );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( this.account == null ){
|
||||
setAccount( null );
|
||||
}
|
||||
|
||||
updateContentWarning();
|
||||
updateMediaAttachment();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState( Bundle savedInstanceState ){
|
||||
super.onRestoreInstanceState( savedInstanceState );
|
||||
|
||||
if( savedInstanceState != null ){
|
||||
long account_db_id = savedInstanceState.getLong(KEY_ACCOUNT_DB_ID,SavedAccount.INVALID_ID);
|
||||
long account_db_id = savedInstanceState.getLong( KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_ID );
|
||||
if( account_db_id != SavedAccount.INVALID_ID ){
|
||||
for( int i = 0, ie = account_list.size() ; i < ie ; ++ i ){
|
||||
SavedAccount a = account_list.get( i );
|
||||
|
@ -92,22 +143,81 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
String sv = savedInstanceState.getString( KEY_VISIBILITY );
|
||||
if( TextUtils.isEmpty( sv ) ) sv = account.visibility;
|
||||
this.visibility = sv;
|
||||
|
||||
sv = savedInstanceState.getString( KEY_ATTACHMENT_LIST );
|
||||
if( ! TextUtils.isEmpty( sv ) ){
|
||||
try{
|
||||
attachment_list.clear();
|
||||
JSONArray array = new JSONArray( sv );
|
||||
for( int i = 0, ie = array.length() ; i < ie ; ++ i ){
|
||||
try{
|
||||
TootAttachment a = TootAttachment.parse( log, array.optJSONObject( i ) );
|
||||
if( a != null ){
|
||||
PostAttachment pa = new PostAttachment();
|
||||
pa.status = ATTACHMENT_UPLOADED;
|
||||
pa.attachment = a;
|
||||
}
|
||||
}catch( Throwable ex2 ){
|
||||
ex2.printStackTrace();
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
Intent intent = getIntent();
|
||||
long account_db_id = intent.getLongExtra( KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_ID );
|
||||
if( account_db_id != SavedAccount.INVALID_ID ){
|
||||
for( int i = 0, ie = account_list.size() ; i < ie ; ++ i ){
|
||||
SavedAccount a = account_list.get( i );
|
||||
if( a.db_id == account_db_id ){
|
||||
setAccount( a );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String sv = intent.getStringExtra( KEY_VISIBILITY );
|
||||
if( TextUtils.isEmpty( sv ) ) sv = account.visibility;
|
||||
this.visibility = sv;
|
||||
}
|
||||
|
||||
if( this.account == null ){
|
||||
setAccount( null );
|
||||
}
|
||||
|
||||
updateContentWarning();
|
||||
updateMediaAttachment();
|
||||
showMediaAttachment();
|
||||
updateVisibility();
|
||||
updateTextCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState( Bundle outState ){
|
||||
if( account != null ){
|
||||
outState.putLong( KEY_ACCOUNT_DB_ID,account.db_id );
|
||||
outState.putLong( KEY_ACCOUNT_DB_ID, account.db_id );
|
||||
}
|
||||
if( visibility != null ){
|
||||
outState.putString( KEY_VISIBILITY, visibility );
|
||||
}
|
||||
if( ! attachment_list.isEmpty() ){
|
||||
JSONArray array = new JSONArray();
|
||||
for( PostAttachment pa : attachment_list ){
|
||||
if( pa.status == ATTACHMENT_UPLOADED ){
|
||||
// アップロード完了したものだけ保持する
|
||||
array.put( pa.attachment.json );
|
||||
}
|
||||
}
|
||||
outState.putString( KEY_ATTACHMENT_LIST, array.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
Button btnAccount;
|
||||
ImageButton btnVisibility;
|
||||
View btnAttachment;
|
||||
View btnPost;
|
||||
View llAttachment;
|
||||
|
@ -115,17 +225,18 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
NetworkImageView ivMedia2;
|
||||
NetworkImageView ivMedia3;
|
||||
NetworkImageView ivMedia4;
|
||||
CheckBox cbNSFW;
|
||||
CheckBox cbContentWarning;
|
||||
EditText etContentWarning;
|
||||
EditText etContent;
|
||||
TextView tvCharCount;
|
||||
ArrayList< SavedAccount > account_list;
|
||||
SavedAccount account;
|
||||
|
||||
private void initUI(){
|
||||
setContentView( R.layout.act_post );
|
||||
|
||||
btnAccount = (Button) findViewById( R.id.btnAccount );
|
||||
btnVisibility = (ImageButton) findViewById( R.id.btnVisibility );
|
||||
btnAttachment = findViewById( R.id.btnAttachment );
|
||||
btnPost = findViewById( R.id.btnPost );
|
||||
llAttachment = findViewById( R.id.llAttachment );
|
||||
|
@ -133,6 +244,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
ivMedia2 = (NetworkImageView) findViewById( R.id.ivMedia2 );
|
||||
ivMedia3 = (NetworkImageView) findViewById( R.id.ivMedia3 );
|
||||
ivMedia4 = (NetworkImageView) findViewById( R.id.ivMedia4 );
|
||||
cbNSFW = (CheckBox) findViewById( R.id.cbNSFW );
|
||||
cbContentWarning = (CheckBox) findViewById( R.id.cbContentWarning );
|
||||
etContentWarning = (EditText) findViewById( R.id.etContentWarning );
|
||||
etContent = (EditText) findViewById( R.id.etContent );
|
||||
|
@ -147,13 +259,14 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
} );
|
||||
|
||||
btnAccount.setOnClickListener( this );
|
||||
btnVisibility.setOnClickListener( this );
|
||||
btnAttachment.setOnClickListener( this );
|
||||
btnPost.setOnClickListener( this );
|
||||
llAttachment = findViewById( R.id.llAttachment );
|
||||
ivMedia1.setOnClickListener( this );
|
||||
ivMedia2.setOnClickListener( this );
|
||||
ivMedia3.setOnClickListener( this );
|
||||
ivMedia4.setOnClickListener( this );
|
||||
|
||||
cbContentWarning.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){
|
||||
|
@ -180,53 +293,451 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
|
|||
}
|
||||
|
||||
private void updateTextCount(){
|
||||
tvCharCount.setText( 500 - etContent.getText().length() );
|
||||
}
|
||||
|
||||
void setAccount( SavedAccount a ){
|
||||
this.account = a;
|
||||
btnAccount.setText( a == null ? getString( R.string.not_selected ) : a.getFullAcct( a ) );
|
||||
tvCharCount.setText( Integer.toString( 500 - etContent.getText().length() ) );
|
||||
}
|
||||
|
||||
private void updateContentWarning(){
|
||||
etContentWarning.setVisibility( cbContentWarning.isChecked() ? View.VISIBLE : View.GONE );
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
// Account
|
||||
|
||||
private void updateMediaAttachment(){
|
||||
if( attachment_list.isEmpty() ){
|
||||
llAttachment.setVisibility( View.GONE );
|
||||
}else{
|
||||
llAttachment.setVisibility( View.VISIBLE );
|
||||
showAttachment( ivMedia1, 0 );
|
||||
showAttachment( ivMedia2, 0 );
|
||||
showAttachment( ivMedia3, 0 );
|
||||
showAttachment( ivMedia4, 0 );
|
||||
}
|
||||
SavedAccount account;
|
||||
|
||||
void setAccount( SavedAccount a ){
|
||||
this.account = a;
|
||||
btnAccount.setText(
|
||||
( a == null ? getString( R.string.not_selected ) : a.getFullAcct( a ) )
|
||||
);
|
||||
}
|
||||
|
||||
private void showAttachment( NetworkImageView iv, int idx ){
|
||||
if( idx >= attachment_list.size() ){
|
||||
iv.setVisibility( View.GONE );
|
||||
}else{
|
||||
iv.setVisibility( View.VISIBLE );
|
||||
PostAttachment a = attachment_list.get( idx );
|
||||
if( a.status == ATTACHMENT_UPLOADING ){
|
||||
iv.setImageResource( R.drawable.ic_loading );
|
||||
}else{
|
||||
iv.setImageBitmap( a.bitmap );
|
||||
}
|
||||
private void performAccountChooser(){
|
||||
// TODO: mention の状況によっては別サーバを選べないかもしれない
|
||||
|
||||
// TODO: 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない
|
||||
|
||||
final ArrayList< SavedAccount > tmp_account_list = new ArrayList<>();
|
||||
tmp_account_list.addAll( account_list );
|
||||
String[] caption_list = new String[ tmp_account_list.size() ];
|
||||
for( int i = 0, ie = tmp_account_list.size() ; i < ie ; ++ i ){
|
||||
caption_list[ i ] = tmp_account_list.get( i ).user;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.choose_account )
|
||||
.setItems( caption_list, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
if( which >= 0 && which < tmp_account_list.size() ){
|
||||
setAccount( tmp_account_list.get( which ) );
|
||||
}
|
||||
}
|
||||
} )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.show();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// Attachment
|
||||
|
||||
static final int ATTACHMENT_UPLOADING = 1;
|
||||
static final int ATTACHMENT_UPLOADED = 2;
|
||||
|
||||
static class PostAttachment {
|
||||
int status;
|
||||
Bitmap bitmap;
|
||||
String url;
|
||||
TootAttachment attachment;
|
||||
}
|
||||
|
||||
final ArrayList< PostAttachment > attachment_list = new ArrayList<>();
|
||||
|
||||
private void showMediaAttachment(){
|
||||
if( attachment_list.isEmpty() ){
|
||||
llAttachment.setVisibility( View.GONE );
|
||||
cbNSFW.setVisibility( View.GONE );
|
||||
}else{
|
||||
llAttachment.setVisibility( View.VISIBLE );
|
||||
cbNSFW.setVisibility( View.VISIBLE );
|
||||
showAttachment_sub( ivMedia1, 0 );
|
||||
showAttachment_sub( ivMedia2, 1 );
|
||||
showAttachment_sub( ivMedia3, 2 );
|
||||
showAttachment_sub( ivMedia4, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
private void showAttachment_sub( NetworkImageView iv, int idx ){
|
||||
if( idx >= attachment_list.size() ){
|
||||
iv.setVisibility( View.GONE );
|
||||
}else{
|
||||
iv.setVisibility( View.VISIBLE );
|
||||
PostAttachment a = attachment_list.get( idx );
|
||||
if( a.status == ATTACHMENT_UPLOADING ){
|
||||
iv.setImageDrawable( ContextCompat.getDrawable(this,R.drawable.ic_loading ));
|
||||
}else if( a.attachment != null ){
|
||||
iv.setImageUrl( a.attachment.preview_url, App1.getImageLoader() );
|
||||
}else{
|
||||
iv.setImageDrawable( ContextCompat.getDrawable(this,R.drawable.ic_unknown ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添付した画像をタップ
|
||||
void performAttachmentDelete( int idx ){
|
||||
final PostAttachment pa = attachment_list.get( idx );
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.confirm_delete_attachment )
|
||||
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
try{
|
||||
attachment_list.remove( pa );
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
showMediaAttachment();
|
||||
}
|
||||
} )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.show();
|
||||
|
||||
}
|
||||
|
||||
private void performAttachment(){
|
||||
if( attachment_list.size() >= 4 ){
|
||||
Utils.showToast( this, false, R.string.attachment_too_many );
|
||||
return;
|
||||
}
|
||||
if( account == null ){
|
||||
Utils.showToast( this, false, R.string.account_select_please );
|
||||
return;
|
||||
}
|
||||
|
||||
// SAFのIntentで開く
|
||||
try{
|
||||
Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT );
|
||||
intent.addCategory( Intent.CATEGORY_OPENABLE );
|
||||
intent.setType( "*/*" );
|
||||
intent.putExtra( Intent.EXTRA_MIME_TYPES, new String[]{ "image/*", "video/*" } );
|
||||
startActivityForResult( intent, REQUEST_CODE_ATTACHMENT );
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
Utils.showToast( this, ex, "ACTION_OPEN_DOCUMENT failed." );
|
||||
}
|
||||
}
|
||||
|
||||
static final byte[] hex = Utils.encodeUTF8( "0123456789abcdef" );
|
||||
|
||||
void addAttachment( final Uri uri, final String mime_type ){
|
||||
if( attachment_list.size() >= 4 ){
|
||||
Utils.showToast( this, false, R.string.attachment_too_many );
|
||||
return;
|
||||
}
|
||||
if( account == null ){
|
||||
Utils.showToast( this, false, R.string.account_select_please );
|
||||
return;
|
||||
}
|
||||
|
||||
final PostAttachment pa = new PostAttachment();
|
||||
pa.status = ATTACHMENT_UPLOADING;
|
||||
attachment_list.add( pa );
|
||||
showMediaAttachment();
|
||||
|
||||
new AsyncTask< Void, Void, TootApiResult >() {
|
||||
final SavedAccount target_account = account;
|
||||
|
||||
@Override
|
||||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient client = new TootApiClient( ActPost.this, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishApiProgress( String s ){
|
||||
}
|
||||
} );
|
||||
client.setAccount( target_account );
|
||||
|
||||
if( TextUtils.isEmpty( mime_type ) ){
|
||||
return new TootApiResult( "mime_type is null." );
|
||||
}
|
||||
|
||||
try{
|
||||
final long content_length = getStreamSize( true, getContentResolver().openInputStream( uri ) );
|
||||
if( content_length > 8000000 ){
|
||||
return new TootApiResult( getString( R.string.file_size_too_big ) );
|
||||
}
|
||||
RequestBody multipart_body = new MultipartBody.Builder()
|
||||
.setType( MultipartBody.FORM )
|
||||
.addFormDataPart(
|
||||
"file"
|
||||
, getDocumentName( uri )
|
||||
, new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType(){
|
||||
return MediaType.parse( mime_type );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException{
|
||||
return content_length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo( BufferedSink sink ) throws IOException{
|
||||
InputStream is = getContentResolver().openInputStream( uri );
|
||||
try{
|
||||
byte[] tmp = new byte[ 4096 ];
|
||||
for( ; ; ){
|
||||
int r = is.read( tmp, 0, tmp.length );
|
||||
if( r <= 0 ) break;
|
||||
sink.write( tmp, 0, r );
|
||||
}
|
||||
}finally{
|
||||
IOUtils.closeQuietly( is );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
Request.Builder request_builder = new Request.Builder()
|
||||
.post( multipart_body );
|
||||
|
||||
TootApiResult result = client.request( "/api/v1/media", request_builder );
|
||||
if( result.object != null ){
|
||||
pa.attachment = TootAttachment.parse( log, result.object );
|
||||
if( pa.attachment == null ){
|
||||
result.error = "TootAttachment.parse failed";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, "read failed." ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(){
|
||||
onPostExecute( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute( TootApiResult result ){
|
||||
pa.status = ATTACHMENT_UPLOADED;
|
||||
|
||||
if( pa.attachment == null ){
|
||||
if( result != null ){
|
||||
Utils.showToast( ActPost.this, true, result.error );
|
||||
}
|
||||
attachment_list.remove( pa );
|
||||
}else{
|
||||
String sv = etContent.getText().toString();
|
||||
sv = sv + pa.attachment.text_url+" ";
|
||||
etContent.setText(sv);
|
||||
}
|
||||
|
||||
showMediaAttachment();
|
||||
}
|
||||
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public String getDocumentName( Uri uri ){
|
||||
|
||||
Cursor cursor = getContentResolver().query( uri, null, null, null, null, null );
|
||||
try{
|
||||
if( cursor != null && cursor.moveToFirst() ){
|
||||
return cursor.getString( cursor.getColumnIndex( OpenableColumns.DISPLAY_NAME ) );
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
long getStreamSize( boolean bClose, InputStream is ) throws IOException{
|
||||
try{
|
||||
long size = 0L;
|
||||
for( ; ; ){
|
||||
long r = IOUtils.skip( is, 16384 );
|
||||
if( r <= 0 ) break;
|
||||
size += r;
|
||||
}
|
||||
return size;
|
||||
}finally{
|
||||
if( bClose ) IOUtils.closeQuietly( is );
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// visibility
|
||||
|
||||
String visibility = TootStatus.VISIBILITY_PUBLIC;
|
||||
|
||||
private void updateVisibility(){
|
||||
btnVisibility.setImageResource( Styler.getVisibilityIcon(visibility) );
|
||||
}
|
||||
|
||||
private void performVisibility(){
|
||||
final String[] caption_list = new String[]{
|
||||
getString( R.string.visibility_public ),
|
||||
getString( R.string.visibility_unlisted ),
|
||||
getString( R.string.visibility_private ),
|
||||
getString( R.string.visibility_direct ),
|
||||
};
|
||||
|
||||
// public static final String VISIBILITY_PUBLIC ="public";
|
||||
// public static final String VISIBILITY_UNLISTED ="unlisted";
|
||||
// public static final String VISIBILITY_PRIVATE ="private";
|
||||
// public static final String VISIBILITY_DIRECT ="direct";
|
||||
|
||||
new AlertDialog.Builder( this )
|
||||
.setTitle( R.string.choose_visibility )
|
||||
.setItems( caption_list, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( DialogInterface dialog, int which ){
|
||||
switch( which ){
|
||||
case 0:
|
||||
visibility = TootStatus.VISIBILITY_PUBLIC;
|
||||
break;
|
||||
case 1:
|
||||
visibility = TootStatus.VISIBILITY_UNLISTED;
|
||||
break;
|
||||
case 2:
|
||||
visibility = TootStatus.VISIBILITY_PRIVATE;
|
||||
break;
|
||||
case 3:
|
||||
visibility = TootStatus.VISIBILITY_DIRECT;
|
||||
break;
|
||||
}
|
||||
updateVisibility();
|
||||
}
|
||||
} )
|
||||
.setNegativeButton( R.string.cancel, null )
|
||||
.show();
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// post
|
||||
|
||||
private void performPost(){
|
||||
final String content = etContent.getText().toString().trim();
|
||||
if(TextUtils.isEmpty( content ) ){
|
||||
Utils.showToast( this,true,R.string.post_error_contents_empty );
|
||||
return;
|
||||
}
|
||||
final String spoiler_text;
|
||||
if( !cbContentWarning.isChecked() ){
|
||||
spoiler_text = null;
|
||||
}else{
|
||||
spoiler_text = etContentWarning.getText().toString().trim();
|
||||
if( TextUtils.isEmpty( spoiler_text ) ){
|
||||
Utils.showToast( this, true, R.string.post_error_contents_warning_empty );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final StringBuilder sb = new StringBuilder( );
|
||||
|
||||
sb.append("status=");
|
||||
sb.append(Uri.encode( content ));
|
||||
|
||||
sb.append("&visibility=");
|
||||
sb.append(Uri.encode( visibility ));
|
||||
|
||||
if( cbNSFW.isChecked() ){
|
||||
sb.append("&sensitive=1");
|
||||
}
|
||||
if( spoiler_text != null ){
|
||||
sb.append("&spoiler_text=");
|
||||
sb.append(Uri.encode( spoiler_text ));
|
||||
}
|
||||
for(PostAttachment pa : attachment_list){
|
||||
if( pa.attachment != null ){
|
||||
sb.append("&media_ids[]="+pa.attachment.id);
|
||||
}
|
||||
}
|
||||
// TODO: in_reply_to_id (optional): local ID of the status you want to reply to
|
||||
|
||||
|
||||
final ProgressDialog progress = new ProgressDialog( this );
|
||||
|
||||
final AsyncTask< Void, Void, TootApiResult > task = new AsyncTask< Void, Void, TootApiResult >() {
|
||||
final SavedAccount target_account = account;
|
||||
|
||||
TootStatus status;
|
||||
|
||||
@Override
|
||||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient client = new TootApiClient( ActPost.this, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishApiProgress( final String s ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
progress.setMessage( s );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
client.setAccount( target_account );
|
||||
|
||||
Request.Builder request_builder = new Request.Builder()
|
||||
.post( RequestBody.create(
|
||||
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
|
||||
,sb.toString()
|
||||
));
|
||||
|
||||
TootApiResult result = client.request( "/api/v1/statuses", request_builder );
|
||||
if( result.object != null ){
|
||||
status = TootStatus.parse( log,result.object );
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(){
|
||||
onPostExecute( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute( TootApiResult result ){
|
||||
progress.dismiss();
|
||||
|
||||
if( status != null ){
|
||||
ActMain.update_at_resume = true;
|
||||
ActPost.this.finish();
|
||||
}else{
|
||||
if( result != null ){
|
||||
Utils.showToast( ActPost.this, true, result.error );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
progress.setIndeterminate( true );
|
||||
progress.setCancelable( true );
|
||||
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel( DialogInterface dialog ){
|
||||
task.cancel( true );
|
||||
}
|
||||
} );
|
||||
progress.show();
|
||||
AsyncTaskCompat.executeParallel( task );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Context;
|
|||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.widget.ImageView;
|
||||
|
||||
|
@ -19,12 +20,30 @@ import jp.juggler.subwaytooter.table.ContentWarning;
|
|||
import jp.juggler.subwaytooter.table.LogData;
|
||||
import jp.juggler.subwaytooter.table.MediaShown;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import okhttp3.OkHttpClient;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
|
||||
import uk.co.chrisjenx.calligraphy.TypefaceUtils;
|
||||
|
||||
public class App1 extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate(){
|
||||
super.onCreate();
|
||||
|
||||
CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
|
||||
.setDefaultFontPath("NotoSansCJKjp-Regular.otf")
|
||||
.setFontAttrId(R.attr.fontPath)
|
||||
.build()
|
||||
);
|
||||
|
||||
if( typeface_emoji == null ){
|
||||
typeface_emoji = TypefaceUtils.load(getAssets(), "emojione_android.ttf");
|
||||
}
|
||||
|
||||
if( typeface_normal == null ){
|
||||
typeface_normal = TypefaceUtils.load(getAssets(), "NotoSansCJKjp-Regular.otf");
|
||||
}
|
||||
|
||||
if( db_open_helper == null ){
|
||||
db_open_helper = new DBOpenHelper( getApplicationContext() );
|
||||
db_open_helper.onCreate( getDB() );
|
||||
|
@ -134,4 +153,8 @@ public class App1 extends Application {
|
|||
|
||||
}
|
||||
|
||||
public static final OkHttpClient ok_http_client = new OkHttpClient();
|
||||
|
||||
public static Typeface typeface_emoji ;
|
||||
public static Typeface typeface_normal ;
|
||||
}
|
||||
|
|
|
@ -101,6 +101,27 @@ public class Column {
|
|||
}
|
||||
}
|
||||
|
||||
public interface StatusEntryCallback{
|
||||
void onIterate(TootStatus status);
|
||||
}
|
||||
|
||||
public void findStatus( SavedAccount target_account,long target_status_id ,StatusEntryCallback callback){
|
||||
if( target_account.user.equals( access_info.user ) ){
|
||||
for( int i = 0, ie = status_list.size() ; i < ie ; ++ i ){
|
||||
TootStatus status = status_list.get( i );
|
||||
if( target_status_id == status.id ){
|
||||
callback.onIterate( status );
|
||||
}
|
||||
TootStatus reblog = status.reblog;
|
||||
if( reblog!= null ){
|
||||
if( target_status_id == reblog.id ){
|
||||
callback.onIterate( status );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface VisualCallback {
|
||||
void onVisualColumn();
|
||||
}
|
||||
|
@ -126,7 +147,7 @@ public class Column {
|
|||
}
|
||||
}
|
||||
|
||||
private void fireVisualCallback(){
|
||||
public void fireVisualCallback(){
|
||||
Iterator< VisualCallback > it = visual_callback.iterator();
|
||||
while( it.hasNext() ){
|
||||
it.next().onVisualColumn();
|
||||
|
@ -160,9 +181,6 @@ public class Column {
|
|||
cancelLastTask();
|
||||
|
||||
AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() {
|
||||
boolean __isCancelled(){
|
||||
return isCancelled();
|
||||
}
|
||||
|
||||
TootStatus.List tmp_list_status;
|
||||
TootReport.List tmp_list_report;
|
||||
|
@ -200,12 +218,12 @@ public class Column {
|
|||
protected TootApiResult doInBackground( Void... params ){
|
||||
TootApiClient client = new TootApiClient( activity, new TootApiClient.Callback() {
|
||||
@Override
|
||||
public boolean isCancelled(){
|
||||
return __isCancelled() || is_dispose.get();
|
||||
public boolean isApiCancelled(){
|
||||
return isCancelled() || is_dispose.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishProgress( final String s ){
|
||||
public void publishApiProgress( final String s ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override
|
||||
public void run(){
|
||||
|
@ -222,30 +240,30 @@ public class Column {
|
|||
switch( type ){
|
||||
default:
|
||||
case TYPE_TL_HOME:
|
||||
return parseStatuses( client.get( "/api/v1/timelines/home" ) );
|
||||
return parseStatuses( client.request( "/api/v1/timelines/home" ) );
|
||||
|
||||
case TYPE_TL_LOCAL:
|
||||
return parseStatuses( client.get( "/api/v1/timelines/public?local=1" ) );
|
||||
return parseStatuses( client.request( "/api/v1/timelines/public?local=1" ) );
|
||||
|
||||
case TYPE_TL_FEDERATE:
|
||||
return parseStatuses( client.get( "/api/v1/timelines/public" ) );
|
||||
return parseStatuses( client.request( "/api/v1/timelines/public" ) );
|
||||
|
||||
case TYPE_TL_STATUSES:
|
||||
if( who_account == null ){
|
||||
parseAccount( client.get( "/api/v1/accounts/" + who_id ) );
|
||||
client.callback.publishProgress( "" );
|
||||
parseAccount( client.request( "/api/v1/accounts/" + who_id ) );
|
||||
client.callback.publishApiProgress( "" );
|
||||
}
|
||||
|
||||
return parseStatuses( client.get( "/api/v1/accounts/" + who_id + "/statuses" ) );
|
||||
return parseStatuses( client.request( "/api/v1/accounts/" + who_id + "/statuses" ) );
|
||||
|
||||
case TYPE_TL_FAVOURITES:
|
||||
return parseStatuses( client.get( "/api/v1/favourites" ) );
|
||||
return parseStatuses( client.request( "/api/v1/favourites" ) );
|
||||
|
||||
case TYPE_TL_REPORTS:
|
||||
return parseReports( client.get( "/api/v1/reports" ) );
|
||||
return parseReports( client.request( "/api/v1/reports" ) );
|
||||
|
||||
case TYPE_TL_NOTIFICATIONS:
|
||||
return parseNotifications( client.get( "/api/v1/notifications" ) );
|
||||
return parseNotifications( client.request( "/api/v1/notifications" ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,17 @@ public class ColumnPagerAdapter extends PagerAdapter{
|
|||
final SparseArray<ColumnViewHolder> holder_list = new SparseArray<>();
|
||||
|
||||
int addColumn( ViewPager pager, Column column ){
|
||||
return addColumn( pager,column,pager.getCurrentItem()+1 );
|
||||
}
|
||||
|
||||
int addColumn( ViewPager pager, Column column,int index ){
|
||||
int size = column_list.size();
|
||||
column_list.add( column );
|
||||
if( index > size ) index = size;
|
||||
pager.setAdapter( null );
|
||||
column_list.add( index,column );
|
||||
pager.setAdapter( this );
|
||||
notifyDataSetChanged();
|
||||
return size;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void removeColumn( ViewPager pager,Column column ){
|
||||
|
@ -40,7 +47,7 @@ public class ColumnPagerAdapter extends PagerAdapter{
|
|||
pager.setAdapter( null );
|
||||
column_list.remove( idx_column );
|
||||
pager.setAdapter( this );
|
||||
pager.setCurrentItem( idx_showing >= column_list.size() ? idx_showing -1 : idx_showing );
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import jp.juggler.subwaytooter.api.entity.TootStatus;
|
|||
import jp.juggler.subwaytooter.table.ContentWarning;
|
||||
import jp.juggler.subwaytooter.table.MediaShown;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
|
@ -90,7 +91,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnColumnClose:
|
||||
activity.performColumnClose( column );
|
||||
activity.performColumnClose( false, column );
|
||||
break;
|
||||
case R.id.btnColumnReload:
|
||||
column.reload();
|
||||
|
@ -99,10 +100,10 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
|
||||
}
|
||||
|
||||
void showError(String message){
|
||||
void showError( String message ){
|
||||
tvLoading.setVisibility( View.VISIBLE );
|
||||
listView.setVisibility( View.GONE );
|
||||
tvLoading.setText( message);
|
||||
tvLoading.setText( message );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,18 +113,18 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
tvColumnName.setText( column.getColumnName() );
|
||||
|
||||
if( column.is_dispose.get() ){
|
||||
showError("column was disposed.");
|
||||
showError( "column was disposed." );
|
||||
return;
|
||||
}
|
||||
|
||||
if( vh_header != null ){
|
||||
vh_header.bind( activity, column.access_info, column.who_account );
|
||||
}
|
||||
|
||||
|
||||
if( column.is_loading ){
|
||||
String message = column.task_progress;
|
||||
if( message == null ) message = "loading?";
|
||||
showError( message);
|
||||
showError( message );
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -135,7 +136,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
case Column.TYPE_TL_FAVOURITES:
|
||||
case Column.TYPE_TL_STATUSES:
|
||||
if( column.status_list.isEmpty() && vh_header == null ){
|
||||
showError(activity.getString(R.string.list_empty));
|
||||
showError( activity.getString( R.string.list_empty ) );
|
||||
}else{
|
||||
tvLoading.setVisibility( View.GONE );
|
||||
listView.setVisibility( View.VISIBLE );
|
||||
|
@ -144,7 +145,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
break;
|
||||
case Column.TYPE_TL_REPORTS:
|
||||
if( column.report_list.isEmpty() ){
|
||||
showError(activity.getString(R.string.list_empty));
|
||||
showError( activity.getString( R.string.list_empty ) );
|
||||
}else{
|
||||
tvLoading.setVisibility( View.GONE );
|
||||
listView.setVisibility( View.VISIBLE );
|
||||
|
@ -153,7 +154,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
break;
|
||||
case Column.TYPE_TL_NOTIFICATIONS:
|
||||
if( column.notification_list.isEmpty() ){
|
||||
showError(activity.getString(R.string.list_empty));
|
||||
showError( activity.getString( R.string.list_empty ) );
|
||||
}else{
|
||||
tvLoading.setVisibility( View.GONE );
|
||||
listView.setVisibility( View.VISIBLE );
|
||||
|
@ -231,6 +232,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
final NetworkImageView ivFollow;
|
||||
final TextView tvFollowerName;
|
||||
final TextView tvFollowerAcct;
|
||||
final ImageButton btnFollow;
|
||||
|
||||
final View llStatus;
|
||||
final NetworkImageView ivThumbnail;
|
||||
|
@ -240,6 +242,10 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
final View llContentWarning;
|
||||
final TextView tvContentWarning;
|
||||
final Button btnContentWarning;
|
||||
|
||||
final View llContents;
|
||||
final TextView tvTags;
|
||||
final TextView tvMentions;
|
||||
final TextView tvContent;
|
||||
|
||||
final View flMedia;
|
||||
|
@ -256,8 +262,12 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
final Button btnFavourite;
|
||||
final ImageButton btnMore;
|
||||
|
||||
|
||||
TootStatus status;
|
||||
SavedAccount account;
|
||||
TootAccount account_thumbnail;
|
||||
TootAccount account_boost;
|
||||
TootAccount account_follow;
|
||||
|
||||
public StatusViewHolder( View view ){
|
||||
this.llBoosted = view.findViewById( R.id.llBoosted );
|
||||
|
@ -269,6 +279,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
this.ivFollow = (NetworkImageView) view.findViewById( R.id.ivFollow );
|
||||
this.tvFollowerName = (TextView) view.findViewById( R.id.tvFollowerName );
|
||||
this.tvFollowerAcct = (TextView) view.findViewById( R.id.tvFollowerAcct );
|
||||
this.btnFollow = (ImageButton)view.findViewById( R.id.btnFollow );
|
||||
|
||||
this.llStatus = view.findViewById( R.id.llStatus );
|
||||
|
||||
|
@ -279,7 +290,12 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
this.llContentWarning = view.findViewById( R.id.llContentWarning );
|
||||
this.tvContentWarning = (TextView) view.findViewById( R.id.tvContentWarning );
|
||||
this.btnContentWarning = (Button) view.findViewById( R.id.btnContentWarning );
|
||||
|
||||
this.llContents = view.findViewById( R.id.llContents );
|
||||
this.tvContent = (TextView) view.findViewById( R.id.tvContent );
|
||||
this.tvTags = (TextView) view.findViewById( R.id.tvTags );
|
||||
this.tvMentions = (TextView) view.findViewById( R.id.tvMentions );
|
||||
|
||||
this.btnReply = (ImageButton) view.findViewById( R.id.btnReply );
|
||||
this.btnBoost = (Button) view.findViewById( R.id.btnBoost );
|
||||
this.btnFavourite = (Button) view.findViewById( R.id.btnFavourite );
|
||||
|
@ -301,11 +317,24 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
ivMedia3.setOnClickListener( this );
|
||||
ivMedia4.setOnClickListener( this );
|
||||
|
||||
btnReply.setOnClickListener( this );
|
||||
btnBoost.setOnClickListener( this );
|
||||
btnFavourite.setOnClickListener( this );
|
||||
btnMore.setOnClickListener( this );
|
||||
|
||||
ivThumbnail.setOnClickListener( this );
|
||||
tvName.setOnClickListener( this );
|
||||
llBoosted.setOnClickListener( this );
|
||||
llFollow.setOnClickListener( this );
|
||||
btnFollow.setOnClickListener( this );
|
||||
|
||||
tvContent.setMovementMethod( LinkMovementMethod.getInstance() );
|
||||
tvTags.setMovementMethod( LinkMovementMethod.getInstance() );
|
||||
tvMentions.setMovementMethod( LinkMovementMethod.getInstance() );
|
||||
}
|
||||
|
||||
public void bind( ActMain activity, View view, Object item, SavedAccount account ){
|
||||
this.account = account;
|
||||
public void bind( ActMain activity, View view, Object item, SavedAccount access_info ){
|
||||
this.account = access_info;
|
||||
|
||||
llBoosted.setVisibility( View.GONE );
|
||||
llFollow.setVisibility( View.GONE );
|
||||
|
@ -316,36 +345,50 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
if( item instanceof TootNotification ){
|
||||
TootNotification n = (TootNotification) item;
|
||||
if( TootNotification.TYPE_FAVOURITE.equals( n.type ) ){
|
||||
account_boost = n.account;
|
||||
|
||||
llBoosted.setVisibility( View.VISIBLE );
|
||||
ivBoosted.setImageResource( R.drawable.btn_favourite );
|
||||
tvBoostedTime.setText( TootStatus.formatTime( n.time_created_at )
|
||||
+ "\n" + account.getFullAcct( n.account )
|
||||
+ "\n" + access_info.getFullAcct(account_boost )
|
||||
);
|
||||
tvBoosted.setText( activity.getString( R.string.favourited_by, n.account.display_name ) );
|
||||
|
||||
if( n.status != null ) bindSub( activity, view, n.status, account );
|
||||
tvBoosted.setText( Utils.formatSpannable1( activity,R.string.display_name_favourited_by, account_boost.display_name));
|
||||
|
||||
if( n.status != null ) bindSub( activity, view, n.status, access_info );
|
||||
}else if( TootNotification.TYPE_REBLOG.equals( n.type ) ){
|
||||
account_boost = n.account;
|
||||
llBoosted.setVisibility( View.VISIBLE );
|
||||
ivBoosted.setImageResource( R.drawable.btn_boost );
|
||||
tvBoostedTime.setText( TootStatus.formatTime( n.time_created_at )
|
||||
+ "\n" + account.getFullAcct( n.account )
|
||||
+ "\n" + access_info.getFullAcct(account_boost )
|
||||
);
|
||||
tvBoosted.setText( activity.getString( R.string.boosted_by, n.account.display_name ) );
|
||||
if( n.status != null ) bindSub( activity, view, n.status, account );
|
||||
tvBoosted.setText( Utils.formatSpannable1( activity, R.string.display_name_boosted_by, account_boost.display_name ) );
|
||||
account_boost = n.account;
|
||||
if( n.status != null ) bindSub( activity, view, n.status, access_info );
|
||||
}else if( TootNotification.TYPE_FOLLOW.equals( n.type ) ){
|
||||
account_boost = n.account;
|
||||
llBoosted.setVisibility( View.VISIBLE );
|
||||
ivBoosted.setImageResource( R.drawable.btn_boost );
|
||||
tvBoostedTime.setText( TootStatus.formatTime( n.time_created_at )
|
||||
+ "\n" + account.getFullAcct( n.account )
|
||||
+ "\n" + access_info.getFullAcct( account_boost )
|
||||
);
|
||||
tvBoosted.setText( activity.getString( R.string.boosted_by, n.account.display_name ) );
|
||||
tvBoosted.setText( Utils.formatSpannable1( activity, R.string.display_name_followed_by, account_boost.display_name ) );
|
||||
//
|
||||
account_follow = n.account;
|
||||
llFollow.setVisibility( View.VISIBLE );
|
||||
ivFollow.setImageUrl( n.account.avatar_static, App1.getImageLoader() );
|
||||
tvFollowerName.setText( n.account.display_name );
|
||||
tvFollowerAcct.setText( account.getFullAcct( n.account ) );
|
||||
ivFollow.setImageUrl( account_follow.avatar_static, App1.getImageLoader() );
|
||||
tvFollowerName.setText(account_follow.display_name );
|
||||
tvFollowerAcct.setText( access_info.getFullAcct( account_follow ) );
|
||||
}else if( TootNotification.TYPE_MENTION.equals( n.type ) ){
|
||||
if( n.status != null ) bindSub( activity, view, n.status, account );
|
||||
account_boost = n.account;
|
||||
llBoosted.setVisibility( View.VISIBLE );
|
||||
ivBoosted.setImageResource( R.drawable.btn_reply );
|
||||
tvBoostedTime.setText( TootStatus.formatTime( n.time_created_at )
|
||||
+ "\n" + access_info.getFullAcct(account_boost )
|
||||
);
|
||||
tvBoosted.setText( Utils.formatSpannable1( activity, R.string.display_name_replied_by, account_boost.display_name ) );
|
||||
|
||||
if( n.status != null ) bindSub( activity, view, n.status, access_info );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -353,22 +396,23 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
if( item instanceof TootStatus ){
|
||||
TootStatus status = (TootStatus) item;
|
||||
if( status.reblog != null ){
|
||||
account_boost = status.account;
|
||||
llBoosted.setVisibility( View.VISIBLE );
|
||||
ivBoosted.setImageResource( R.drawable.btn_boost );
|
||||
tvBoostedTime.setText( TootStatus.formatTime( status.time_created_at )
|
||||
+ "\n" + account.getFullAcct( status.account )
|
||||
+ "\n" + access_info.getFullAcct( account_boost )
|
||||
);
|
||||
tvBoosted.setText( activity.getString( R.string.boosted_by, status.account.display_name ) );
|
||||
bindSub( activity, view, status.reblog, account );
|
||||
tvBoosted.setText( Utils.formatSpannable1( activity, R.string.display_name_boosted_by, status.account.display_name ) );
|
||||
bindSub( activity, view, status.reblog, access_info );
|
||||
}else{
|
||||
bindSub( activity, view, status, account );
|
||||
bindSub( activity, view, status, access_info );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSub( ActMain activity, View view, TootStatus status, SavedAccount account ){
|
||||
this.status = status;
|
||||
|
||||
account_thumbnail = status.account;
|
||||
llStatus.setVisibility( View.VISIBLE );
|
||||
tvTime.setText( TootStatus.formatTime( status.time_created_at )
|
||||
+ "\n" + account.getFullAcct( status.account )
|
||||
|
@ -377,14 +421,29 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
ivThumbnail.setImageUrl( status.account.avatar_static, App1.getImageLoader() );
|
||||
tvContent.setText( status.decoded_content );
|
||||
|
||||
if( status.decoded_tags == null ){
|
||||
tvTags.setVisibility( View.GONE );
|
||||
}else{
|
||||
tvTags.setVisibility( View.VISIBLE );
|
||||
tvTags.setText( status.decoded_tags);
|
||||
}
|
||||
|
||||
if( status.decoded_mentions == null ){
|
||||
tvMentions.setVisibility( View.GONE );
|
||||
}else{
|
||||
tvMentions.setVisibility( View.VISIBLE );
|
||||
tvMentions.setText( status.decoded_mentions);
|
||||
}
|
||||
|
||||
// Content warning
|
||||
if( TextUtils.isEmpty( status.spoiler_text ) ){
|
||||
llContentWarning.setVisibility( View.GONE );
|
||||
tvContent.setVisibility( View.VISIBLE );
|
||||
llContents.setVisibility( View.VISIBLE );
|
||||
}else{
|
||||
llContentWarning.setVisibility( View.VISIBLE );
|
||||
tvContentWarning.setText( status.spoiler_text );
|
||||
showContent( ContentWarning.isShown( account.host, status.id, false ));
|
||||
boolean cw_shown = ContentWarning.isShown( account.host, status.id, false );
|
||||
showContent( cw_shown );
|
||||
}
|
||||
|
||||
if( status.media_attachments == null || status.media_attachments.isEmpty() ){
|
||||
|
@ -397,32 +456,52 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
setMedia( ivMedia4, status, 3 );
|
||||
|
||||
// hide sensitive media
|
||||
boolean is_shown = MediaShown.isShown( account.host,status.id, ! status.sensitive );
|
||||
boolean is_shown = MediaShown.isShown( account.host, status.id, account.dont_hide_nsfw || ! status.sensitive );
|
||||
btnShowMedia.setVisibility( ! is_shown ? View.VISIBLE : View.GONE );
|
||||
}
|
||||
|
||||
Drawable d;
|
||||
int color;
|
||||
|
||||
color = ( status.reblogged ? 0xff0088ff : 0xff000000 );
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_boost ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnBoost.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnBoost.setText( Long.toString( status.reblogs_count ) );
|
||||
btnBoost.setTextColor( color );
|
||||
if( activity.isBusyBoost( account,status )){
|
||||
color = 0xff000000;
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_boost ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnBoost.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnBoost.setText( "?" );
|
||||
btnBoost.setTextColor( color );
|
||||
|
||||
color = ( status.favourited ? 0xff0088ff : 0xff000000 );
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_favourite ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnFavourite.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnFavourite.setText( Long.toString( status.favourites_count ) );
|
||||
btnFavourite.setTextColor( color );
|
||||
}else{
|
||||
color = ( status.reblogged ? 0xff0088ff : 0xff000000 );
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_boost ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnBoost.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnBoost.setText( Long.toString( status.reblogs_count ) );
|
||||
btnBoost.setTextColor( color );
|
||||
|
||||
}
|
||||
|
||||
if( activity.isBusyFav( account,status )){
|
||||
color = 0xff000000;
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_refresh ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnFavourite.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnFavourite.setText( "?" );
|
||||
btnFavourite.setTextColor( color );
|
||||
}else{
|
||||
color = ( status.favourited ? 0xff0088ff : 0xff000000 );
|
||||
d = ContextCompat.getDrawable( activity, R.drawable.btn_favourite ).mutate();
|
||||
d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP );
|
||||
btnFavourite.setCompoundDrawablesRelativeWithIntrinsicBounds( d, null, null, null );
|
||||
btnFavourite.setText( Long.toString( status.favourites_count ) );
|
||||
btnFavourite.setTextColor( color );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void showContent( boolean shown ){
|
||||
btnContentWarning.setText( shown ? R.string.hide : R.string.show );
|
||||
tvContent.setVisibility( shown ? View.VISIBLE : View.GONE );
|
||||
llContents.setVisibility( shown ? View.VISIBLE : View.GONE );
|
||||
}
|
||||
|
||||
private void setMedia( NetworkImageView iv, TootStatus status, int idx ){
|
||||
|
@ -441,11 +520,11 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
public void onClick( View v ){
|
||||
switch( v.getId() ){
|
||||
case R.id.btnHideMedia:
|
||||
MediaShown.save( account.host,status.id, false );
|
||||
MediaShown.save( account.host, status.id, false );
|
||||
btnShowMedia.setVisibility( View.VISIBLE );
|
||||
break;
|
||||
case R.id.btnShowMedia:
|
||||
MediaShown.save( account.host,status.id, true );
|
||||
MediaShown.save( account.host, status.id, true );
|
||||
btnShowMedia.setVisibility( View.GONE );
|
||||
break;
|
||||
case R.id.ivMedia1:
|
||||
|
@ -460,25 +539,55 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
case R.id.ivMedia4:
|
||||
clickMedia( 3 );
|
||||
break;
|
||||
case R.id.btnContentWarning:
|
||||
{
|
||||
boolean new_shown = (tvContent.getVisibility()==View.GONE);
|
||||
ContentWarning.save( account.host,status.id , new_shown);
|
||||
showContent( new_shown );
|
||||
break;
|
||||
}
|
||||
case R.id.btnContentWarning:{
|
||||
boolean new_shown = ( llContents.getVisibility() == View.GONE );
|
||||
ContentWarning.save( account.host, status.id, new_shown );
|
||||
showContent( new_shown );
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.btnReply:
|
||||
activity.performReply( account,status);
|
||||
break;
|
||||
case R.id.btnBoost:
|
||||
activity.performBoost( account,status.reblog != null ? status.reblog : status ,false);
|
||||
break;
|
||||
case R.id.btnFavourite:
|
||||
activity.performFavourite( account,status.reblog != null ? status.reblog : status);
|
||||
break;
|
||||
case R.id.btnMore:
|
||||
activity.performMore( account,status);
|
||||
break;
|
||||
case R.id.ivThumbnail:
|
||||
case R.id.tvName:
|
||||
activity.performOpenUser(account,account_thumbnail);
|
||||
break;
|
||||
case R.id.llBoosted:
|
||||
activity.performOpenUser(account,account_boost);
|
||||
break;
|
||||
case R.id.llFollow:
|
||||
activity.performOpenUser(account,account_follow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void clickMedia( int i ){
|
||||
try{
|
||||
activity.openChromeTab( status.media_attachments.get( i ).remote_url );
|
||||
TootAttachment a = status.media_attachments.get( i );
|
||||
|
||||
String sv = a.remote_url;
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
sv = a.url;
|
||||
}
|
||||
activity.openChromeTab( sv );
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class HeaderViewHolder implements View.OnClickListener {
|
||||
final View viewRoot;
|
||||
final NetworkImageView ivBackground;
|
||||
|
@ -509,6 +618,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
|
|||
btnFollowers.setOnClickListener( this );
|
||||
btnStatusCount.setOnClickListener( this );
|
||||
|
||||
tvNote.setMovementMethod( LinkMovementMethod.getInstance() );
|
||||
}
|
||||
|
||||
public void bind( ActMain activity, SavedAccount access_info, TootAccount who ){
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus;
|
||||
|
||||
/**
|
||||
* Created by tateisu on 2017/04/22.
|
||||
*/
|
||||
|
||||
public class Styler {
|
||||
public static int getVisibilityIcon( String visibility ){
|
||||
return
|
||||
TootStatus.VISIBILITY_PUBLIC.equals( visibility ) ? R.drawable.ic_public
|
||||
: TootStatus.VISIBILITY_UNLISTED.equals( visibility ) ? R.drawable.ic_lock_open
|
||||
: TootStatus.VISIBILITY_PRIVATE.equals( visibility ) ? R.drawable.ic_lock
|
||||
: TootStatus.VISIBILITY_DIRECT.equals( visibility ) ? R.drawable.ic_mail
|
||||
: R.drawable.ic_public;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String getVisibilityString( Context context ,String visibility){
|
||||
return
|
||||
TootStatus.VISIBILITY_PUBLIC.equals( visibility ) ? context.getString( R.string.visibility_public )
|
||||
: TootStatus.VISIBILITY_UNLISTED.equals( visibility ) ? context.getString( R.string.visibility_unlisted )
|
||||
: TootStatus.VISIBILITY_PRIVATE.equals( visibility ) ? context.getString( R.string.visibility_private )
|
||||
: TootStatus.VISIBILITY_DIRECT.equals( visibility ) ? context.getString( R.string.visibility_direct )
|
||||
: "?";
|
||||
}
|
||||
}
|
|
@ -9,23 +9,32 @@ import org.json.JSONArray;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.table.SavedAccount;
|
||||
import jp.juggler.subwaytooter.util.CancelChecker;
|
||||
import jp.juggler.subwaytooter.util.HTTPClient;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import jp.juggler.subwaytooter.table.ClientInfo;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class TootApiClient {
|
||||
private static final LogCategory log = new LogCategory( "TootApiClient" );
|
||||
|
||||
static final OkHttpClient ok_http_client = App1.ok_http_client;
|
||||
|
||||
public interface Callback {
|
||||
boolean isCancelled();
|
||||
boolean isApiCancelled();
|
||||
|
||||
void publishProgress( String s );
|
||||
void publishApiProgress( String s );
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
|
@ -53,52 +62,65 @@ public class TootApiClient {
|
|||
this.account = account;
|
||||
}
|
||||
|
||||
public TootApiResult get( String path ){
|
||||
|
||||
final HTTPClient client = new HTTPClient( 60000, 10, "account", new CancelChecker() {
|
||||
@Override
|
||||
public boolean isCancelled(){
|
||||
return callback.isCancelled();
|
||||
}
|
||||
} );
|
||||
public static final MediaType MEDIA_TYPE_FORM_URL_ENCODED = MediaType.parse( "application/x-www-form-urlencoded" );
|
||||
|
||||
public TootApiResult request( String path ){
|
||||
return request(path,new Request.Builder() );
|
||||
}
|
||||
|
||||
public TootApiResult request( String path, Request.Builder request_builder ){
|
||||
|
||||
JSONObject client_info = null;
|
||||
JSONObject token_info = ( account == null ? null : account.token_info );
|
||||
|
||||
for( ; ; ){
|
||||
if( callback.isCancelled() ) return null;
|
||||
if( callback.isApiCancelled() ) return null;
|
||||
if( token_info == null ){
|
||||
if( client_info == null ){
|
||||
// DBにあるならそれを使う
|
||||
client_info = ClientInfo.load( instance );
|
||||
if( client_info != null ) continue;
|
||||
|
||||
callback.publishProgress( context.getString( R.string.register_app_to_server, instance ) );
|
||||
callback.publishApiProgress( context.getString( R.string.register_app_to_server, instance ) );
|
||||
|
||||
// OAuth2 クライアント登録
|
||||
String client_name = "jp.juggler.subwaytooter." + UUID.randomUUID().toString();
|
||||
client.post_content = Utils.encodeUTF8(
|
||||
"client_name=" + Uri.encode( client_name )
|
||||
+ "&redirect_uris=urn:ietf:wg:oauth:2.0:oob"
|
||||
+ "&scopes=read write follow"
|
||||
);
|
||||
byte[] data = client.getHTTP( log, "https://" + instance + "/api/v1/apps" );
|
||||
if( callback.isCancelled() ) return null;
|
||||
|
||||
if( data == null ){
|
||||
return new TootApiResult( context.getString( R.string.network_error, client.last_error ) );
|
||||
Request request = new Request.Builder()
|
||||
.url( "https://" + instance + "/api/v1/apps" )
|
||||
.post( RequestBody.create( MEDIA_TYPE_FORM_URL_ENCODED
|
||||
, "client_name=" + Uri.encode( client_name )
|
||||
+ "&redirect_uris=urn:ietf:wg:oauth:2.0:oob"
|
||||
+ "&scopes=read write follow"
|
||||
) )
|
||||
.build();
|
||||
Call call = ok_http_client.newCall( request );
|
||||
|
||||
Response response;
|
||||
try{
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
|
||||
}
|
||||
if( callback.isApiCancelled() ) return null;
|
||||
|
||||
if( ! response.isSuccessful() ){
|
||||
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
|
||||
}
|
||||
try{
|
||||
String result = Utils.decodeUTF8( data );
|
||||
String json = response.body().string();
|
||||
if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
|
||||
return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
|
||||
}
|
||||
// {"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"******","client_secret":"******"}
|
||||
client_info = new JSONObject( result );
|
||||
client_info = new JSONObject( json );
|
||||
String error = Utils.optStringX( client_info, "error" );
|
||||
if( ! TextUtils.isEmpty( error ) ){
|
||||
return new TootApiResult( context.getString( R.string.api_error, error ) );
|
||||
}
|
||||
ClientInfo.save( instance, result );
|
||||
ClientInfo.save( instance, json );
|
||||
continue;
|
||||
}catch( JSONException ex ){
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
|
||||
}
|
||||
|
@ -109,30 +131,46 @@ public class TootApiClient {
|
|||
return new TootApiResult( context.getString( R.string.login_required ) );
|
||||
}
|
||||
|
||||
callback.publishProgress( context.getString( R.string.request_access_token ) );
|
||||
callback.publishApiProgress( context.getString( R.string.request_access_token ) );
|
||||
|
||||
// アクセストークンの取得
|
||||
//
|
||||
client.post_content = Utils.encodeUTF8(
|
||||
"client_id=" + Uri.encode( Utils.optStringX( client_info, "client_id" ) )
|
||||
+ "&client_secret=" + Uri.encode( Utils.optStringX( client_info, "client_secret" ) )
|
||||
+ "&grant_type=password"
|
||||
+ "&username=" + Uri.encode( user_mail )
|
||||
+ "&password=" + Uri.encode( password )
|
||||
);
|
||||
byte[] data = client.getHTTP( log, "https://" + instance + "/oauth/token" );
|
||||
if( callback.isCancelled() ) return null;
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url( "https://" + instance + "/oauth/token" )
|
||||
.post( RequestBody.create(
|
||||
MEDIA_TYPE_FORM_URL_ENCODED
|
||||
,"client_id=" + Uri.encode( Utils.optStringX( client_info, "client_id" ) )
|
||||
+ "&client_secret=" + Uri.encode( Utils.optStringX( client_info, "client_secret" ) )
|
||||
+ "&grant_type=password"
|
||||
+ "&username=" + Uri.encode( user_mail )
|
||||
+ "&password=" + Uri.encode( password )
|
||||
+ "&scope=read write follow"
|
||||
+ "&scopes=read write follow"
|
||||
))
|
||||
.build();
|
||||
Call call = ok_http_client.newCall( request );
|
||||
|
||||
Response response;
|
||||
try{
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
|
||||
}
|
||||
if( callback.isApiCancelled() ) return null;
|
||||
|
||||
// TODO: アプリIDが無効な場合はどんなエラーが出る?
|
||||
|
||||
if( data == null ){
|
||||
return new TootApiResult( context.getString( R.string.network_error, client.last_error ) );
|
||||
if( ! response.isSuccessful() ){
|
||||
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
|
||||
}
|
||||
|
||||
try{
|
||||
String result = Utils.decodeUTF8( data );
|
||||
String json = response.body().string();
|
||||
|
||||
// {"access_token":"******","token_type":"bearer","scope":"read","created_at":1492334641}
|
||||
token_info = new JSONObject( result );
|
||||
if( TextUtils.isEmpty( json ) || json.charAt( 0 ) == '<' ){
|
||||
return new TootApiResult( context.getString( R.string.login_failed ) );
|
||||
}
|
||||
token_info = new JSONObject( json );
|
||||
String error = Utils.optStringX( client_info, "error" );
|
||||
if( ! TextUtils.isEmpty( error ) ){
|
||||
return new TootApiResult( context.getString( R.string.api_error, error ) );
|
||||
|
@ -141,51 +179,60 @@ public class TootApiClient {
|
|||
account.updateTokenInfo( token_info );
|
||||
}
|
||||
continue;
|
||||
}catch( JSONException ex ){
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
|
||||
}
|
||||
}
|
||||
|
||||
// アクセストークンを使ってAPIを呼び出す
|
||||
{
|
||||
callback.publishProgress( context.getString( R.string.request_api, path ) );
|
||||
|
||||
client.post_content = null;
|
||||
client.extra_header = new String[]{
|
||||
"Authorization", "Bearer " + Utils.optStringX( token_info, "access_token" )
|
||||
};
|
||||
byte[] data = client.getHTTP( log, "https://" + instance + path );
|
||||
if( callback.isCancelled() ) return null;
|
||||
|
||||
// TODO: アクセストークンが無効な場合はどうなる?
|
||||
// TODO: アプリIDが無効な場合はどうなる?
|
||||
|
||||
if( data == null ){
|
||||
return new TootApiResult( context.getString( R.string.network_error, client.last_error ) );
|
||||
}
|
||||
|
||||
try{
|
||||
String result = Utils.decodeUTF8( data );
|
||||
if( result.startsWith( "[" ) ){
|
||||
JSONArray array = new JSONArray( result );
|
||||
return new TootApiResult( token_info, result, array );
|
||||
}else{
|
||||
JSONObject json = new JSONObject( result );
|
||||
|
||||
String error = Utils.optStringX( json, "error" );
|
||||
if( ! TextUtils.isEmpty( error ) ){
|
||||
return new TootApiResult( context.getString( R.string.api_error, error ) );
|
||||
}
|
||||
return new TootApiResult( token_info, result, json );
|
||||
// アクセストークンを使ってAPIを呼び出す
|
||||
{
|
||||
callback.publishApiProgress( context.getString( R.string.request_api, path ) );
|
||||
|
||||
Request request = request_builder
|
||||
.url("https://" + instance + path)
|
||||
.header( "Authorization", "Bearer " + Utils.optStringX( token_info, "access_token" ) )
|
||||
.build();
|
||||
|
||||
Call call = ok_http_client.newCall( request );
|
||||
Response response;
|
||||
try{
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
|
||||
}
|
||||
|
||||
if( callback.isApiCancelled() ) return null;
|
||||
|
||||
// TODO: アクセストークンが無効な場合はどうなる?
|
||||
// TODO: アプリIDが無効な場合はどうなる?
|
||||
|
||||
if( ! response.isSuccessful() ){
|
||||
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
|
||||
}
|
||||
|
||||
try{
|
||||
String json = response.body().string();
|
||||
|
||||
if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
|
||||
return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
|
||||
}else if( json.startsWith( "[" ) ){
|
||||
JSONArray array = new JSONArray( json );
|
||||
return new TootApiResult( token_info, json, array );
|
||||
}else{
|
||||
JSONObject object = new JSONObject( json );
|
||||
|
||||
String error = Utils.optStringX( object, "error" );
|
||||
if( ! TextUtils.isEmpty( error ) ){
|
||||
return new TootApiResult( context.getString( R.string.api_error, error ) );
|
||||
}
|
||||
return new TootApiResult( token_info, json, object );
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
|
||||
}
|
||||
}catch( JSONException ex ){
|
||||
ex.printStackTrace();
|
||||
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import jp.juggler.subwaytooter.util.Emojione;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.util.HTMLDecoder;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
|
@ -24,7 +30,7 @@ public class TootAccount {
|
|||
public String acct;
|
||||
|
||||
// The account's display name
|
||||
public String display_name;
|
||||
public CharSequence display_name;
|
||||
|
||||
//Boolean for when the account cannot be followed without waiting for approval first
|
||||
public boolean locked;
|
||||
|
@ -44,7 +50,7 @@ public class TootAccount {
|
|||
|
||||
// Biography of user
|
||||
// 説明文。改行は\r\n。リンクなどはHTMLタグで書かれている
|
||||
public String note;
|
||||
public Spannable note;
|
||||
|
||||
//URL of the user's profile page (can be remote)
|
||||
// https://mastodon.juggler.jp/@tateisu
|
||||
|
@ -70,21 +76,30 @@ public class TootAccount {
|
|||
dst.id = src.optLong( "id" );
|
||||
dst.username = Utils.optStringX( src, "username" );
|
||||
dst.acct = Utils.optStringX( src, "acct" );
|
||||
dst.display_name = Utils.optStringX( src, "display_name" );
|
||||
|
||||
String sv = Utils.optStringX( src, "display_name" );
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
dst.display_name = dst.username;
|
||||
}else{
|
||||
dst.display_name = Emojione.decodeEmoji( HTMLDecoder.decodeEntity(sv ) );
|
||||
}
|
||||
|
||||
dst.locked = src.optBoolean( "locked" );
|
||||
dst.created_at = Utils.optStringX( src, "created_at" );
|
||||
dst.followers_count = src.optLong( "followers_count" );
|
||||
dst.following_count = src.optLong( "following_count" );
|
||||
dst.statuses_count = src.optLong( "statuses_count" );
|
||||
dst.note = Utils.optStringX( src, "note" );
|
||||
dst.note = HTMLDecoder.decodeHTML( Utils.optStringX( src, "note" ) );
|
||||
dst.url = Utils.optStringX( src, "url" );
|
||||
dst.avatar = Utils.optStringX( src, "avatar" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
|
||||
dst.avatar_static = Utils.optStringX( src, "avatar_static" ); // "https:\/\/mastodon.juggler.jp\/system\/accounts\/avatars\/000\/000\/148\/original\/0a468974fac5a448.PNG?1492081886",
|
||||
dst.header = Utils.optStringX( src, "header" ); // "https:\/\/mastodon.juggler.jp\/headers\/original\/missing.png"
|
||||
dst.header_static = Utils.optStringX( src, "header_static" ); // "https:\/\/mastodon.juggler.jp\/headers\/original\/missing.png"}
|
||||
|
||||
dst.time_created_at = TootStatus.parseTime( log,dst.created_at );
|
||||
dst.time_created_at = TootStatus.parseTime( log, dst.created_at );
|
||||
|
||||
return dst;
|
||||
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
log.e( ex, "TootAccount.parse failed." );
|
||||
|
@ -109,5 +124,4 @@ public class TootAccount {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -35,10 +35,13 @@ public class TootAttachment {
|
|||
// Shorter URL for the image, for insertion into text (only present on local images)
|
||||
public String text_url;
|
||||
|
||||
public JSONObject json;
|
||||
|
||||
public static TootAttachment parse( LogCategory log, JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
try{
|
||||
TootAttachment dst = new TootAttachment();
|
||||
dst.json = src;
|
||||
dst.id = src.optLong( "id" );
|
||||
dst.type = Utils.optStringX( src, "type" );
|
||||
dst.url = Utils.optStringX( src, "url" );
|
||||
|
|
|
@ -19,6 +19,7 @@ import jp.juggler.subwaytooter.util.Utils;
|
|||
|
||||
public class TootStatus {
|
||||
|
||||
|
||||
public static class List extends ArrayList< TootStatus > {
|
||||
|
||||
}
|
||||
|
@ -70,6 +71,10 @@ public class TootStatus {
|
|||
|
||||
//One of: public, unlisted, private, direct
|
||||
public String visibility;
|
||||
public static final String VISIBILITY_PUBLIC ="public";
|
||||
public static final String VISIBILITY_UNLISTED ="unlisted";
|
||||
public static final String VISIBILITY_PRIVATE ="private";
|
||||
public static final String VISIBILITY_DIRECT ="direct";
|
||||
|
||||
// An array of Attachments
|
||||
public TootAttachment.List media_attachments;
|
||||
|
@ -78,7 +83,7 @@ public class TootStatus {
|
|||
public TootMention.List mentions;
|
||||
|
||||
//An array of Tags
|
||||
public ArrayList<String> tags;
|
||||
public TootTag.List tags;
|
||||
|
||||
//Application from which the status was posted
|
||||
public String application;
|
||||
|
@ -86,6 +91,8 @@ public class TootStatus {
|
|||
public long time_created_at;
|
||||
|
||||
public Spannable decoded_content;
|
||||
public Spannable decoded_tags;
|
||||
public Spannable decoded_mentions;
|
||||
|
||||
public static TootStatus parse( LogCategory log, JSONObject src ){
|
||||
|
||||
|
@ -112,12 +119,14 @@ public class TootStatus {
|
|||
status.visibility = Utils.optStringX( src, "visibility" );
|
||||
status.media_attachments = TootAttachment.parseList( log, src.optJSONArray( "media_attachments" ) );
|
||||
status.mentions = TootMention.parseList( log, src.optJSONArray( "mentions" ));
|
||||
status.tags = Utils.parseStringArray( log, src.optJSONArray( "tags" ));
|
||||
status.tags = TootTag.parseList( log, src.optJSONArray( "tags" ));
|
||||
status.application = Utils.optStringX( src, "application" ); // null
|
||||
|
||||
status.time_created_at = parseTime( log, status.created_at );
|
||||
status.decoded_content = HTMLDecoder.decodeHTML(status.content);
|
||||
|
||||
status.decoded_tags = HTMLDecoder.decodeTags( status.tags);
|
||||
status.decoded_mentions = HTMLDecoder.decodeMentions( status.mentions);
|
||||
|
||||
return status;
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class TootTag {
|
||||
|
||||
// The hashtag, not including the preceding #
|
||||
public String name;
|
||||
|
||||
// The URL of the hashtag
|
||||
public String url;
|
||||
|
||||
|
||||
public static TootTag parse( LogCategory log, JSONObject src ){
|
||||
if( src == null ) return null;
|
||||
try{
|
||||
TootTag dst = new TootTag();
|
||||
dst.name = Utils.optStringX(src, "name" );
|
||||
dst.url = Utils.optStringX(src, "url" );
|
||||
return dst;
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
log.e(ex,"TootTag.parse failed.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class List extends ArrayList<TootTag>{
|
||||
|
||||
}
|
||||
|
||||
public static TootTag.List parseList( LogCategory log, JSONArray array ){
|
||||
TootTag.List result = new TootTag.List();
|
||||
if( array != null ){
|
||||
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
|
||||
JSONObject src = array.optJSONObject( i );
|
||||
if( src == null ) continue;
|
||||
TootTag item = parse( log, src );
|
||||
if( item != null ) result.add( 0, item );
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -34,7 +34,7 @@ public class AccountPicker {
|
|||
|
||||
for(int i=0,ie=account_list.size();i<ie;++i){
|
||||
SavedAccount ai = account_list.get(i);
|
||||
caption_list[i] = ai.acct + ( ai.login_required ? " "+ activity.getString( R.string.login_required) : "");
|
||||
caption_list[i] = ai.acct;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
|
|
|
@ -15,15 +15,19 @@ import jp.juggler.subwaytooter.api.entity.TootAccount;
|
|||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
|
||||
public class SavedAccount extends TootAccount{
|
||||
private static final LogCategory log = new LogCategory( "SavedAccount" );
|
||||
|
||||
static final String table = "access_info";
|
||||
private static final String table = "access_info";
|
||||
|
||||
static final String COL_ID = BaseColumns._ID;
|
||||
static final String COL_HOST = "h";
|
||||
static final String COL_USER = "u";
|
||||
static final String COL_ACCOUNT = "a";
|
||||
static final String COL_TOKEN = "t";
|
||||
static final String COL_LOGIN_REQUIRED = "lr";
|
||||
private static final String COL_ID = BaseColumns._ID;
|
||||
private static final String COL_HOST = "h";
|
||||
private static final String COL_USER = "u";
|
||||
private static final String COL_ACCOUNT = "a";
|
||||
private static final String COL_TOKEN = "t";
|
||||
|
||||
private static final String COL_VISIBILITY = "visibility";
|
||||
private static final String COL_CONFIRM_BOOST = "confirm_boost";
|
||||
private static final String COL_DONT_HIDE_NSFW = "dont_hide_nsfw";
|
||||
|
||||
public static final long INVALID_ID = -1L;
|
||||
|
||||
|
@ -31,8 +35,10 @@ public class SavedAccount extends TootAccount{
|
|||
public long db_id = INVALID_ID;
|
||||
public String host;
|
||||
public String user;
|
||||
public boolean login_required;
|
||||
public JSONObject token_info;
|
||||
public String visibility;
|
||||
public boolean confirm_boost;
|
||||
public boolean dont_hide_nsfw;
|
||||
|
||||
public static void onDBCreate( SQLiteDatabase db ){
|
||||
db.execSQL(
|
||||
|
@ -42,7 +48,9 @@ public class SavedAccount extends TootAccount{
|
|||
+ ",h text not null"
|
||||
+ ",a text not null"
|
||||
+ ",t text not null"
|
||||
+ ",lr integer default 0"
|
||||
+ ",visibility text"
|
||||
+ ",confirm_boost integer default 1"
|
||||
+ ",dont_hide_nsfw integer default 0"
|
||||
+ ")"
|
||||
);
|
||||
db.execSQL("create index if not exists " + table + "_user on " + table + "(u)" );
|
||||
|
@ -53,21 +61,27 @@ public class SavedAccount extends TootAccount{
|
|||
|
||||
}
|
||||
|
||||
private static SavedAccount parse( LogCategory log, Cursor cursor ) throws JSONException{
|
||||
private static SavedAccount parse( Cursor cursor ) throws JSONException{
|
||||
JSONObject src = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_ACCOUNT ) ) );
|
||||
SavedAccount dst = (SavedAccount)parse(log,src,new SavedAccount());
|
||||
if( dst != null){
|
||||
dst.db_id = cursor.getLong( cursor.getColumnIndex( COL_ID ) );
|
||||
dst.host = cursor.getString( cursor.getColumnIndex( COL_HOST ) );
|
||||
dst.user = cursor.getString( cursor.getColumnIndex( COL_USER ) );
|
||||
dst.login_required = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_LOGIN_REQUIRED ) ) );
|
||||
|
||||
int colIdx_visibility = cursor.getColumnIndex( COL_VISIBILITY );
|
||||
dst.visibility = cursor.isNull( colIdx_visibility )? null : cursor.getString( colIdx_visibility );
|
||||
|
||||
dst.confirm_boost = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_CONFIRM_BOOST ) ) );
|
||||
dst.dont_hide_nsfw = ( 0 != cursor.getInt( cursor.getColumnIndex( COL_DONT_HIDE_NSFW ) ) );
|
||||
|
||||
dst.token_info = new JSONObject( cursor.getString( cursor.getColumnIndex( COL_TOKEN ) ) );
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
public static long insert( LogCategory log,String host, String user, JSONObject account,JSONObject token ){
|
||||
public static long insert( String host, String user, JSONObject account,JSONObject token ){
|
||||
try{
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put( COL_HOST, host );
|
||||
|
@ -81,6 +95,14 @@ public class SavedAccount extends TootAccount{
|
|||
return INVALID_ID;
|
||||
}
|
||||
|
||||
public void delete(){
|
||||
try{
|
||||
App1.getDB().delete( table, COL_ID + "=?", new String[]{ Long.toString(db_id) } );
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "saveAccount failed." );
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTokenInfo( JSONObject token_info ){
|
||||
if( db_id != INVALID_ID ){
|
||||
ContentValues cv = new ContentValues();
|
||||
|
@ -88,13 +110,22 @@ public class SavedAccount extends TootAccount{
|
|||
App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString(db_id) } );
|
||||
}
|
||||
}
|
||||
public void saveSetting(){
|
||||
if( db_id != INVALID_ID ){
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put( COL_VISIBILITY, visibility );
|
||||
cv.put( COL_CONFIRM_BOOST, confirm_boost? 1:0 );
|
||||
cv.put( COL_DONT_HIDE_NSFW, dont_hide_nsfw ? 1: 0 );
|
||||
App1.getDB().update( table, cv, COL_ID + "=?", new String[]{ Long.toString(db_id) } );
|
||||
}
|
||||
}
|
||||
|
||||
public static SavedAccount loadAccount( LogCategory log, long id ){
|
||||
try{
|
||||
Cursor cursor = App1.getDB().query( table, null, COL_ID+"=?", new String[]{ Long.toString(id) }, null, null, null );
|
||||
try{
|
||||
if( cursor.moveToFirst() ){
|
||||
return parse( log,cursor );
|
||||
return parse( cursor );
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
|
@ -112,7 +143,7 @@ public class SavedAccount extends TootAccount{
|
|||
Cursor cursor = App1.getDB().query( table, null, null, null, null, null, null );
|
||||
try{
|
||||
while( cursor.moveToNext() ){
|
||||
result.add( parse( log,cursor ) );
|
||||
result.add( parse( cursor ) );
|
||||
}
|
||||
return result;
|
||||
}finally{
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan;
|
||||
|
||||
public abstract class Emojione
|
||||
{
|
||||
private static final Pattern SHORTNAME_PATTERN = Pattern.compile(":([-+\\w]+):");
|
||||
|
||||
private static final HashMap<String,String> map_name2unicode = EmojiMap._shortNameToUnicode;
|
||||
private static final HashMap<String,String> map_unicode2name = EmojiMap._unicodeToShortName;
|
||||
|
||||
static class DecodeEnv{
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
int last_span_start = -1;
|
||||
int last_span_end = -1;
|
||||
|
||||
void closeSpan(){
|
||||
if( last_span_start >= 0 ){
|
||||
CalligraphyTypefaceSpan typefaceSpan = new CalligraphyTypefaceSpan( App1.typeface_emoji );
|
||||
sb.setSpan(typefaceSpan, last_span_start,last_span_end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
last_span_start = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void addEmoji(String s){
|
||||
if( last_span_start < 0 ){
|
||||
last_span_start = sb.length();
|
||||
}
|
||||
sb.append(s);
|
||||
last_span_end = sb.length();
|
||||
}
|
||||
|
||||
void addUnicodeString(String s){
|
||||
int i = 0;
|
||||
int end = s.length();
|
||||
while( i < end ){
|
||||
int remain = end - i;
|
||||
if( remain >= 4 ){
|
||||
String check = s.substring( i, i + 4 );
|
||||
if( map_unicode2name.containsKey( check ) ){
|
||||
addEmoji( check );
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( remain >= 3 ){
|
||||
String check = s.substring( i, i + 3 );
|
||||
if( map_unicode2name.containsKey( check ) ){
|
||||
addEmoji( check );
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( remain >= 2 ){
|
||||
String check = s.substring( i, i + 2 );
|
||||
if( map_unicode2name.containsKey( check ) ){
|
||||
addEmoji( check );
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( remain >= 1 ){
|
||||
String check = s.substring( i, i + 1 );
|
||||
if( map_unicode2name.containsKey( check ) ){
|
||||
addEmoji( check );
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
closeSpan();
|
||||
sb.append( s.charAt( i ) );
|
||||
++ i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CharSequence decodeEmoji( String s ){
|
||||
DecodeEnv decode_env = new DecodeEnv();
|
||||
Matcher matcher = SHORTNAME_PATTERN.matcher(s);
|
||||
int last_end = 0;
|
||||
while( matcher.find() ){
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
if( start > last_end ){
|
||||
decode_env.addUnicodeString(s.substring( last_end,start ));
|
||||
}
|
||||
last_end = end;
|
||||
//
|
||||
String unicode = map_name2unicode.get(matcher.group(1));
|
||||
if( unicode == null ){
|
||||
decode_env.addUnicodeString(s.substring( start, end ));
|
||||
}else{
|
||||
decode_env.addEmoji( unicode );
|
||||
}
|
||||
}
|
||||
// close span
|
||||
decode_env.closeSpan();
|
||||
// copy remain
|
||||
int end = s.length();
|
||||
if( end > last_end ){
|
||||
decode_env.addUnicodeString(s.substring( last_end, end ));
|
||||
}
|
||||
return decode_env.sb;
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import android.text.Html;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
|
||||
import com.emojione.Emojione;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootMention;
|
||||
import jp.juggler.subwaytooter.api.entity.TootTag;
|
||||
|
||||
public class HTMLDecoder {
|
||||
static final LogCategory log = new LogCategory( "HTMLDecoder" );
|
||||
|
||||
|
@ -27,7 +27,9 @@ public class HTMLDecoder {
|
|||
static final Pattern reTag = Pattern.compile( "<(/?)(\\w+)" );
|
||||
static final Pattern reTagEnd = Pattern.compile( "(/?)>$" );
|
||||
static final Pattern reHref = Pattern.compile( "\\bhref=\"([^\"]*)\"" );
|
||||
|
||||
|
||||
|
||||
static class TokenParser {
|
||||
|
||||
final String src;
|
||||
|
@ -133,15 +135,13 @@ public class HTMLDecoder {
|
|||
if( DEBUG_HTML_PARSER ) log.d( "parseChild: %s)%s", indent, tag );
|
||||
}
|
||||
|
||||
|
||||
|
||||
String decodeEmoji( String s ){
|
||||
return Emojione.shortnameToUnicode( s, false );
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void encodeSpan( SpannableStringBuilder sb ){
|
||||
if( TAG_TEXT.equals( tag ) ){
|
||||
sb.append( decodeEntity( decodeEmoji( text ) ) );
|
||||
sb.append( Emojione.decodeEmoji( decodeEntity( text ) ) );
|
||||
return;
|
||||
}
|
||||
if( DEBUG_HTML_PARSER ) sb.append( "(start " + tag + ")" );
|
||||
|
@ -203,6 +203,49 @@ public class HTMLDecoder {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static Spannable decodeTags( TootTag.List src_list ){
|
||||
if( src_list == null || src_list.isEmpty()) return null;
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
for(TootTag item : src_list){
|
||||
if(sb.length() > 0) sb.append(" ");
|
||||
int start = sb.length();
|
||||
sb.append('#');
|
||||
sb.append(item.name);
|
||||
final String item_url = item.url;
|
||||
sb.setSpan( new ClickableSpan() {
|
||||
@Override public void onClick( View widget ){
|
||||
if( link_callback != null ){
|
||||
link_callback.onClickLink( item_url );
|
||||
}
|
||||
}
|
||||
}, start, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
public static Spannable decodeMentions( TootMention.List src_list ){
|
||||
if( src_list == null || src_list.isEmpty()) return null;
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||
for(TootMention item : src_list){
|
||||
if(sb.length() > 0) sb.append(" ");
|
||||
int start = sb.length();
|
||||
sb.append('@');
|
||||
sb.append( item.acct );
|
||||
final String item_url = item.url;
|
||||
sb.setSpan( new ClickableSpan() {
|
||||
@Override public void onClick( View widget ){
|
||||
if( link_callback != null ){
|
||||
link_callback.onClickLink( item_url );
|
||||
}
|
||||
}
|
||||
}, start, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final HashMap< String, Character > entity_map = new HashMap<>();
|
||||
|
||||
private static void _addEntity( String s, char c ){
|
||||
|
@ -215,7 +258,7 @@ public class HTMLDecoder {
|
|||
|
||||
static final Pattern reEntity = Pattern.compile( "&(#?)(\\w+);" );
|
||||
|
||||
static CharSequence decodeEntity( String src ){
|
||||
public static String decodeEntity( String src ){
|
||||
StringBuilder sb = null;
|
||||
Matcher m = reEntity.matcher( src );
|
||||
int last_end = 0;
|
||||
|
@ -262,7 +305,7 @@ public class HTMLDecoder {
|
|||
if( end > last_end ){
|
||||
sb.append( src.substring( last_end, end ) );
|
||||
}
|
||||
return sb;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void init1(){
|
||||
|
|
|
@ -1,692 +0,0 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
//! リトライつきHTTPクライアント
|
||||
public class HTTPClient {
|
||||
|
||||
static final boolean debug_http = false;
|
||||
|
||||
public String[] extra_header;
|
||||
public int rcode;
|
||||
public boolean allow_error = false;
|
||||
public Map< String, List< String > > response_header;
|
||||
public HashMap< String, String > cookie_pot;
|
||||
public int max_try;
|
||||
@SuppressWarnings("unused")
|
||||
public int timeout_dns = 1000 * 3;
|
||||
public int timeout_connect;
|
||||
public int timeout_read;
|
||||
public String caption;
|
||||
public boolean silent_error = false;
|
||||
public long time_expect_connect = 3000;
|
||||
public boolean bDisableKeepAlive = false;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public HTTPClient( int timeout, int max_try, String caption, CancelChecker cancel_checker ){
|
||||
this.cancel_checker = cancel_checker;
|
||||
this.timeout_connect = this.timeout_read = timeout;
|
||||
this.max_try = max_try;
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public HTTPClient( int timeout, int max_try, String caption, final AtomicBoolean _cancel_checker ){
|
||||
this.cancel_checker = new CancelChecker() {
|
||||
@Override
|
||||
public boolean isCancelled(){
|
||||
return _cancel_checker.get();
|
||||
}
|
||||
};
|
||||
this.timeout_connect = this.timeout_read = timeout;
|
||||
this.max_try = max_try;
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setCookiePot( boolean enabled ){
|
||||
if( enabled == ( cookie_pot != null ) ) return;
|
||||
cookie_pot = ( enabled ? new HashMap< String, String >() : null );
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// デフォルトの入力ストリームハンドラ
|
||||
|
||||
HTTPClientReceiver default_receiver = new HTTPClientReceiver() {
|
||||
byte[] buf = new byte[ 2048 ];
|
||||
ByteArrayOutputStream bao = new ByteArrayOutputStream( 0 );
|
||||
|
||||
public byte[] onHTTPClientStream( LogCategory log, CancelChecker cancel_checker, InputStream in, int content_length ){
|
||||
try{
|
||||
bao.reset();
|
||||
for( ; ; ){
|
||||
if( cancel_checker.isCancelled() ){
|
||||
if( debug_http ) log.w(
|
||||
"[%s,read]cancelled!"
|
||||
, caption
|
||||
);
|
||||
return null;
|
||||
}
|
||||
int delta = in.read( buf );
|
||||
if( delta <= 0 ) break;
|
||||
bao.write( buf, 0, delta );
|
||||
}
|
||||
return bao.toByteArray();
|
||||
}catch( Throwable ex ){
|
||||
log.e(
|
||||
"[%s,read] %s:%s"
|
||||
, caption
|
||||
, ex.getClass().getSimpleName()
|
||||
, ex.getMessage()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////
|
||||
// 別スレッドからのキャンセル処理
|
||||
|
||||
public CancelChecker cancel_checker;
|
||||
volatile Thread io_thread;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public boolean isCancelled(){
|
||||
return cancel_checker.isCancelled();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public synchronized void cancel( LogCategory log ){
|
||||
Thread t = io_thread;
|
||||
if( t == null ) return;
|
||||
log.i(
|
||||
"[%s,cancel] %s"
|
||||
, caption
|
||||
, t
|
||||
);
|
||||
try{
|
||||
t.interrupt();
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] post_content = null;
|
||||
public String post_content_type = null;
|
||||
public boolean quit_network_error = false;
|
||||
|
||||
public String last_error = null;
|
||||
public long mtime;
|
||||
|
||||
public static String user_agent = null;
|
||||
|
||||
///////////////////////////////
|
||||
// HTTPリクエスト処理
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public byte[] getHTTP( LogCategory log, String url ){
|
||||
return getHTTP( log, url, default_receiver );
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public byte[] getHTTP( LogCategory log, String url, HTTPClientReceiver receiver ){
|
||||
|
||||
// // http://android-developers.blogspot.jp/2011/09/androids-http-clients.html
|
||||
// // HTTP connection reuse which was buggy pre-froyo
|
||||
// if( Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO ){
|
||||
// System.setProperty( "http.keepAlive", "false" );
|
||||
// }
|
||||
|
||||
try{
|
||||
synchronized( this ){
|
||||
this.io_thread = Thread.currentThread();
|
||||
}
|
||||
URL urlObject;
|
||||
try{
|
||||
urlObject = new URL( url );
|
||||
}catch( MalformedURLException ex ){
|
||||
log.d( "[%s,init] bad url %s %s", caption, url, ex.getMessage() );
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
// desire だと、どうもリソースリークしているようなので行わないことにした。
|
||||
// DNSを引けるか確認する
|
||||
if(debug_http) Log.d(logcat,"check hostname "+url);
|
||||
if( !checkDNSResolver(urlObject) ){
|
||||
Log.w(logcat,"broken name resolver");
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
long timeStart = SystemClock.elapsedRealtime();
|
||||
for( int nTry = 0 ; nTry < max_try ; ++ nTry ){
|
||||
long t1, t2, lap;
|
||||
try{
|
||||
this.rcode = 0;
|
||||
// キャンセルされたか確認
|
||||
if( cancel_checker.isCancelled() ) return null;
|
||||
|
||||
// http connection
|
||||
HttpURLConnection conn = (HttpURLConnection) urlObject.openConnection();
|
||||
|
||||
if( user_agent != null ) conn.setRequestProperty( "User-Agent", user_agent );
|
||||
|
||||
// 追加ヘッダがあれば記録する
|
||||
if( extra_header != null ){
|
||||
for( int i = 0 ; i < extra_header.length ; i += 2 ){
|
||||
conn.addRequestProperty( extra_header[ i ], extra_header[ i + 1 ] );
|
||||
if( debug_http )
|
||||
log.d( "%s: %s", extra_header[ i ], extra_header[ i + 1 ] );
|
||||
}
|
||||
}
|
||||
if( bDisableKeepAlive ){
|
||||
conn.setRequestProperty( "Connection", "close" );
|
||||
}
|
||||
// クッキーがあれば指定する
|
||||
if( cookie_pot != null ){
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for( Map.Entry< String, String > pair : cookie_pot.entrySet() ){
|
||||
if( sb.length() > 0 ) sb.append( "; " );
|
||||
sb.append( pair.getKey() );
|
||||
sb.append( '=' );
|
||||
sb.append( pair.getValue() );
|
||||
}
|
||||
conn.addRequestProperty( "Cookie", sb.toString() );
|
||||
}
|
||||
|
||||
// リクエストを送ってレスポンスの頭を読む
|
||||
try{
|
||||
t1 = SystemClock.elapsedRealtime();
|
||||
if( debug_http )
|
||||
log.d( "[%s,connect] start %s", caption, toHostName( url ) );
|
||||
conn.setDoInput( true );
|
||||
conn.setConnectTimeout( this.timeout_connect );
|
||||
conn.setReadTimeout( this.timeout_read );
|
||||
if( post_content == null ){
|
||||
conn.setDoOutput( false );
|
||||
conn.connect();
|
||||
}else{
|
||||
conn.setDoOutput( true );
|
||||
// if( Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ){
|
||||
// conn.setRequestProperty( "Content-Length", Integer.toString( post_content.length ) );
|
||||
// }
|
||||
if( post_content_type != null ){
|
||||
conn.setRequestProperty( "Content-Type", post_content_type );
|
||||
}
|
||||
OutputStream out = conn.getOutputStream();
|
||||
out.write( post_content );
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
// http://stackoverflow.com/questions/12931791/java-io-ioexception-received-authentication-challenge-is-null-in-ics-4-0-3
|
||||
int rcode;
|
||||
try{
|
||||
// Will throw IOException if server responds with 401.
|
||||
rcode = this.rcode = conn.getResponseCode();
|
||||
}catch( IOException ex ){
|
||||
String sv = ex.getMessage();
|
||||
if( sv != null && sv.contains( "authentication challenge" ) ){
|
||||
log.d( "retry getResponseCode!" );
|
||||
// Will return 401, because now connection has the correct internal state.
|
||||
rcode = this.rcode = conn.getResponseCode();
|
||||
}else{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
mtime = conn.getLastModified();
|
||||
t2 = SystemClock.elapsedRealtime();
|
||||
lap = t2 - t1;
|
||||
if( lap > time_expect_connect )
|
||||
log.d( "[%s,connect] time=%sms %s", caption, lap, toHostName( url ) );
|
||||
|
||||
// ヘッダを覚えておく
|
||||
response_header = conn.getHeaderFields();
|
||||
|
||||
// クッキーが来ていたら覚える
|
||||
if( cookie_pot != null ){
|
||||
String v = conn.getHeaderField( "set-cookie" );
|
||||
if( v != null ){
|
||||
int pos = v.indexOf( '=' );
|
||||
cookie_pot.put( v.substring( 0, pos ), v.substring( pos + 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( rcode >= 500 ){
|
||||
if( ! silent_error )
|
||||
log.e( "[%s,connect] temporary error %d", caption, rcode );
|
||||
last_error = String.format( "(HTTP error %d)", rcode );
|
||||
continue;
|
||||
}else if( ! allow_error && rcode >= 300 ){
|
||||
if( ! silent_error )
|
||||
log.e( "[%s,connect] permanent error %d", caption, rcode );
|
||||
last_error = String.format( "(HTTP error %d)", rcode );
|
||||
return null;
|
||||
}
|
||||
|
||||
}catch( UnknownHostException ex ){
|
||||
rcode = 0;
|
||||
last_error = ex.getClass().getSimpleName();
|
||||
// このエラーはリトライしてもムリ
|
||||
conn.disconnect();
|
||||
return null;
|
||||
}catch( SSLHandshakeException ex ){
|
||||
last_error = String.format( "SSL handshake error. Please check device's date and time. (%s %s)", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
|
||||
if( ! silent_error ){
|
||||
log.e( "[%s,connect] %s"
|
||||
, caption
|
||||
, last_error
|
||||
);
|
||||
if( ex.getMessage() == null ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.rcode = - 1;
|
||||
return null;
|
||||
}catch( Throwable ex ){
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
|
||||
if( ! silent_error ){
|
||||
log.e( "[%s,connect] %s"
|
||||
, caption
|
||||
, last_error
|
||||
);
|
||||
if( ex.getMessage() == null ){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 時計が合ってない場合は Received authentication challenge is null なエラーが出るらしい
|
||||
// getting a 401 Unauthorized error, due to a malformed Authorization header.
|
||||
if( ex instanceof IOException
|
||||
&& ex.getMessage() != null
|
||||
&& ex.getMessage().contains( "authentication challenge" )
|
||||
){
|
||||
ex.printStackTrace();
|
||||
log.d( "Please check device's date and time." );
|
||||
this.rcode = 401;
|
||||
return null;
|
||||
}else if( ex instanceof ConnectException
|
||||
&& ex.getMessage() != null
|
||||
&& ex.getMessage().contains( "ENETUNREACH" )
|
||||
){
|
||||
// このアプリの場合は network unreachable はリトライしない
|
||||
return null;
|
||||
}
|
||||
if( quit_network_error ) return null;
|
||||
|
||||
// 他のエラーはリトライしてみよう。キャンセルされたなら次のループの頭で抜けるはず
|
||||
conn.disconnect();
|
||||
continue;
|
||||
}
|
||||
InputStream in = null;
|
||||
try{
|
||||
if( debug_http ) if( rcode != 200 )
|
||||
log.d( "[%s,read] start status=%d", caption, this.rcode );
|
||||
try{
|
||||
in = conn.getInputStream();
|
||||
}catch( FileNotFoundException ex ){
|
||||
in = conn.getErrorStream();
|
||||
}
|
||||
if( in == null ){
|
||||
log.d( "[%s,read] missing input stream. rcode=%d", caption, rcode );
|
||||
return null;
|
||||
}
|
||||
int content_length = conn.getContentLength();
|
||||
byte[] data = receiver.onHTTPClientStream( log, cancel_checker, in, content_length );
|
||||
if( data == null ) continue;
|
||||
if( data.length > 0 ){
|
||||
if( nTry > 0 ) log.w( "[%s] OK. retry=%d,time=%dms"
|
||||
, caption
|
||||
, nTry
|
||||
, SystemClock.elapsedRealtime() - timeStart
|
||||
);
|
||||
return data;
|
||||
}
|
||||
if( ! cancel_checker.isCancelled()
|
||||
&& ! silent_error
|
||||
){
|
||||
log.w(
|
||||
"[%s,read] empty data."
|
||||
, caption
|
||||
);
|
||||
}
|
||||
}finally{
|
||||
try{
|
||||
if( in != null ) in.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
conn.disconnect();
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if( ! silent_error ) log.e( "[%s] fail. try=%d. rcode=%d", caption, max_try, rcode );
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
}finally{
|
||||
synchronized( this ){
|
||||
io_thread = null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//! HTTPレスポンスのヘッダを読む
|
||||
@SuppressWarnings("unused")
|
||||
public void dump_res_header( LogCategory log ){
|
||||
log.d( "HTTP code %d", rcode );
|
||||
if( response_header != null ){
|
||||
for( Map.Entry< String, List< String > > entry : response_header.entrySet() ){
|
||||
String k = entry.getKey();
|
||||
for( String v : entry.getValue() ){
|
||||
log.d( "%s: %s", k, v );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "ConstantConditions" })
|
||||
public String get_cache( LogCategory log, File file, String url ){
|
||||
String last_error = null;
|
||||
for( int nTry = 0 ; nTry < 10 ; ++ nTry ){
|
||||
if( cancel_checker.isCancelled() ) return "cancelled";
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
try{
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL( url ).openConnection();
|
||||
try{
|
||||
conn.setConnectTimeout( 1000 * 10 );
|
||||
conn.setReadTimeout( 1000 * 10 );
|
||||
if( file.exists() ) conn.setIfModifiedSince( file.lastModified() );
|
||||
conn.connect();
|
||||
this.rcode = conn.getResponseCode();
|
||||
if( rcode == 304 ){
|
||||
if( file.exists() ){
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.setLastModified( now );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if( rcode == 200 ){
|
||||
InputStream in = conn.getInputStream();
|
||||
try{
|
||||
ByteArrayOutputStream bao = new ByteArrayOutputStream();
|
||||
try{
|
||||
byte[] tmp = new byte[ 4096 ];
|
||||
for( ; ; ){
|
||||
if( cancel_checker.isCancelled() ) return "cancelled";
|
||||
int delta = in.read( tmp, 0, tmp.length );
|
||||
if( delta <= 0 ) break;
|
||||
bao.write( tmp, 0, delta );
|
||||
}
|
||||
byte[] data = bao.toByteArray();
|
||||
if( data != null ){
|
||||
FileOutputStream out = new FileOutputStream( file );
|
||||
try{
|
||||
out.write( data );
|
||||
return null;
|
||||
}finally{
|
||||
try{
|
||||
out.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
try{
|
||||
bao.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
if( file.exists() ){
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
}finally{
|
||||
try{
|
||||
in.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
log.e( "http error: %d %s", rcode, url );
|
||||
if( rcode >= 400 && rcode < 500 ){
|
||||
last_error = String.format( "HTTP error %d", rcode );
|
||||
break;
|
||||
}
|
||||
}finally{
|
||||
conn.disconnect();
|
||||
}
|
||||
// retry ?
|
||||
}catch( MalformedURLException ex ){
|
||||
ex.printStackTrace();
|
||||
last_error = String.format( "bad URL:%s", ex.getMessage() );
|
||||
break;
|
||||
}catch( IOException ex ){
|
||||
ex.printStackTrace();
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
}
|
||||
}
|
||||
return last_error;
|
||||
}
|
||||
/////////////////////////////////////////////////////////
|
||||
// 複数URLに対応したリクエスト処理
|
||||
|
||||
public boolean no_cache = false;
|
||||
|
||||
@SuppressWarnings({ "unused", "ConstantConditions" })
|
||||
public File getFile( LogCategory log, File cache_dir, String[] url_list, File _file ){
|
||||
//
|
||||
if( url_list == null || url_list.length < 1 ){
|
||||
setError( 0, "missing url argument." );
|
||||
return null;
|
||||
}
|
||||
// make cache_dir
|
||||
if( cache_dir != null ){
|
||||
if( ! cache_dir.mkdirs() && ! cache_dir.isDirectory() ){
|
||||
setError( 0, "can not create cache_dir" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for( int nTry = 0 ; nTry < 10 ; ++ nTry ){
|
||||
if( cancel_checker.isCancelled() ){
|
||||
setError( 0, "cancelled." );
|
||||
return null;
|
||||
}
|
||||
//
|
||||
String url = url_list[ nTry % url_list.length ];
|
||||
File file = ( _file != null ? _file : new File( cache_dir, Utils.url2name( url ) ) );
|
||||
|
||||
//
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try{
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL( url ).openConnection();
|
||||
if( user_agent != null ) conn.setRequestProperty( "User-Agent", user_agent );
|
||||
try{
|
||||
conn.setConnectTimeout( 1000 * 10 );
|
||||
conn.setReadTimeout( 1000 * 10 );
|
||||
if( ! no_cache && file.exists() )
|
||||
conn.setIfModifiedSince( file.lastModified() );
|
||||
conn.connect();
|
||||
this.rcode = conn.getResponseCode();
|
||||
|
||||
if( debug_http ) if( rcode != 200 ) log.d( "getFile %s %s", rcode, url );
|
||||
|
||||
// 変更なしの場合
|
||||
if( rcode == 304 ){
|
||||
/// log.d("304: %s",file);
|
||||
return file;
|
||||
}
|
||||
|
||||
// 変更があった場合
|
||||
if( rcode == 200 ){
|
||||
// メッセージボディをファイルに保存する
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
try{
|
||||
byte[] tmp = new byte[ 4096 ];
|
||||
in = conn.getInputStream();
|
||||
out = new FileOutputStream( file );
|
||||
for( ; ; ){
|
||||
if( cancel_checker.isCancelled() ){
|
||||
setError( 0, "cancelled" );
|
||||
if( file.exists() ){
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
int delta = in.read( tmp, 0, tmp.length );
|
||||
if( delta <= 0 ) break;
|
||||
out.write( tmp, 0, delta );
|
||||
}
|
||||
out.close();
|
||||
out = null;
|
||||
//
|
||||
long mtime = conn.getLastModified();
|
||||
if( mtime >= 1000 ){
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.setLastModified( mtime );
|
||||
}
|
||||
//
|
||||
/// log.d("200: %s",file);
|
||||
return file;
|
||||
}catch( Throwable ex ){
|
||||
setError( ex );
|
||||
}finally{
|
||||
try{
|
||||
if( in != null ) in.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
try{
|
||||
if( out != null ) out.close();
|
||||
}catch( Throwable ignored ){
|
||||
}
|
||||
}
|
||||
// エラーがあったらリトライ
|
||||
if( file.exists() ){
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// その他、よく分からないケース
|
||||
log.e( "http error: %d %s", rcode, url );
|
||||
|
||||
// URLが複数提供されている場合、404エラーはリトライ対象
|
||||
if( rcode == 404 && url_list.length > 1 ){
|
||||
last_error = String.format( "(HTTP error %d)", rcode );
|
||||
continue;
|
||||
}
|
||||
|
||||
// それ以外の永続エラーはリトライしない
|
||||
if( rcode >= 400 && rcode < 500 ){
|
||||
last_error = String.format( "(HTTP error %d)", rcode );
|
||||
break;
|
||||
}
|
||||
}finally{
|
||||
conn.disconnect();
|
||||
}
|
||||
// retry ?
|
||||
}catch( UnknownHostException ex ){
|
||||
rcode = 0;
|
||||
last_error = ex.getClass().getSimpleName();
|
||||
// このエラーはリトライしてもムリ
|
||||
break;
|
||||
}catch( MalformedURLException ex ){
|
||||
setError( ex );
|
||||
break;
|
||||
}catch( SocketTimeoutException ex ){
|
||||
setError_silent( log, ex );
|
||||
}catch( ConnectException ex ){
|
||||
setError_silent( log, ex );
|
||||
}catch( IOException ex ){
|
||||
setError( ex );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public boolean setError( int i, String string ){
|
||||
rcode = i;
|
||||
last_error = string;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setError( Throwable ex ){
|
||||
ex.printStackTrace();
|
||||
rcode = 0;
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean setError_silent( LogCategory log, Throwable ex ){
|
||||
log.d( "ERROR: %s %s", ex.getClass().getName(), ex.getMessage() );
|
||||
rcode = 0;
|
||||
last_error = String.format( "%s %s", ex.getClass().getSimpleName(), ex.getMessage() );
|
||||
return false;
|
||||
}
|
||||
|
||||
//! HTTPレスポンスのヘッダを読む
|
||||
public String getHeaderString( String key, String defval ){
|
||||
List< String > list = response_header.get( key );
|
||||
if( list != null && list.size() > 0 ){
|
||||
String v = list.get( 0 );
|
||||
if( v != null ) return v;
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
//! HTTPレスポンスのヘッダを読む
|
||||
@SuppressWarnings("unused")
|
||||
public int getHeaderInt( String key, int defval ){
|
||||
String v = getHeaderString( key, null );
|
||||
try{
|
||||
return Integer.parseInt( v, 10 );
|
||||
}catch( Throwable ex ){
|
||||
return defval;
|
||||
}
|
||||
}
|
||||
|
||||
static Pattern reHostName = Pattern.compile( "//([^/]+)/" );
|
||||
|
||||
static String toHostName( String url ){
|
||||
Matcher m = reHostName.matcher( url );
|
||||
if( m.find() ) return m.group( 1 );
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package jp.juggler.subwaytooter.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
//! HTTPClientのバッファ管理を独自に行いたい場合に使用する.
|
||||
//! このインタフェースを実装したものをHTTPClient.getHTTP()の第二引数に指定する
|
||||
public interface HTTPClientReceiver {
|
||||
byte[] onHTTPClientStream( LogCategory log,CancelChecker cancel_checker, InputStream in, int content_length);
|
||||
}
|
|
@ -23,6 +23,7 @@ import android.os.Looper;
|
|||
import android.os.storage.StorageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
@ -45,6 +46,8 @@ import java.util.Map;
|
|||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import jp.juggler.subwaytooter.ActMain;
|
||||
|
||||
public class Utils {
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
|
@ -531,7 +534,17 @@ public class Utils {
|
|||
return MIME_TYPE_APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
|
||||
public static CharSequence formatSpannable1( Context context, int string_id, CharSequence display_name ){
|
||||
String s = context.getString( string_id );
|
||||
int end = s.length();
|
||||
int pos = s.indexOf( "%1$s" );
|
||||
if( pos == -1 ) return s;
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder( );
|
||||
if( pos > 0 ) sb.append(s.substring( 0,pos ));
|
||||
sb.append( display_name);
|
||||
if( pos +4 < end ) sb.append(s.substring( pos+4,end ));
|
||||
return sb;
|
||||
}
|
||||
|
||||
static class FileInfo {
|
||||
|
||||
|
|
After Width: | Height: | Size: 376 B |
After Width: | Height: | Size: 608 B |
After Width: | Height: | Size: 605 B |
After Width: | Height: | Size: 631 B |
After Width: | Height: | Size: 997 B |
After Width: | Height: | Size: 836 B |
After Width: | Height: | Size: 277 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 412 B |
After Width: | Height: | Size: 431 B |
After Width: | Height: | Size: 424 B |
After Width: | Height: | Size: 634 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 196 B |
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#FF808080">
|
||||
<item android:id="@android:id/mask" android:drawable="@android:color/white" />
|
||||
</ripple>
|
After Width: | Height: | Size: 378 B |
After Width: | Height: | Size: 778 B |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 642 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 328 B |
After Width: | Height: | Size: 658 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 506 B |
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#FF808080" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#00000080" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#00000080" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#00000000" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,400 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:fillViewport="true"
|
||||
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingEnd="@dimen/activity_horizontal_margin"
|
||||
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingStart="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
|
||||
android:scrollbarStyle="outsideOverlay"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/instance"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInstance"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:ellipsize="start"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/user"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvUser"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:ellipsize="start"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/actions"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAccessToken"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:ellipsize="start"
|
||||
android:text="@string/get_access_token"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAccountRemove"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:ellipsize="start"
|
||||
android:text="@string/account_remove"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/default_status_visibility"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnVisibility"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/confirm_before_boost"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swConfirmBeforeBoost"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:gravity="center"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/sensitive_content_default_open"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swNSFWOpen"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:gravity="center"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etTargetUrl"-->
|
||||
<!--android:text="@string/target_url"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etTargetUrl"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="textUri"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnHelpTargetUrl"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:text="@string/local_folder"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:id="@+id/tvFolder"-->
|
||||
<!--style="@style/setting_horizontal_stretch"-->
|
||||
<!--android:ellipsize="start"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnFolderPicker"-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:minWidth="32dp"-->
|
||||
<!--android:text="@string/dot_dot"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnHelpFolderPicker"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etFileType"-->
|
||||
<!--android:text="@string/file_type"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etFileType"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="text"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnFileTypeHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etRepeatInterval"-->
|
||||
<!--android:text="@string/repeat_interval"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etRepeatInterval"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="number"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnIntervalHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:text="@string/geo_tagging_mode"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<Spinner-->
|
||||
<!--android:id="@+id/spLocationMode"-->
|
||||
<!--style="@style/setting_horizontal_stretch"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnLocationModeHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etLocationIntervalDesired"-->
|
||||
<!--android:text="@string/location_interval_desired"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etLocationIntervalDesired"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="number"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnLocationIntervalDesiredHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etLocationIntervalMin"-->
|
||||
<!--android:text="@string/location_interval_min"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etLocationIntervalMin"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="number"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnLocationIntervalMinHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:text="@string/force_wifi_ap"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<Switch-->
|
||||
<!--android:id="@+id/swForceWifi"-->
|
||||
<!--style="@style/setting_horizontal_stretch"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnForceWifiHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:labelFor="@+id/etSSID"-->
|
||||
<!--android:text="@string/wifi_ap_ssid"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<EditText-->
|
||||
<!--android:id="@+id/etSSID"-->
|
||||
<!--style="@style/setting_edit_text"-->
|
||||
<!--android:inputType="text"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnSSIDPicker"-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:minWidth="32dp"-->
|
||||
<!--android:text="@string/dot_dot"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnSSIDHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:text="@string/thumbnail_auto_rotate"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<Switch-->
|
||||
<!--android:id="@+id/swThumbnailAutoRotate"-->
|
||||
<!--style="@style/setting_horizontal_stretch"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnThumbnailAutoRotateHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--style="@style/setting_row_label"-->
|
||||
<!--android:text="@string/copy_before_view_send"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<LinearLayout style="@style/setting_row_form">-->
|
||||
|
||||
<!--<Switch-->
|
||||
<!--android:id="@+id/swCopyBeforeViewSend"-->
|
||||
<!--style="@style/setting_horizontal_stretch"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--/>-->
|
||||
|
||||
<!--<Button-->
|
||||
<!--android:id="@+id/btnCopyBeforeViewSendHelp"-->
|
||||
<!--style="@style/setting_row_help"-->
|
||||
<!--/>-->
|
||||
<!--</LinearLayout>-->
|
||||
|
||||
<!--<View style="@style/setting_divider"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:layout_marginBottom="20dp"-->
|
||||
<!--android:layout_marginTop="20dp"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:text="@string/setting_desc"-->
|
||||
<!--/>-->
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -1,118 +1,173 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
android:fillViewport="true"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAccount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:textAllCaps="false"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/post_from"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAccount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAttachment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="@drawable/btn_attachment"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnVisibility"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="@drawable/ic_public"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCharCount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end|center_vertical"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPost"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="@drawable/btn_post"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llAttachment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia1"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia2"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia3"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia4"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:scaleType="fitCenter"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbNSFW"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/nsfw"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAttachment"
|
||||
android:layout_width="wrap_content"
|
||||
<CheckBox
|
||||
android:id="@+id/cbContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/btn_attachment"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/content_warning"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/content_warning_hint"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCharCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/status"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnPost"
|
||||
android:layout_width="wrap_content"
|
||||
<EditText
|
||||
android:id="@+id/etContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/btn_post"
|
||||
android:hint="@string/content_hint"
|
||||
android:inputType="textMultiLine"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llAttachment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia2"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia3"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia4"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/content_warning"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/content_warning_hint"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/status"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:hint="@string/content_hint"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -32,10 +32,10 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#40ffffff"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
android:background="#C0FFFFFF"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
|
@ -43,7 +43,7 @@
|
|||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="#888"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
|
@ -89,6 +89,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="statuses\n124"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -96,6 +97,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="following\n9999"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -103,6 +105,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="followers\n9999"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingTop="12dp"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -15,17 +15,18 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivBoosted"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitEnd"
|
||||
android:src="@drawable/btn_boost"
|
||||
android:id="@+id/ivBoosted"
|
||||
/>
|
||||
|
||||
|
||||
|
@ -50,6 +51,7 @@
|
|||
android:id="@+id/tvBoosted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
tools:text="~にブーストされました"
|
||||
/>
|
||||
|
||||
|
@ -63,14 +65,15 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivFollow"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/ivFollow"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="fitEnd"
|
||||
/>
|
||||
|
@ -86,6 +89,7 @@
|
|||
android:id="@+id/tvFollowerName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
tools:text="Follower Name"
|
||||
/>
|
||||
|
||||
|
@ -99,20 +103,23 @@
|
|||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnFollow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/follow"
|
||||
android:src="@drawable/btn_follow"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:id="@+id/llStatus"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
|
@ -121,6 +128,7 @@
|
|||
android:layout_height="64dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
@ -147,111 +155,146 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:textStyle="bold"
|
||||
tools:text="Displayname @username"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llContentWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:id="@+id/llContentWarning"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnContentWarning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="見る"
|
||||
android:id="@+id/btnContentWarning"
|
||||
android:minWidth="32dp"
|
||||
tools:text="見る"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvContentWarning"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/tvContentWarning"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvContent"
|
||||
android:id="@+id/tvMentions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:text="Contents\nContents"
|
||||
android:gravity="end"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/llContents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:id="@+id/flMedia"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<LinearLayout
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="end"
|
||||
/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:text="Contents\nContents"
|
||||
/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flMedia"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia1"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="32dp"
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:id="@+id/ivMedia4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/thumbnail"
|
||||
android:scaleType="centerCrop"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHideMedia"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/hide"
|
||||
android:src="@drawable/btn_close"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnShowMedia"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@null"
|
||||
android:src="@drawable/btn_close"
|
||||
android:contentDescription="@string/hide"
|
||||
android:id="@+id/btnHideMedia"
|
||||
android:background="#000"
|
||||
android:gravity="center"
|
||||
android:text="@string/tap_to_show"
|
||||
android:textColor="#fff"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000"
|
||||
android:textColor="#fff"
|
||||
android:text="@string/tap_to_show"
|
||||
android:id="@+id/btnShowMedia"
|
||||
android:gravity="center"
|
||||
/>
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
@ -259,32 +302,38 @@
|
|||
<ImageButton
|
||||
android:id="@+id/btnReply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/reply"
|
||||
android:minWidth="48dp"
|
||||
android:src="@drawable/btn_reply"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBoost"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:drawablePadding="4dp"
|
||||
android:minWidth="32dp"
|
||||
android:minWidth="48dp"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnFavourite"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:drawablePadding="4dp"
|
||||
android:minWidth="32dp"
|
||||
android:minWidth="48dp"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnMore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/more"
|
||||
android:minWidth="48dp"
|
||||
android:src="@drawable/btn_more"
|
||||
/>
|
||||
|
||||
|
|
|
@ -40,20 +40,22 @@
|
|||
|
||||
<ImageButton
|
||||
android:id="@+id/btnColumnReload"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/reload"
|
||||
android:src="@drawable/btn_refresh"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnColumnClose"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/btn_bg_transparent"
|
||||
android:contentDescription="@string/close_column"
|
||||
android:src="@drawable/black_close"
|
||||
/>
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
|
||||
<group android:checkableBehavior="single">
|
||||
|
||||
<item android:title="@string/account">
|
||||
<menu>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_account_add"
|
||||
android:icon="@drawable/ic_account_add"
|
||||
android:title="@string/account_add"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_account_setting"
|
||||
android:icon="@drawable/ic_setting"
|
||||
android:title="@string/account_setting"/>
|
||||
</menu>
|
||||
</item>
|
||||
<item android:title="@string/add_column">
|
||||
<menu>
|
||||
<item
|
||||
|
@ -55,7 +62,19 @@
|
|||
<!--android:title="Tools"/>-->
|
||||
</menu>
|
||||
</item>
|
||||
<item android:title="@string/setting">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/nav_column_list"
|
||||
android:icon="@drawable/ic_list"
|
||||
android:title="@string/column_list"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_app_setting"
|
||||
android:icon="@drawable/ic_setting"
|
||||
android:title="@string/app_setting"/>
|
||||
</menu>
|
||||
</item>
|
||||
</group>
|
||||
|
||||
<!--<item android:title="Communicate">-->
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
<color name="colorPrimary">#c4c4c4</color>
|
||||
<color name="colorPrimaryDark">#303030</color>
|
||||
<color name="colorAccent">#5a5a5a</color>
|
||||
|
||||
<color name="colorLink">#00a2ff</color>
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<string name="password_not_specified">password not specified</string>
|
||||
|
||||
<string name="network_error">network error. %1$s</string>
|
||||
<string name="network_error_arg">network error. %1$s</string>
|
||||
<string name="api_error">API error. %1$s</string>
|
||||
|
||||
<string name="register_app_to_server">registering this app to %1$s…</string>
|
||||
|
@ -40,7 +41,6 @@
|
|||
<string name="media_attachment">media attachment</string>
|
||||
<string name="close_column">close column</string>
|
||||
<string name="thumbnail">thumbnail</string>
|
||||
<string name="boosted_by">boosted by %1$s</string>
|
||||
<string name="reload">reload</string>
|
||||
<string name="add_favourites">add your favourites</string>
|
||||
<string name="federate_tl">federate timeline</string>
|
||||
|
@ -53,7 +53,6 @@
|
|||
<string name="your_notifications">your notifications</string>
|
||||
<string name="statuses_of">statuses of %1$s</string>
|
||||
<string name="follow">follow</string>
|
||||
<string name="favourited_by">favourited by %1$s</string>
|
||||
<string name="add_column">add column</string>
|
||||
<string name="profile_page">profile\npage</string>
|
||||
<string name="following">following</string>
|
||||
|
@ -73,4 +72,44 @@
|
|||
<string name="content_hint">please input your status</string>
|
||||
<string name="status">status</string>
|
||||
<string name="list_empty">no items in list</string>
|
||||
<string name="post_from">post from</string>
|
||||
<string name="choose_account">choose account</string>
|
||||
<string name="response_not_json">API response is not JSON.</string>
|
||||
<string name="attachment_too_many">max 4 files allowed.</string>
|
||||
<string name="account_select_please">please select account</string>
|
||||
<string name="login_failed">login failed</string>
|
||||
<string name="file_size_too_big">file size too big. maximum limit is 8MB.</string>
|
||||
<string name="visibility_public">public</string>
|
||||
<string name="visibility_unlisted">unlisted</string>
|
||||
<string name="visibility_private">private</string>
|
||||
<string name="visibility_direct">direct</string>
|
||||
<string name="choose_visibility">choose visibility</string>
|
||||
<string name="confirm_delete_attachment">remove this attachment?</string>
|
||||
<string name="post_error_contents_empty">please input your status.</string>
|
||||
<string name="post_error_contents_warning_empty">please input contents warning</string>
|
||||
<string name="wait_previous_operation">Please wait until last operation is over.</string>
|
||||
<string name="cant_remove_boost_while_favourited">Can\'t remove boost while favourited</string>
|
||||
<string name="confirm">confirm</string>
|
||||
<string name="confirm_boost">Boost this status? it\'s shown by all followers and your profile page.</string>
|
||||
<string name="tags">tags</string>
|
||||
<string name="mentions">mentions</string>
|
||||
|
||||
<string name="display_name_favourited_by">favourited by %1$s</string>
|
||||
<string name="display_name_boosted_by">boosted by %1$s</string>
|
||||
<string name="display_name_replied_by">replied by %1$s</string>
|
||||
<string name="display_name_followed_by">followed by %1$s</string>
|
||||
<string name="account">account</string>
|
||||
<string name="account_setting">account setting</string>
|
||||
<string name="setting">setting</string>
|
||||
<string name="app_setting">app setting</string>
|
||||
<string name="columun_list">column list</string>
|
||||
<string name="column_list">column list</string>
|
||||
<string name="get_access_token">get access token</string>
|
||||
<string name="account_remove">remove account from this app</string>
|
||||
<string name="actions">actions</string>
|
||||
<string name="default_status_visibility">default visibility of status</string>
|
||||
<string name="confirm_before_boost">confirm before boost</string>
|
||||
<string name="sensitive_content_default_open">sensitive content default open</string>
|
||||
<string name="user">user</string>
|
||||
<string name="confirm_account_remove">Account will be deleted. also all columns are remove.\nAre you sure?</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
|
@ -17,4 +17,75 @@
|
|||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
<style name="setting_group_header">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">20sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:layout_marginTop">20dp</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="setting_row_label">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
|
||||
<item name="android:paddingLeft">12dp</item>
|
||||
<item name="android:paddingRight">0dp</item>
|
||||
|
||||
<item name="android:paddingStart" tools:ignore="NewApi">12dp</item>
|
||||
<item name="android:paddingEnd" tools:ignore="NewApi">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="setting_row_form">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:orientation">horizontal</item>
|
||||
<item name="android:baselineAligned">true</item>
|
||||
|
||||
<item name="android:paddingLeft">48dp</item>
|
||||
<item name="android:paddingRight">0dp</item>
|
||||
|
||||
<item name="android:paddingStart" tools:ignore="NewApi">48dp</item>
|
||||
<item name="android:paddingEnd" tools:ignore="NewApi">0dp</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="setting_row_help">
|
||||
<item name="android:background">@drawable/btn_bg_transparent</item>
|
||||
<item name="android:textColor">@color/colorAccent</item>
|
||||
<item name="android:layout_width">40dp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:minWidth">32dp</item>
|
||||
<item name="android:minHeight">32dp</item>
|
||||
|
||||
<item name="android:layout_marginLeft">8dp</item>
|
||||
<item name="android:layout_marginRight">0dp</item>
|
||||
|
||||
<item name="android:layout_marginStart" tools:ignore="NewApi">8dp</item>
|
||||
<item name="android:layout_marginEnd" tools:ignore="NewApi">0dp</item>
|
||||
|
||||
<item name="android:text">@string/question</item>
|
||||
</style>
|
||||
|
||||
<style name="setting_divider">
|
||||
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1dp</item>
|
||||
<item name="android:background">#ddd</item>
|
||||
<item name="android:layout_marginTop">12dp</item>
|
||||
<item name="android:layout_marginBottom">12dp</item>
|
||||
</style>
|
||||
|
||||
<style name="setting_horizontal_stretch">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
</style>
|
||||
|
||||
<style name="setting_edit_text" parent="@style/setting_horizontal_stretch">
|
||||
<item name="android:imeOptions">actionDone</item>
|
||||
</style>
|
||||
|
||||
<string name="question">\?</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
#!perl --
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
use LWP::Simple;
|
||||
use JSON;
|
||||
use Data::Dump qw(dump);
|
||||
use Encode;
|
||||
|
||||
# perl convert-emoji-codes.pl < eac.json >converted.txt
|
||||
|
||||
# eac.json is here
|
||||
# https://github.com/Ranks/emojione/blob/master/extras/alpha-codes/eac.json
|
||||
|
||||
my $data;
|
||||
{
|
||||
local $/ = undef;
|
||||
$data = <STDIN>;
|
||||
}
|
||||
|
||||
my $eac_map = decode_json $data;
|
||||
undef $data;
|
||||
|
||||
my @list;
|
||||
while( my($k,$v)=each %$eac_map){
|
||||
|
||||
{
|
||||
my $t = $v->{"alpha code"};
|
||||
my @a = ($t =~ /:([^\s:]+):/g );
|
||||
for(@a){
|
||||
push @list,[$k, $_ ];
|
||||
}
|
||||
}
|
||||
{
|
||||
my $t = $v->{"aliases"};
|
||||
my @a = ($t =~ /:([^\s:]+):/g );
|
||||
for(@a){
|
||||
push @list,[$k, $_ ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my %unicode_map;
|
||||
sub putUnicodeMap{
|
||||
my($map,$char,@remain)=@_;
|
||||
$map->{$char} or $map->{$char} = {};
|
||||
if(not @remain){
|
||||
$map->{$char}->{e}=1;
|
||||
}else{
|
||||
putUnicodeMap( $map->{$char} ,@remain );
|
||||
}
|
||||
}
|
||||
|
||||
my $func_num = 0;
|
||||
my $n = 0;
|
||||
my $codepoint_max = 0;
|
||||
my $length_max = 0;
|
||||
sub addCode{
|
||||
my($k,$name)=@_;
|
||||
if( $n == 0 ){
|
||||
++$func_num;
|
||||
print "\tprivate static void init$func_num(){\n";
|
||||
}
|
||||
my @chars = split /-/,$k;
|
||||
for(@chars){
|
||||
my $codepoint = hex($_);
|
||||
if( $codepoint > $codepoint_max ){
|
||||
$codepoint_max = $codepoint;
|
||||
}
|
||||
}
|
||||
my $char_count = 0+@chars;
|
||||
if( $char_count > $length_max ){
|
||||
$length_max = $char_count;
|
||||
}
|
||||
|
||||
my $char_java = join(',',map{ "0x$_"} @chars );
|
||||
print qq|\t\t_addEntry("$name", new String(new int[] {$char_java}, 0, $char_count));\n|;
|
||||
if( ++$n > 100 ){
|
||||
print "\t}\n";
|
||||
$n = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(sort {$a->[1] cmp $b->[1]} @list){
|
||||
addCode( @$_ );
|
||||
}
|
||||
if( $n > 0 ){
|
||||
print "\t}\n";
|
||||
}
|
||||
print "\tstatic{\n";
|
||||
for(my $i=1;$i <= $func_num;++$i){
|
||||
print "\t\tinit$i();\n";
|
||||
}
|
||||
print "\t}\n";
|
||||
|
||||
printf "//codepoint_max=0x%x, length_max=$length_max\n",$codepoint_max;
|