diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index f8d6a385..04039db8 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -2,6 +2,7 @@ dont + emoji emojione favourited noto @@ -10,6 +11,7 @@ reblogged reblogs subwaytooter + swipy timelines unfavourite unreblog diff --git a/app/build.gradle b/app/build.gradle index 92660f52..7f5453c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,16 @@ +import java.text.SimpleDateFormat + apply plugin: 'com.android.application' android { - compileSdkVersion 24 - buildToolsVersion '25.0.0' + compileSdkVersion 25 + buildToolsVersion '25.0.2' defaultConfig { applicationId "jp.juggler.subwaytooter" minSdkVersion 21 - targetSdkVersion 24 + targetSdkVersion 25 versionCode 1 - versionName "1.0" + versionName "0.0.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -17,23 +19,44 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + productFlavors { + rc { + } + } + + // Generate Signed APK のファイル名を変更 + applicationVariants.all { variant -> + if (variant.buildType.name == "release") { + variant.outputs.each { output -> + if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) { + // Rename APK + def versionCode = defaultConfig.versionCode + def versionName = defaultConfig.versionName + def flavor = variant.flavorName + def date = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + def newName = "SubwayTooter-${flavor}-${versionCode}-${versionName}-${date}.apk" + output.outputFile = new File((String) output.outputFile.parent, (String) newName) + } + } + } + } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:24.2.0' - compile 'com.android.support:support-v4:24.2.0' - compile 'com.android.support:design:24.2.0' + compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support:support-v4:25.3.1' + compile 'com.android.support:design:25.3.1' + compile 'com.android.support:customtabs:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' - 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' - + compile 'com.github.woxthebox:draglistview:1.4.3' + compile 'com.github.omadahealth:swipy:1.2.3@aar' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c3285c3d..8f2d51d3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,25 +36,24 @@ - \ No newline at end of file diff --git a/app/src/main/assets/NotoSansCJKjp-Regular.otf b/app/src/main/assets/NotoSansCJKjp-Regular.otf deleted file mode 100644 index 296fbebd..00000000 Binary files a/app/src/main/assets/NotoSansCJKjp-Regular.otf and /dev/null differ diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java index d6227360..8d9dc96f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.java @@ -1,10 +1,14 @@ package jp.juggler.subwaytooter; +import android.app.Dialog; +import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v4.os.AsyncTaskCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; @@ -13,9 +17,14 @@ import android.widget.CompoundButton; import android.widget.Switch; import android.widget.TextView; +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.LoginForm; import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.util.LogCategory; +import jp.juggler.subwaytooter.util.Utils; public class ActAccountSetting extends AppCompatActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { static final LogCategory log = new LogCategory( "ActAccountSetting" ); @@ -77,7 +86,6 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick swConfirmBeforeBoost.setChecked( a.confirm_boost ); swNSFWOpen.setChecked( a.dont_hide_nsfw ); - updateVisibility(); } @@ -177,8 +185,82 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick /////////////////////////////////////////////////// private void performAccessToken(){ + LoginForm.showLoginForm( this, account.host, new LoginForm.LoginFormCallback() { + + @Override + public void startLogin( final Dialog dialog, final String instance, final String user_mail, final String password ){ + + final ProgressDialog progress = new ProgressDialog( ActAccountSetting.this ); + + final AsyncTask< Void, String, TootApiResult > task = new AsyncTask< Void, String, TootApiResult >() { + + long row_id; + + @Override + protected TootApiResult doInBackground( Void... params ){ + TootApiClient api_client = new TootApiClient( ActAccountSetting.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 ); + } + } ); + } + } ); + + api_client.setUserInfo( instance, user_mail, password ); + + TootApiResult result = api_client.request( "/api/v1/accounts/verify_credentials" ); + if( result != null && result.object != null ){ + TootAccount ta = TootAccount.parse( log, result.object ); + + if( ! ta.username.equals( account.username ) ){ + return new TootApiResult( getString( R.string.user_name_not_match ) ); + } + + account.updateTokenInfo( result.token_info ); + row_id = account.db_id; + } + return result; + } + + @Override + protected void onPostExecute( TootApiResult result ){ + progress.dismiss(); + + if( result == null ){ + // cancelled. + }else if( result.object == null ){ + Utils.showToast( ActAccountSetting.this, true, result.error ); + log.e( result.error ); + }else{ + Utils.showToast( ActAccountSetting.this, false, R.string.access_token_updated ); + dialog.dismiss(); + } + } + }; + 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 ); + } + } ); + } - + /////////////////////////////////////////////////// } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java index e34b7845..7cc4922d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.java @@ -1,10 +1,81 @@ package jp.juggler.subwaytooter; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.os.AsyncTaskCompat; +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; -/** - * Created by tateisu on 2017/04/22. - */ +import jp.juggler.subwaytooter.util.LogCategory; -public class ActAppSetting extends AppCompatActivity { +public class ActAppSetting extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener { + + static final LogCategory log = new LogCategory( "ActAppSetting" ); + + public static void open( Context context ){ + context.startActivity( new Intent(context,ActAppSetting.class) ); + + } + + SharedPreferences pref; + + @Override + protected void onCreate( @Nullable Bundle savedInstanceState ){ + super.onCreate( savedInstanceState ); + initUI(); + pref = Pref.pref(this); + + loadUIFromData(); + + } + + TextView tvInstance; + TextView tvUser; + View btnAccessToken; + View btnAccountRemove; + Button btnVisibility; + Switch swBackToColumnList; + Switch swDontConfirmBeforeCloseColumn; + + private void initUI(){ + setContentView( R.layout.act_app_setting ); + swBackToColumnList = (Switch) findViewById( R.id.swBackToColumnList ); + swDontConfirmBeforeCloseColumn = (Switch) findViewById( R.id.swDontConfirmBeforeCloseColumn ); + + swBackToColumnList.setOnCheckedChangeListener( this ); + swDontConfirmBeforeCloseColumn.setOnCheckedChangeListener( this ); + } + + private void loadUIFromData( ){ + + swBackToColumnList.setChecked( pref.getBoolean( Pref.KEY_BACK_TO_COLUMN_LIST,false ) ); + swDontConfirmBeforeCloseColumn.setChecked( pref.getBoolean( Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN,false ) ); + + } + + private void saveUIToData(){ + pref + .edit() + .putBoolean( Pref.KEY_BACK_TO_COLUMN_LIST,swBackToColumnList.isChecked() ) + .putBoolean( Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN,swDontConfirmBeforeCloseColumn.isChecked() ) + .apply(); + } + + @Override + public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){ + saveUIToData(); + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActColumnList.java b/app/src/main/java/jp/juggler/subwaytooter/ActColumnList.java index 936274bd..dfc5239b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActColumnList.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActColumnList.java @@ -1,10 +1,299 @@ package jp.juggler.subwaytooter; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; -/** - * Created by tateisu on 2017/04/22. - */ +import com.woxthebox.draglistview.DragItem; +import com.woxthebox.draglistview.DragItemAdapter; +import com.woxthebox.draglistview.DragListView; +import com.woxthebox.draglistview.swipe.ListSwipeHelper; +import com.woxthebox.draglistview.swipe.ListSwipeItem; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import jp.juggler.subwaytooter.util.Utils; public class ActColumnList extends AppCompatActivity { + public static final String EXTRA_ORDER = "order"; + public static final String EXTRA_SELECTION = "selection"; + + @Override + protected void onCreate( @Nullable Bundle savedInstanceState ){ + super.onCreate( savedInstanceState ); + initUI(); + + if( savedInstanceState != null ){ + restoreData( + savedInstanceState.getString( EXTRA_ORDER ) + , savedInstanceState.getInt( EXTRA_SELECTION ) + ); + }else{ + Intent intent = getIntent(); + restoreData( + intent.getStringExtra( EXTRA_ORDER ) + , intent.getIntExtra( EXTRA_SELECTION, - 1 ) + ); + } + } + + @Override + protected void onSaveInstanceState( Bundle outState ){ + super.onSaveInstanceState( outState ); + // + outState.putInt( EXTRA_SELECTION, old_selection ); + // + JSONArray array = new JSONArray(); + List< MyItem > item_list = listAdapter.getItemList(); + for( int i = 0, ie = item_list.size() ; i < ie ; ++ i ){ + array.put( item_list.get( i ).json ); + } + outState.putString( EXTRA_ORDER, array.toString() ); + } + + @Override + public void onBackPressed(){ + makeResult( - 1 ); + super.onBackPressed(); + } + + DragListView listView; + MyListAdapter listAdapter; + int old_selection; + + private void initUI(){ + setContentView( R.layout.act_column_list ); + + // リストのアダプター + listAdapter = new MyListAdapter(); + + // ハンドル部分をドラッグで並べ替えできるRecyclerView + listView = (DragListView) findViewById( R.id.drag_list_view ); + listView.setLayoutManager( new LinearLayoutManager( this ) ); + listView.setAdapter( listAdapter, true ); + listView.setCanDragHorizontally( false ); + listView.setCustomDragItem( new MyDragItem( this, R.layout.lv_column_list ) ); + + listView.getRecyclerView().setVerticalScrollBarEnabled( true ); + listView.setDragListListener( new DragListView.DragListListenerAdapter() { + @Override + public void onItemDragStarted( int position ){ + // 操作中はリフレッシュ禁止 + // mRefreshLayout.setEnabled( false ); + } + + @Override + public void onItemDragEnded( int fromPosition, int toPosition ){ + // 操作完了でリフレッシュ許可 + // mRefreshLayout.setEnabled( USE_SWIPE_REFRESH ); + +// if( fromPosition != toPosition ){ +// // 並べ替えが発生した +// } + } + } ); + + // リストを左右スワイプした + listView.setSwipeListener( new ListSwipeHelper.OnSwipeListenerAdapter() { + + @Override + public void onItemSwipeStarted( ListSwipeItem item ){ + // 操作中はリフレッシュ禁止 + // mRefreshLayout.setEnabled( false ); + } + + @Override + public void onItemSwipeEnded( ListSwipeItem item, ListSwipeItem.SwipeDirection swipedDirection ){ + // 操作完了でリフレッシュ許可 + // mRefreshLayout.setEnabled( USE_SWIPE_REFRESH ); + + // 左にスワイプした(右端に青が見えた) なら要素を削除する + if( swipedDirection == ListSwipeItem.SwipeDirection.LEFT ){ + MyItem adapterItem = (MyItem) item.getTag(); + listAdapter.removeItem( listAdapter.getPositionForItem( adapterItem ) ); + } + } + } ); + } + + void restoreData( String svColumnList, int ivSelection ){ + + this.old_selection = ivSelection; + + ArrayList< MyItem > tmp_list = new ArrayList<>(); + try{ + JSONArray array = new JSONArray( svColumnList ); + for( int i = 0, ie = array.length() ; i < ie ; ++ i ){ + try{ + JSONObject src = array.optJSONObject( i ); + MyItem item = new MyItem( src, i ); + if( src != null ){ + tmp_list.add( item ); + if( old_selection == item.old_index ){ + item.setOldSelection( true ); + } + } + }catch( Throwable ex2 ){ + ex2.printStackTrace(); + } + } + }catch( Throwable ex ){ + ex.printStackTrace(); + } + listAdapter.setItemList( tmp_list ); + } + + void makeResult( int new_selection ){ + Intent intent = new Intent(); + + List< MyItem > item_list = listAdapter.getItemList(); + // どの要素を選択するか + if( new_selection >= 0 && new_selection < listAdapter.getItemCount() ){ + intent.putExtra( EXTRA_SELECTION, new_selection ); + }else{ + for( int i = 0, ie = item_list.size() ; i < ie ; ++ i ){ + if( item_list.get( i ).bOldSelection ){ + intent.putExtra( EXTRA_SELECTION, i ); + break; + } + } + } + // 並べ替え用データ + ArrayList< Integer > order_list = new ArrayList<>(); + for( MyItem item : item_list ){ + order_list.add( item.old_index ); + } + intent.putExtra( EXTRA_ORDER, order_list ); + + setResult( RESULT_OK, intent ); + } + + private void performItemSelected( MyItem item ){ + int idx = listAdapter.getPositionForItem( item ); + makeResult( idx ); + finish(); + } + + // リスト要素のデータ + static class MyItem { + long id; + JSONObject json; + String name; + String access; + boolean bOldSelection; + int old_index; + + MyItem( JSONObject src, long id ){ + this.json = src; + this.name = src.optString( Column.KEY_COLUMN_NAME ); + this.access = src.optString( Column.KEY_COLUMN_ACCESS ); + this.old_index = src.optInt( Column.KEY_OLD_INDEX ); + this.id = id; + } + + void setOldSelection( boolean b ){ + bOldSelection = b; + } + } + + // リスト要素のViewHolder + static class MyViewHolder extends DragItemAdapter.ViewHolder { + final View ivBookmark; + final TextView tvAccess; + final TextView tvName; + + MyViewHolder( final View viewRoot ){ + super( viewRoot + , R.id.ivDragHandle // View ID。 ここを押すとドラッグ操作をすぐに開始する + , true // 長押しでドラッグ開始するなら真 + ); + + // リスト要素のビューが ListSwipeItem だった場合、Swipe操作を制御できる + if( viewRoot instanceof ListSwipeItem ){ + ListSwipeItem lsi = (ListSwipeItem) viewRoot; + lsi.setSwipeInStyle( ListSwipeItem.SwipeInStyle.SLIDE ); + lsi.setSupportedSwipeDirection( ListSwipeItem.SwipeDirection.LEFT ); + } + + ivBookmark = viewRoot.findViewById( R.id.ivBookmark ); + tvAccess = (TextView) viewRoot.findViewById( R.id.tvAccess ); + tvName = (TextView) viewRoot.findViewById( R.id.tvName ); + } + + void bind( MyItem item ){ + itemView.setTag( item ); // itemView は親クラスのメンバ変数 + ivBookmark.setVisibility( item.bOldSelection ? View.VISIBLE: View.INVISIBLE ); + tvAccess.setText( item.access ); + tvName.setText( item.name ); + } + +// @Override +// public boolean onItemLongClicked( View view ){ +// return false; +// } + + @Override + public void onItemClicked( View view ){ + MyItem item = (MyItem) itemView.getTag(); // itemView は親クラスのメンバ変数 + ActColumnList activity = ( (ActColumnList) Utils.getActivityFromView( view ) ); + if( activity != null ) activity.performItemSelected( item ); + } + } + + // ドラッグ操作中のデータ + private static class MyDragItem extends DragItem { + MyDragItem( Context context, int layoutId ){ + super( context, layoutId ); + } + + @Override + public void onBindDragView( View clickedView, View dragView ){ + dragView.findViewById( R.id.ivBookmark ).setVisibility( + clickedView.findViewById( R.id.ivBookmark ).getVisibility() + ); + ((TextView)dragView.findViewById( R.id.tvAccess )).setText( + ((TextView)clickedView.findViewById( R.id.tvAccess )).getText() + ); + ((TextView)dragView.findViewById( R.id.tvName )).setText( + ((TextView)clickedView.findViewById( R.id.tvName )).getText() + ); + } + } + + private class MyListAdapter extends DragItemAdapter< MyItem, MyViewHolder > { + + MyListAdapter(){ + super(); + setHasStableIds( true ); + setItemList( new ArrayList< MyItem >() ); + } + + @Override + public MyViewHolder onCreateViewHolder( ViewGroup parent, int viewType ){ + View view = getLayoutInflater().inflate( R.layout.lv_column_list, parent, false ); + return new MyViewHolder( view ); + } + + @Override + public void onBindViewHolder( MyViewHolder holder, int position ){ + super.onBindViewHolder( holder, position ); + holder.bind( getItemList().get( position ) ); + } + + @Override + public long getItemId( int position ){ + MyItem item = mItemList.get( position ); // mItemList は親クラスのメンバ変数 + return item.id; + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java index 499cde55..61992046 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.java @@ -4,6 +4,8 @@ import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -31,8 +33,7 @@ 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.ArrayList; import java.util.HashSet; import jp.juggler.subwaytooter.api.TootApiClient; @@ -59,11 +60,15 @@ public class ActMain extends AppCompatActivity // super.attachBaseContext( CalligraphyContextWrapper.wrap(newBase)); // } + SharedPreferences pref; + @Override protected void onCreate( Bundle savedInstanceState ){ super.onCreate( savedInstanceState ); requestWindowFeature( Window.FEATURE_NO_TITLE ); + pref = Pref.pref(this); + initUI(); loadColumnList(); } @@ -106,11 +111,36 @@ public class ActMain extends AppCompatActivity super.onPause(); } + static final int REQUEST_CODE_COLUMN_LIST =1; + + @Override + protected void onActivityResult( int requestCode, int resultCode, Intent data ){ + if( resultCode == RESULT_OK && requestCode == REQUEST_CODE_COLUMN_LIST ){ + if( data != null ){ + ArrayList order = data.getIntegerArrayListExtra( ActColumnList.EXTRA_ORDER ); + if( order != null ){ + pager_adapter.setOrder( pager, order ); + } + if( pager_adapter.column_list.isEmpty() ){ + llEmpty.setVisibility( View.VISIBLE ); + } + + int select = data.getIntExtra( ActColumnList.EXTRA_SELECTION ,-1); + if( select != -1 ){ + pager.setCurrentItem( select,true ); + } + } + } + super.onActivityResult( requestCode, resultCode, data ); + } + @Override public void onBackPressed(){ DrawerLayout drawer = (DrawerLayout) findViewById( R.id.drawer_layout ); if( drawer.isDrawerOpen( GravityCompat.START ) ){ drawer.closeDrawer( GravityCompat.START ); + }else if( pref.getBoolean( Pref.KEY_BACK_TO_COLUMN_LIST ,false) ){ + performColumnList(); }else if( ! pager_adapter.column_list.isEmpty() ){ performColumnClose( false,pager_adapter.getColumn( pager.getCurrentItem() ) ); }else{ @@ -237,7 +267,7 @@ public class ActMain extends AppCompatActivity } public void performAccountAdd(){ - LoginForm.showLoginForm( this, new LoginForm.LoginFormCallback() { + LoginForm.showLoginForm( this, null,new LoginForm.LoginFormCallback() { @Override public void startLogin( final Dialog dialog, final String instance, final String user_mail, final String password ){ @@ -311,18 +341,9 @@ public class ActMain extends AppCompatActivity } - private void onAccountUpdated( SavedAccount data ){ - Utils.showToast( this, false, R.string.account_confirmed ); - // - llEmpty.setVisibility( View.GONE ); - // - Column col = new Column( this, data, Column.TYPE_TL_HOME ); - int idx = pager_adapter.addColumn( pager, col ); - pager.setCurrentItem( idx ); - } - + public void performColumnClose( boolean bConfirm,final Column column ){ - if(! bConfirm ){ + if(! bConfirm && ! pref.getBoolean( Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN,false ) ){ new AlertDialog.Builder( this ) .setTitle( R.string.confirm ) .setMessage( R.string.close_column ) @@ -346,27 +367,55 @@ public class ActMain extends AppCompatActivity } } + ////////////////////////////////////////////////////////////// + // カラム追加系 - void performOpenUser(SavedAccount access_info,TootAccount user){ + public void addColumn(SavedAccount ai,int type,long who,long status_id ){ + // 既に同じカラムがあればそこに移動する + for( Column column : pager_adapter.column_list ){ + if( ai.user.equals( column.access_info.user ) + && column.type == type + && column.who_id == who + && column.status_id == status_id + ){ + pager.setCurrentItem( pager_adapter.column_list.indexOf( column ) ,true); + return; + } + } + 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); + Column col = new Column( ActMain.this, ai, type, who,status_id ); + int idx = pager_adapter.addColumn( pager, col ); + pager.setCurrentItem( idx ,true); } - private void performAddTimeline( final int type, final Object... params ){ + private void onAccountUpdated( SavedAccount data ){ + Utils.showToast( this, false, R.string.account_confirmed ); + addColumn(data, Column.TYPE_TL_HOME,data.id ,0L ); + } + + void performOpenUser(SavedAccount access_info,TootAccount user){ + addColumn( access_info,Column.TYPE_TL_STATUSES, user.id ,0L); + } + + + public void performConversation( SavedAccount access_info, TootStatus status ){ + addColumn( access_info,Column.TYPE_TL_CONVERSATION, access_info.id, status.id ); + } + + private void performAddTimeline( final int type ){ AccountPicker.pick( this, new AccountPicker.AccountPickerCallback() { @Override public void onAccountPicked( SavedAccount ai ){ - llEmpty.setVisibility( View.GONE ); - // - Column col = new Column( ActMain.this, ai, type, ai.id, params ); - int idx = pager_adapter.addColumn( pager, col ); - pager.setCurrentItem( idx ,true); + addColumn( ai, type,ai.id ,0L); } } ); } + ////////////////////////////////////////////////////////////// + + public void openBrowser( String url ){ openChromeTab( url ); } @@ -393,75 +442,16 @@ public class ActMain extends AppCompatActivity openChromeTab( url ); } }; - - static final String FILE_COLUMN_LIST = "column_list"; - - private void loadColumnList(){ - try{ - InputStream is = openFileInput( FILE_COLUMN_LIST ); - try{ - ByteArrayOutputStream bao = new ByteArrayOutputStream( is.available() ); - byte[] tmp = new byte[ 4096 ]; - for( ; ; ){ - int r = is.read( tmp, 0, tmp.length ); - if( r <= 0 ) break; - bao.write( tmp, 0, r ); - } - JSONArray array = new JSONArray( Utils.decodeUTF8( bao.toByteArray() ) ); - for( int i = 0, ie = array.length() ; i < ie ; ++ i ){ - try{ - JSONObject src = array.optJSONObject( i ); - Column col = new Column( ActMain.this, src ); - pager_adapter.addColumn( pager, col ); - }catch( Throwable ex ){ - ex.printStackTrace(); - } - } - }finally{ - is.close(); - } - }catch( FileNotFoundException ignored ){ - }catch( Throwable ex ){ - ex.printStackTrace(); - Utils.showToast( this, ex, "loadColumnList failed." ); - } - - if( pager_adapter.column_list.size() > 0 ){ - llEmpty.setVisibility( View.GONE ); - } - } - - private void saveColumnList(){ - JSONArray array = new JSONArray(); - for( Column column : pager_adapter.column_list ){ - try{ - JSONObject item = new JSONObject(); - column.encodeJSON( item ); - array.put( item ); - }catch( JSONException ex ){ - ex.printStackTrace(); - } - } - try{ - OutputStream os = openFileOutput( FILE_COLUMN_LIST, MODE_PRIVATE ); - try{ - os.write( Utils.encodeUTF8( array.toString() ) ); - }finally{ - os.close(); - } - }catch( Throwable ex ){ - ex.printStackTrace(); - Utils.showToast( this, ex, "saveColumnList failed." ); - } - } - - private void performTootButton(){ + private void performTootButton(){ Column c = pager_adapter.getColumn( pager.getCurrentItem() ); if( c != null && c.access_info != null ){ ActPost.open( this, c.access_info.db_id ,null ); } } + public void performReply( SavedAccount account, TootStatus status ){ + ActPost.open( this, account.db_id ,status ); + } ///////////////////////////////////////////////////////////////////////// @@ -710,17 +700,10 @@ public class ActMain extends AppCompatActivity 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() { @@ -732,8 +715,89 @@ public class ActMain extends AppCompatActivity } private void performAppSetting(){ - Utils.showToast( this,false,"not implemented." ); + ActAppSetting.open( ActMain.this); } + + //////////////////////////////////////////////////////// + // column list + + + JSONArray encodeColumnList(){ + JSONArray array = new JSONArray(); + for(int i=0,ie = pager_adapter.column_list.size(); i 0 ){ + llEmpty.setVisibility( View.GONE ); + } + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java index 1f31e5b1..a77cc270 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.java @@ -29,6 +29,7 @@ import com.android.volley.toolbox.NetworkImageView; import org.apache.commons.io.IOUtils; import org.json.JSONArray; +import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; @@ -39,8 +40,10 @@ 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.TootMention; import jp.juggler.subwaytooter.api.entity.TootStatus; 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.MediaType; @@ -53,13 +56,21 @@ 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"; + static final String KEY_REPLY_STATUS = "reply_status"; - public static void open( Context context, long account_db_id, String visibility ){ + static final String KEY_ATTACHMENT_LIST = "attachment_list"; + static final String KEY_VISIBILITY = "visibility"; + static final String KEY_IN_REPLY_TO_ID = "in_reply_to_id"; + static final String KEY_IN_REPLY_TO_TEXT = "in_reply_to_text"; + static final String KEY_IN_REPLY_TO_IMAGE = "in_reply_to_image"; + + + public static void open( Context context, long account_db_id, TootStatus reply_status ){ Intent intent = new Intent( context, ActPost.class ); intent.putExtra( KEY_ACCOUNT_DB_ID, account_db_id ); - if( visibility != null ) intent.putExtra( KEY_VISIBILITY, visibility ); + if( reply_status != null ){ + intent.putExtra( KEY_REPLY_STATUS, reply_status.json.toString() ); + } context.startActivity( intent ); } @@ -94,10 +105,13 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { case R.id.btnPost: performPost(); break; + + case R.id.btnRemoveReply: + removeReply(); + break; } } - static final int REQUEST_CODE_ATTACHMENT = 1; @Override @@ -143,11 +157,9 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } } - String sv = savedInstanceState.getString( KEY_VISIBILITY ); - if( TextUtils.isEmpty( sv ) ) sv = account.visibility; - this.visibility = sv; + this.visibility = savedInstanceState.getString( KEY_VISIBILITY ); - sv = savedInstanceState.getString( KEY_ATTACHMENT_LIST ); + String sv = savedInstanceState.getString( KEY_ATTACHMENT_LIST ); if( ! TextUtils.isEmpty( sv ) ){ try{ attachment_list.clear(); @@ -168,6 +180,10 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { ex.printStackTrace(); } } + + this.in_reply_to_id = savedInstanceState.getLong( KEY_IN_REPLY_TO_ID, - 1L ); + this.in_reply_to_text = savedInstanceState.getString( KEY_IN_REPLY_TO_TEXT ); + this.in_reply_to_image = savedInstanceState.getString( KEY_IN_REPLY_TO_IMAGE ); }else{ Intent intent = getIntent(); long account_db_id = intent.getLongExtra( KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_ID ); @@ -181,9 +197,75 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } } - String sv = intent.getStringExtra( KEY_VISIBILITY ); - if( TextUtils.isEmpty( sv ) ) sv = account.visibility; - this.visibility = sv; + String sv = intent.getStringExtra( KEY_REPLY_STATUS ); + if( sv != null ){ + try{ + TootStatus repley_status = TootStatus.parse( log, new JSONObject( sv ) ); + + // CW をリプライ元に合わせる + if( ! TextUtils.isEmpty( repley_status.spoiler_text ) ){ + cbContentWarning.setChecked( true ); + etContentWarning.setText( repley_status.spoiler_text ); + } + + // mention を自動設定する + String acct_me = account.getFullAcct( account ); + ArrayList< String > mention_list = new ArrayList<>(); + mention_list.add( account.getFullAcct( repley_status.account ) ); + if( repley_status.mentions != null ){ + for( TootMention mention : repley_status.mentions ){ + sv = account.getFullAcct( mention.acct ); + if( !sv.equals( acct_me ) && ! mention_list.contains( sv ) ){ + mention_list.add( sv ); + } + } + } + StringBuilder sb = new StringBuilder(); + for( String acct : mention_list ){ + if( sb.length() > 0 ) sb.append( ' ' ); + sb.append( acct ); + } + if( sb.length() > 0 ){ + sb.append( ' ' ); + etContent.setText( sb.toString() ); + etContent.setSelection( sb.length() ); + } + + // リプライ表示をつける + in_reply_to_id = repley_status.id; + in_reply_to_text = repley_status.content; + in_reply_to_image = repley_status.account.avatar_static; + + // 公開範囲 + try{ + // 比較する前にデフォルトの公開範囲を計算する + if( TextUtils.isEmpty( visibility ) ){ + visibility = account.visibility; + if( TextUtils.isEmpty( visibility ) ){ + visibility = TootStatus.VISIBILITY_PUBLIC; + } + } + + // デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める + int i = TootStatus.compareVisibility( this.visibility, repley_status.visibility ); + if( i > 0 ){ // より大きい=>より公開範囲が広い + this.visibility = repley_status.visibility; + } + }catch( Throwable ex ){ + ex.printStackTrace(); + } + + }catch( Throwable ex ){ + ex.printStackTrace(); + } + } + } + + if( TextUtils.isEmpty( visibility ) ){ + visibility = account.visibility; + if( TextUtils.isEmpty( visibility ) ){ + visibility = TootStatus.VISIBILITY_PUBLIC; + } } if( this.account == null ){ @@ -194,6 +276,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { showMediaAttachment(); updateVisibility(); updateTextCount(); + showReplyTo(); } @Override @@ -214,6 +297,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } outState.putString( KEY_ATTACHMENT_LIST, array.toString() ); } + + outState.putLong( KEY_IN_REPLY_TO_ID, in_reply_to_id ); + outState.putString( KEY_IN_REPLY_TO_TEXT, in_reply_to_text ); + outState.putString( KEY_IN_REPLY_TO_IMAGE, in_reply_to_image ); + } Button btnAccount; @@ -230,8 +318,14 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { EditText etContentWarning; EditText etContent; TextView tvCharCount; + ArrayList< SavedAccount > account_list; + View llReply; + TextView tvReplyTo; + View btnRemoveReply; + NetworkImageView ivReply; + private void initUI(){ setContentView( R.layout.act_post ); @@ -250,6 +344,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { etContent = (EditText) findViewById( R.id.etContent ); tvCharCount = (TextView) findViewById( R.id.tvCharCount ); + llReply = findViewById( R.id.llReply ); + tvReplyTo = (TextView) findViewById( R.id.tvReplyTo ); + btnRemoveReply = findViewById( R.id.btnRemoveReply ); + ivReply= (NetworkImageView) findViewById( R.id.ivReply ); + account_list = SavedAccount.loadAccountList( log ); Collections.sort( account_list, new Comparator< SavedAccount >() { @Override @@ -262,6 +361,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { btnVisibility.setOnClickListener( this ); btnAttachment.setOnClickListener( this ); btnPost.setOnClickListener( this ); + btnRemoveReply.setOnClickListener( this ); ivMedia1.setOnClickListener( this ); ivMedia2.setOnClickListener( this ); ivMedia3.setOnClickListener( this ); @@ -312,12 +412,22 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } private void performAccountChooser(){ - // TODO: mention の状況によっては別サーバを選べないかもしれない - // TODO: 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない + if( ! attachment_list.isEmpty() ){ + // 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない + Utils.showToast( this,false,R.string.cant_change_account_when_attachiment_specified ); + } final ArrayList< SavedAccount > tmp_account_list = new ArrayList<>(); - tmp_account_list.addAll( account_list ); + if( in_reply_to_id != -1L ){ + // リプライは数値IDなのでサーバが同じじゃないと選択できない + for( SavedAccount a : account_list ){ + if( !a.host.equals( account.host ) ) continue; + tmp_account_list.add(a); + } + }else{ + 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; @@ -371,11 +481,11 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { iv.setVisibility( View.VISIBLE ); PostAttachment a = attachment_list.get( idx ); if( a.status == ATTACHMENT_UPLOADING ){ - iv.setImageDrawable( ContextCompat.getDrawable(this,R.drawable.ic_loading )); + 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 )); + iv.setImageDrawable( ContextCompat.getDrawable( this, R.drawable.ic_unknown ) ); } } } @@ -535,8 +645,8 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { attachment_list.remove( pa ); }else{ String sv = etContent.getText().toString(); - sv = sv + pa.attachment.text_url+" "; - etContent.setText(sv); + sv = sv + pa.attachment.text_url + " "; + etContent.setText( sv ); } showMediaAttachment(); @@ -575,10 +685,10 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { ////////////////////////////////////////////////////////////////////// // visibility - String visibility = TootStatus.VISIBILITY_PUBLIC; + String visibility; private void updateVisibility(){ - btnVisibility.setImageResource( Styler.getVisibilityIcon(visibility) ); + btnVisibility.setImageResource( Styler.getVisibilityIcon( visibility ) ); } private void performVisibility(){ @@ -626,12 +736,12 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { private void performPost(){ final String content = etContent.getText().toString().trim(); - if(TextUtils.isEmpty( content ) ){ - Utils.showToast( this,true,R.string.post_error_contents_empty ); + if( TextUtils.isEmpty( content ) ){ + Utils.showToast( this, true, R.string.post_error_contents_empty ); return; } final String spoiler_text; - if( !cbContentWarning.isChecked() ){ + if( ! cbContentWarning.isChecked() ){ spoiler_text = null; }else{ spoiler_text = etContentWarning.getText().toString().trim(); @@ -641,29 +751,33 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } } + final StringBuilder sb = new StringBuilder(); - final StringBuilder sb = new StringBuilder( ); - - sb.append("status="); - sb.append(Uri.encode( content )); - - sb.append("&visibility="); - sb.append(Uri.encode( visibility )); + sb.append( "status=" ); + sb.append( Uri.encode( content ) ); + + sb.append( "&visibility=" ); + sb.append( Uri.encode( visibility ) ); if( cbNSFW.isChecked() ){ - sb.append("&sensitive=1"); + sb.append( "&sensitive=1" ); } + if( spoiler_text != null ){ - sb.append("&spoiler_text="); - sb.append(Uri.encode( spoiler_text )); + sb.append( "&spoiler_text=" ); + sb.append( Uri.encode( spoiler_text ) ); } - for(PostAttachment pa : attachment_list){ + + if( in_reply_to_id != - 1L ){ + sb.append( "&in_reply_to_id=" ); + sb.append( Long.toString( in_reply_to_id ) ); + } + + for( PostAttachment pa : attachment_list ){ if( pa.attachment != null ){ - sb.append("&media_ids[]="+pa.attachment.id); + 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 ); @@ -671,7 +785,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { final SavedAccount target_account = account; TootStatus status; - + @Override protected TootApiResult doInBackground( Void... params ){ TootApiClient client = new TootApiClient( ActPost.this, new TootApiClient.Callback() { @@ -690,21 +804,21 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { } ); } } ); - + client.setAccount( target_account ); - + Request.Builder request_builder = new Request.Builder() .post( RequestBody.create( - TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED - ,sb.toString() - )); + 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 ); + status = TootStatus.parse( log, result.object ); } return result; - + } @Override @@ -739,5 +853,26 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener { AsyncTaskCompat.executeParallel( task ); } + ///////////////////////////////////////////////// + long in_reply_to_id = - 1L; + String in_reply_to_text; + String in_reply_to_image; + + void showReplyTo(){ + if( in_reply_to_id == - 1L ){ + llReply.setVisibility( View.GONE ); + }else{ + llReply.setVisibility( View.VISIBLE ); + tvReplyTo.setText( HTMLDecoder.decodeHTML( in_reply_to_text ) ); + ivReply.setImageUrl( in_reply_to_image,App1.getImageLoader() ); + } + } + + private void removeReply(){ + in_reply_to_id = - 1L; + in_reply_to_text = null; + in_reply_to_image = null; + showReplyTo(); + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.java b/app/src/main/java/jp/juggler/subwaytooter/App1.java index 66245bd7..2275faf0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.java +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.java @@ -31,7 +31,6 @@ public class App1 extends Application { super.onCreate(); CalligraphyConfig.initDefault(new CalligraphyConfig.Builder() - .setDefaultFontPath("NotoSansCJKjp-Regular.otf") .setFontAttrId(R.attr.fontPath) .build() ); @@ -40,14 +39,11 @@ public class App1 extends Application { 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() ); } + if( image_loader == null ){ image_loader = new MyImageLoader( Volley.newRequestQueue( getApplicationContext() ) @@ -156,5 +152,4 @@ public class App1 extends Application { public static final OkHttpClient ok_http_client = new OkHttpClient(); public static Typeface typeface_emoji ; - public static Typeface typeface_normal ; } diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.java b/app/src/main/java/jp/juggler/subwaytooter/Column.java index 39e4d1e4..7b78ba17 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.java +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.java @@ -2,23 +2,32 @@ package jp.juggler.subwaytooter; import android.os.AsyncTask; import android.support.v4.os.AsyncTaskCompat; +import android.text.TextUtils; +import android.util.SparseLongArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; 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.TootContext; +import jp.juggler.subwaytooter.api.entity.TootId; import jp.juggler.subwaytooter.api.entity.TootNotification; import jp.juggler.subwaytooter.api.entity.TootReport; 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.Headers; public class Column { static final LogCategory log = new LogCategory( "Column" ); @@ -26,11 +35,17 @@ public class Column { static final String KEY_ACCOUNT_ROW_ID = "account_id"; static final String KEY_TYPE = "type"; static final String KEY_WHO_ID = "who_id"; + static final String KEY_STATUS_ID = "status_id"; + + static final String KEY_COLUMN_ACCESS = "column_access"; + static final String KEY_COLUMN_NAME = "column_name"; + static final String KEY_OLD_INDEX = "old_index"; final ActMain activity; final SavedAccount access_info; final int type; final long who_id; + long status_id; static final int TYPE_TL_HOME = 1; static final int TYPE_TL_LOCAL = 2; @@ -39,6 +54,7 @@ public class Column { static final int TYPE_TL_FAVOURITES = 5; static final int TYPE_TL_REPORTS = 6; static final int TYPE_TL_NOTIFICATIONS = 7; + static final int TYPE_TL_CONVERSATION = 8; public Column( ActMain activity, SavedAccount access_info, int type ){ this( activity, access_info, type, access_info.id ); @@ -49,6 +65,11 @@ public class Column { this.access_info = access_info; this.type = type; this.who_id = who_id; + if( type == TYPE_TL_CONVERSATION ){ + if( params==null || params.length < 1 ) throw new IndexOutOfBoundsException( "TYPE_TL_CONVERSATION requires status_id as Long" ); + if( !( params[0] instanceof Long ) )throw new IllegalArgumentException( "TYPE_TL_CONVERSATION status_id is not Long" ); + status_id = (Long) params[0]; + } startLoading(); } @@ -58,6 +79,7 @@ public class Column { if( access_info == null ) throw new RuntimeException( "missing account" ); this.type = src.optInt( KEY_TYPE ); this.who_id = src.optLong( KEY_WHO_ID ); + this.status_id = src.optLong( KEY_STATUS_ID ); startLoading(); } @@ -67,10 +89,16 @@ public class Column { is_dispose.set( true ); } - public void encodeJSON( JSONObject item ) throws JSONException{ - item.put( KEY_ACCOUNT_ROW_ID , access_info.db_id ); + public void encodeJSON( JSONObject item, int old_index ) throws JSONException{ + item.put( KEY_ACCOUNT_ROW_ID, access_info.db_id ); item.put( KEY_TYPE, type ); item.put( KEY_WHO_ID, who_id ); + item.put( KEY_STATUS_ID,status_id); + + // 以下は保存には必要ないが、カラムリスト画面で使う + item.put( KEY_COLUMN_ACCESS, access_info.user ); + item.put( KEY_COLUMN_NAME, getColumnName() ); + item.put( KEY_OLD_INDEX, old_index ); } public String getColumnName(){ @@ -97,15 +125,17 @@ public class Column { case TYPE_TL_NOTIFICATIONS: return activity.getString( R.string.notifications ); - + case TYPE_TL_CONVERSATION: + return activity.getString( R.string.conversation_around,status_id ); } } - public interface StatusEntryCallback{ - void onIterate(TootStatus status); + public interface StatusEntryCallback { + void onIterate( TootStatus status ); } - public void findStatus( SavedAccount target_account,long target_status_id ,StatusEntryCallback callback){ + // ブーストやお気に入りの更新に使う。ステータスを列挙する。 + 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 ); @@ -113,7 +143,7 @@ public class Column { callback.onIterate( status ); } TootStatus reblog = status.reblog; - if( reblog!= null ){ + if( reblog != null ){ if( target_status_id == reblog.id ){ callback.onIterate( status ); } @@ -157,12 +187,24 @@ public class Column { AsyncTask< Void, Void, TootApiResult > last_task; void cancelLastTask(){ - if( last_task != null ) last_task.cancel( true ); + if( last_task != null ){ + last_task.cancel( true ); + last_task = null; + // + bInitialLoading = false; + bRefreshLoading = false; + mInitialLoadingError = activity.getString( R.string.cancelled ); + // + } } - boolean is_loading = false; + boolean bInitialLoading; + boolean bRefreshLoading; + + String mInitialLoadingError; + String mRefreshLoadingError; + String task_progress; - String error = null; final TootStatus.List status_list = new TootStatus.List(); final TootReport.List report_list = new TootReport.List(); @@ -174,12 +216,23 @@ public class Column { startLoading(); } + static final String PATH_TL_HOME = "/api/v1/timelines/home?limit=80"; + static final String PATH_TL_LOCAL = "/api/v1/timelines/public?limit=80&local=1"; + static final String PATH_TL_FEDERATE = "/api/v1/timelines/public?limit=80"; + static final String PATH_TL_FAVOURITES = "/api/v1/favourites?limit=80"; + static final String PATH_TL_REPORTS = "/api/v1/reports?limit=80"; + static final String PATH_TL_NOTIFICATIONS = "/api/v1/notifications?limit=80"; + void startLoading(){ - error = null; - is_loading = true; - fireVisualCallback(); cancelLastTask(); + mInitialLoadingError = null; + bInitialLoading = true; + max_id = null; + since_id = null; + + fireVisualCallback(); + AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() { TootStatus.List tmp_list_status; @@ -188,6 +241,7 @@ public class Column { TootApiResult parseStatuses( TootApiResult result ){ if( result != null ){ + saveRange( result, true, true ); tmp_list_status = TootStatus.parseList( log, result.array ); } return result; @@ -195,6 +249,7 @@ public class Column { TootApiResult parseAccount( TootApiResult result ){ if( result != null ){ + saveRange( result, true, true ); who_account = TootAccount.parse( log, result.object ); } return result; @@ -202,6 +257,7 @@ public class Column { TootApiResult parseReports( TootApiResult result ){ if( result != null ){ + saveRange( result, true, true ); tmp_list_report = TootReport.parseList( log, result.array ); } return result; @@ -209,6 +265,7 @@ public class Column { TootApiResult parseNotifications( TootApiResult result ){ if( result != null ){ + saveRange( result, true, true ); tmp_list_notification = TootNotification.parseList( log, result.array ); } return result; @@ -240,30 +297,45 @@ public class Column { switch( type ){ default: case TYPE_TL_HOME: - return parseStatuses( client.request( "/api/v1/timelines/home" ) ); + return parseStatuses( client.request( PATH_TL_HOME ) ); case TYPE_TL_LOCAL: - return parseStatuses( client.request( "/api/v1/timelines/public?local=1" ) ); + return parseStatuses( client.request( PATH_TL_LOCAL ) ); case TYPE_TL_FEDERATE: - return parseStatuses( client.request( "/api/v1/timelines/public" ) ); + return parseStatuses( client.request( PATH_TL_FEDERATE ) ); case TYPE_TL_STATUSES: if( who_account == null ){ - parseAccount( client.request( "/api/v1/accounts/" + who_id ) ); + parseAccount( client.request( "/api/v1/accounts/" + who_id + "?limit=80" ) ); client.callback.publishApiProgress( "" ); } - return parseStatuses( client.request( "/api/v1/accounts/" + who_id + "/statuses" ) ); + return parseStatuses( client.request( "/api/v1/accounts/" + who_id + "/statuses?limit=80" ) ); case TYPE_TL_FAVOURITES: - return parseStatuses( client.request( "/api/v1/favourites" ) ); + return parseStatuses( client.request( PATH_TL_FAVOURITES ) ); case TYPE_TL_REPORTS: - return parseReports( client.request( "/api/v1/reports" ) ); + return parseReports( client.request( PATH_TL_REPORTS ) ); case TYPE_TL_NOTIFICATIONS: - return parseNotifications( client.request( "/api/v1/notifications" ) ); + return parseNotifications( client.request( PATH_TL_NOTIFICATIONS ) ); + + case TYPE_TL_CONVERSATION: + TootApiResult result = client.request( "/api/v1/statuses/"+status_id ); + if( result== null || result.object == null ) return result; + TootStatus target_status = TootStatus.parse( log,result.object ); + target_status.conversation_main = true; + // + result = client.request( "/api/v1/statuses/"+status_id+"/context" ); + if( result== null || result.object == null ) return result; + TootContext context = TootContext.parse( log,result.object ); + tmp_list_status = new TootStatus.List(); + if( context.ancestors != null ) tmp_list_status.addAll( context.ancestors); + tmp_list_status.add(target_status); + if( context.descendants != null ) tmp_list_status.addAll( context.descendants); + return result; } } @@ -274,11 +346,16 @@ public class Column { @Override protected void onPostExecute( TootApiResult result ){ - is_loading = false; - if( result == null ){ - Column.this.error = activity.getString( R.string.cancelled ); - }else if( result.error != null ){ - Column.this.error = result.error; + + if( isCancelled() || result == null ){ + return; + } + + bInitialLoading = false; + last_task = null; + + if( result.error != null ){ + Column.this.mInitialLoadingError = result.error; }else{ switch( type ){ default: @@ -287,27 +364,16 @@ public class Column { case TYPE_TL_FEDERATE: case TYPE_TL_STATUSES: case TYPE_TL_FAVOURITES: - if( tmp_list_status != null ){ - for( int i = tmp_list_status.size() - 1 ; i >= 0 ; -- i ){ - status_list.add( 0, tmp_list_status.get( i ) ); - } - } + case TYPE_TL_CONVERSATION: + initList( status_list, tmp_list_status ); break; case TYPE_TL_REPORTS: - if( tmp_list_report != null ){ - for( int i = tmp_list_report.size() - 1 ; i >= 0 ; -- i ){ - report_list.add( 0, tmp_list_report.get( i ) ); - } - } + initList( report_list, tmp_list_report ); break; case TYPE_TL_NOTIFICATIONS: - if( tmp_list_notification != null ){ - for( int i = tmp_list_notification.size() - 1 ; i >= 0 ; -- i ){ - notification_list.add( 0, tmp_list_notification.get( i ) ); - } - } + initList( notification_list, tmp_list_notification ); break; } @@ -318,4 +384,224 @@ public class Column { AsyncTaskCompat.executeParallel( task ); } + + static final Pattern reMaxId = Pattern.compile( "&max_id=(\\d+)" ); // より古いデータの取得に使う + static final Pattern reSinceId = Pattern.compile( "&since_id=(\\d+)" ); // より新しいデータの取得に使う + + String max_id; + String since_id; + + private void saveRange( TootApiResult result, boolean bBottom, boolean bTop ){ + // Link: ; rel="next", + // ; rel="prev" + + if( result.response != null ){ + String sv = result.response.header( "Link" ); + if( ! TextUtils.isEmpty( sv ) ){ + if( bBottom ){ + Matcher m = reMaxId.matcher( sv ); + if( m.find() ){ + max_id = m.group( 1 ); + log.d( "col=%s,max_id=%s", this.hashCode(), max_id ); + } + } + if( bTop ){ + Matcher m = reSinceId.matcher( sv ); + if( m.find() ){ + since_id = m.group( 1 ); + log.d( "col=%s,since_id=%s", this.hashCode(), since_id ); + } + } + } + } + } + + String addRange( boolean bBottom, String path ){ + char delm = ( - 1 != path.indexOf( '?' ) ? '&' : '?' ); + if( bBottom ){ + if( max_id != null ) return path + delm + "max_id=" + max_id; + }else{ + if( since_id != null ) return path + delm + "since_id=" + since_id; + } + return path; + } + + < T extends TootId > void initList( ArrayList< T > dst, ArrayList< T > src ){ + if( src == null ) return; + dst.clear(); + dst.addAll( src ); + } + + < T extends TootId > void mergeList( ArrayList< T > dst, ArrayList< T > src, boolean bBottom ){ + // 古いリストにある要素の集合 + HashSet< Long > id_set = new HashSet(); + for( T t : dst ){ + id_set.add( t.id ); + } + ArrayList< T > tmp_list = new ArrayList<>( src.size() ); + for( T t : src ){ + if( id_set.contains( t.id ) ) continue; + tmp_list.add( t ); + } + + if( ! bBottom ){ + tmp_list.addAll( dst ); + dst.clear(); + dst.addAll( tmp_list ); + }else{ + dst.addAll( tmp_list ); + } + } + + public boolean startRefresh( final boolean bBottom ){ + if( last_task != null ){ + log.d( "busy" ); + return false; + }else if( bBottom && max_id == null ){ + log.d( "startRefresh failed. missing max_id" ); + return false; + }else if( !bBottom && since_id == null ){ + log.d( "startRefresh failed. missing since_id" ); + return false; + } + bRefreshLoading = true; + mRefreshLoadingError = null; + + AsyncTask< Void, Void, TootApiResult > task = this.last_task = new AsyncTask< Void, Void, TootApiResult >() { + + TootStatus.List tmp_list_status; + TootReport.List tmp_list_report; + TootNotification.List tmp_list_notification; + + TootApiResult parseStatuses( TootApiResult result ){ + if( result != null ){ + saveRange( result, bBottom, ! bBottom ); + tmp_list_status = TootStatus.parseList( log, result.array ); + } + return result; + } + + TootApiResult parseAccount( TootApiResult result ){ + if( result != null ){ + saveRange( result, bBottom, ! bBottom ); + who_account = TootAccount.parse( log, result.object ); + } + return result; + } + + TootApiResult parseReports( TootApiResult result ){ + if( result != null ){ + saveRange( result, bBottom, ! bBottom ); + tmp_list_report = TootReport.parseList( log, result.array ); + } + return result; + } + + TootApiResult parseNotifications( TootApiResult result ){ + if( result != null ){ + saveRange( result, bBottom, ! bBottom ); + tmp_list_notification = TootNotification.parseList( log, result.array ); + } + return result; + } + + @Override + protected TootApiResult doInBackground( Void... params ){ + TootApiClient client = new TootApiClient( activity, new TootApiClient.Callback() { + @Override + public boolean isApiCancelled(){ + return isCancelled() || is_dispose.get(); + } + + @Override + public void publishApiProgress( final String s ){ + Utils.runOnMainThread( new Runnable() { + @Override + public void run(){ + if( isCancelled() ) return; + task_progress = s; + fireVisualCallback(); + } + } ); + } + } ); + + client.setAccount( access_info ); + + switch( type ){ + default: + case TYPE_TL_HOME: + return parseStatuses( client.request( addRange( bBottom, PATH_TL_HOME ) ) ); + + case TYPE_TL_LOCAL: + return parseStatuses( client.request( addRange( bBottom, PATH_TL_LOCAL ) ) ); + + case TYPE_TL_FEDERATE: + return parseStatuses( client.request( addRange( bBottom, PATH_TL_FEDERATE ) ) ); + + case TYPE_TL_STATUSES: + if( who_account == null ){ + parseAccount( client.request( "/api/v1/accounts/" + who_id + "?limit=80" ) ); + client.callback.publishApiProgress( "" ); + } + + return parseStatuses( client.request( addRange( bBottom, "/api/v1/accounts/" + who_id + "/statuses?limit=80" ) ) ); + + case TYPE_TL_FAVOURITES: + return parseStatuses( client.request( addRange( bBottom, PATH_TL_FAVOURITES ) ) ); + + case TYPE_TL_REPORTS: + return parseReports( client.request( addRange( bBottom, PATH_TL_REPORTS ) ) ); + + case TYPE_TL_NOTIFICATIONS: + return parseNotifications( client.request( addRange( bBottom, PATH_TL_NOTIFICATIONS ) ) ); + + } + } + + @Override + protected void onCancelled( TootApiResult result ){ + onPostExecute( null ); + } + + @Override + protected void onPostExecute( TootApiResult result ){ + + if( isCancelled() || result == null ){ + return; + } + last_task = null; + bRefreshLoading = false; + + if( result.error != null ){ + Column.this.mRefreshLoadingError = result.error; + }else{ + switch( type ){ + default: + case TYPE_TL_HOME: + case TYPE_TL_LOCAL: + case TYPE_TL_FEDERATE: + case TYPE_TL_STATUSES: + case TYPE_TL_FAVOURITES: + mergeList( status_list, tmp_list_status, bBottom ); + break; + + case TYPE_TL_REPORTS: + mergeList( report_list, tmp_list_report, bBottom ); + break; + + case TYPE_TL_NOTIFICATIONS: + mergeList( notification_list, tmp_list_notification, bBottom ); + break; + } + + } + fireVisualCallback(); + } + }; + + AsyncTaskCompat.executeParallel( task ); + return true; + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java index bdcc20a0..14023ff4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnPagerAdapter.java @@ -5,11 +5,13 @@ import android.app.Activity; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.SparseArray; +import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; +import java.util.HashSet; public class ColumnPagerAdapter extends PagerAdapter{ @@ -47,10 +49,29 @@ public class ColumnPagerAdapter extends PagerAdapter{ pager.setAdapter( null ); column_list.remove( idx_column ); pager.setAdapter( this ); - - } + public void setOrder( ViewPager pager,ArrayList< Integer > order ){ + pager.setAdapter( null ); + + ArrayList tmp_list = new ArrayList<>(); + HashSet used_set = new HashSet<>( ); + + for(Integer i : order){ + used_set.add( i ); + tmp_list.add( column_list.get(i)); + } + for(int i=0,ie=column_list.size();i= 0 && idx < column_list.size() ) return column_list.get( idx ); @@ -84,7 +105,7 @@ public class ColumnPagerAdapter extends PagerAdapter{ // holder_list.put( page_idx, holder ); // - holder.onPageCreate( root ); + holder.onPageCreate( root ,page_idx,column_list.size()); return root; } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java index bdb12d7d..9830b07d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.java @@ -7,6 +7,7 @@ import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageButton; @@ -15,6 +16,8 @@ import android.widget.ListView; import android.widget.TextView; import com.android.volley.toolbox.NetworkImageView; +import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayout; +import com.omadahealth.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -27,11 +30,11 @@ 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.Emojione; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; -public class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback { +public class ColumnViewHolder implements View.OnClickListener, Column.VisualCallback, SwipyRefreshLayout.OnRefreshListener { static final LogCategory log = new LogCategory( "ColumnViewHolder" ); public final AtomicBoolean is_destroyed = new AtomicBoolean( false ); @@ -56,10 +59,14 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall TextView tvColumnName; StatusListAdapter status_adapter; HeaderViewHolder vh_header; + SwipyRefreshLayout swipyRefreshLayout; - void onPageCreate( View root ){ + void onPageCreate( View root, int page_idx, int page_count ){ log.d( "onPageCreate:%s", column.getColumnName() ); + ( (TextView) root.findViewById( R.id.tvColumnIndex ) ) + .setText( activity.getString( R.string.column_index, page_idx + 1, page_count ) ); + tvColumnName = (TextView) root.findViewById( R.id.tvColumnName ); tvColumnContext = (TextView) root.findViewById( R.id.tvColumnContext ); @@ -70,12 +77,20 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall listView = (ListView) root.findViewById( R.id.listView ); status_adapter = new StatusListAdapter(); listView.setAdapter( status_adapter ); + listView.setOnItemClickListener( status_adapter ); + + this.swipyRefreshLayout = (SwipyRefreshLayout) root.findViewById( R.id.swipyRefreshLayout ); + swipyRefreshLayout.setOnRefreshListener( this ); if( column.type == Column.TYPE_TL_STATUSES ){ vh_header = new HeaderViewHolder( activity, listView ); listView.addHeaderView( vh_header.viewRoot ); } + if( column.type == Column.TYPE_TL_CONVERSATION ){ + swipyRefreshLayout.setEnabled( false ); + } + // column.addVisualListener( this ); @@ -102,7 +117,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall void showError( String message ){ tvLoading.setVisibility( View.VISIBLE ); - listView.setVisibility( View.GONE ); + swipyRefreshLayout.setVisibility( View.GONE ); tvLoading.setText( message ); } @@ -121,13 +136,21 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall vh_header.bind( activity, column.access_info, column.who_account ); } - if( column.is_loading ){ + if( column.bInitialLoading ){ String message = column.task_progress; if( message == null ) message = "loading?"; showError( message ); return; } + if( ! column.bRefreshLoading ){ + swipyRefreshLayout.setRefreshing( false ); + if( column.mRefreshLoadingError != null ){ + Utils.showToast( activity, true, column.mRefreshLoadingError ); + column.mRefreshLoadingError = null; + } + } + switch( column.type ){ default: case Column.TYPE_TL_HOME: @@ -139,7 +162,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall showError( activity.getString( R.string.list_empty ) ); }else{ tvLoading.setVisibility( View.GONE ); - listView.setVisibility( View.VISIBLE ); + swipyRefreshLayout.setVisibility( View.VISIBLE ); status_adapter.set( column.status_list ); } break; @@ -148,7 +171,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall showError( activity.getString( R.string.list_empty ) ); }else{ tvLoading.setVisibility( View.GONE ); - listView.setVisibility( View.VISIBLE ); + swipyRefreshLayout.setVisibility( View.VISIBLE ); status_adapter.set( column.report_list ); } break; @@ -157,16 +180,23 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall showError( activity.getString( R.string.list_empty ) ); }else{ tvLoading.setVisibility( View.GONE ); - listView.setVisibility( View.VISIBLE ); + swipyRefreshLayout.setVisibility( View.VISIBLE ); status_adapter.set( column.notification_list ); } break; } } + @Override + public void onRefresh( SwipyRefreshLayoutDirection direction ){ + if( ! column.startRefresh( direction == SwipyRefreshLayoutDirection.BOTTOM ) ){ + swipyRefreshLayout.setRefreshing( false ); + } + } + /////////////////////////////////////////////////////////////////// - class StatusListAdapter extends BaseAdapter { + class StatusListAdapter extends BaseAdapter implements AdapterView.OnItemClickListener { final ArrayList< Object > status_list = new ArrayList<>(); public void set( TootStatus.List src ){ @@ -219,6 +249,98 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall return view; } + @Override + public void onItemClick( AdapterView< ? > parent, View view, int position, long id ){ + Object o = view.getTag(); + if( o instanceof StatusViewHolder ){ + ( (StatusViewHolder) o ).onItemClick(); + } + } + } + + class HeaderViewHolder implements View.OnClickListener { + final View viewRoot; + final NetworkImageView ivBackground; + final TextView tvCreated; + final NetworkImageView ivAvatar; + final TextView tvDisplayName; + final TextView tvAcct; + final Button btnFollowing; + final Button btnFollowers; + final Button btnStatusCount; + final TextView tvNote; + TootAccount who; + + public HeaderViewHolder( final ActMain activity, ListView parent ){ + viewRoot = activity.getLayoutInflater().inflate( R.layout.lv_list_header, parent, false ); + this.ivBackground = (NetworkImageView) viewRoot.findViewById( R.id.ivBackground ); + this.tvCreated = (TextView) viewRoot.findViewById( R.id.tvCreated ); + this.ivAvatar = (NetworkImageView) viewRoot.findViewById( R.id.ivAvatar ); + this.tvDisplayName = (TextView) viewRoot.findViewById( R.id.tvDisplayName ); + this.tvAcct = (TextView) viewRoot.findViewById( R.id.tvAcct ); + this.btnFollowing = (Button) viewRoot.findViewById( R.id.btnFollowing ); + this.btnFollowers = (Button) viewRoot.findViewById( R.id.btnFollowers ); + this.btnStatusCount = (Button) viewRoot.findViewById( R.id.btnStatusCount ); + this.tvNote = (TextView) viewRoot.findViewById( R.id.tvNote ); + + ivBackground.setOnClickListener( this ); + btnFollowing.setOnClickListener( this ); + btnFollowers.setOnClickListener( this ); + btnStatusCount.setOnClickListener( this ); + + tvNote.setMovementMethod( LinkMovementMethod.getInstance() ); + } + + public void bind( ActMain activity, SavedAccount access_info, TootAccount who ){ + this.who = who; + if( who == null ){ + tvCreated.setText( "" ); + ivBackground.setImageDrawable( null ); + ivAvatar.setImageDrawable( null ); + tvDisplayName.setText( "" ); + tvAcct.setText( "" ); + tvNote.setText( "" ); + btnStatusCount.setText( activity.getString( R.string.statuses ) + "\n" + "?" ); + btnFollowing.setText( activity.getString( R.string.following ) + "\n" + "?" ); + btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + "?" ); + }else{ + tvCreated.setText( TootStatus.formatTime( who.time_created_at ) ); + ivBackground.setImageUrl( who.header_static, App1.getImageLoader() ); + ivAvatar.setImageUrl( who.avatar_static, App1.getImageLoader() ); + tvDisplayName.setText( who.display_name ); + + String s = access_info.getFullAcct( who ); + if( who.locked ){ + s += " " + Emojione.map_name2unicode.get( "lock" ); + } + tvAcct.setText( Emojione.decodeEmoji( s ) ); + + tvNote.setText( who.note ); + btnStatusCount.setText( activity.getString( R.string.statuses ) + "\n" + who.statuses_count ); + btnFollowing.setText( activity.getString( R.string.following ) + "\n" + who.following_count ); + btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + who.followers_count ); + } + } + + @Override + public void onClick( View v ){ + switch( v.getId() ){ + case R.id.ivBackground: + if( who != null ){ + activity.openBrowser( who.url ); + } + break; + case R.id.btnFollowing: + Utils.showToast( activity, false, "not implemented" ); + break; + case R.id.btnFollowers: + Utils.showToast( activity, false, "not implemented" ); + break; + case R.id.btnStatusCount: + Utils.showToast( activity, false, "not implemented" ); + break; + } + } } class StatusViewHolder implements View.OnClickListener { @@ -244,8 +366,8 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall final Button btnContentWarning; final View llContents; - final TextView tvTags; - final TextView tvMentions; + final TextView tvTags; + final TextView tvMentions; final TextView tvContent; final View flMedia; @@ -262,14 +384,15 @@ 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; + View btnConversation; public StatusViewHolder( View view ){ + this.llBoosted = view.findViewById( R.id.llBoosted ); this.ivBoosted = (ImageView) view.findViewById( R.id.ivBoosted ); this.tvBoosted = (TextView) view.findViewById( R.id.tvBoosted ); @@ -279,7 +402,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.btnFollow = (ImageButton) view.findViewById( R.id.btnFollow ); this.llStatus = view.findViewById( R.id.llStatus ); @@ -296,6 +419,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall this.tvTags = (TextView) view.findViewById( R.id.tvTags ); this.tvMentions = (TextView) view.findViewById( R.id.tvMentions ); + this.btnConversation = view.findViewById( R.id.btnConversation ); this.btnReply = (ImageButton) view.findViewById( R.id.btnReply ); this.btnBoost = (Button) view.findViewById( R.id.btnBoost ); this.btnFavourite = (Button) view.findViewById( R.id.btnFavourite ); @@ -317,13 +441,14 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall ivMedia3.setOnClickListener( this ); ivMedia4.setOnClickListener( this ); + btnConversation.setOnClickListener( this ); btnReply.setOnClickListener( this ); btnBoost.setOnClickListener( this ); btnFavourite.setOnClickListener( this ); btnMore.setOnClickListener( this ); - + ivThumbnail.setOnClickListener( this ); - tvName.setOnClickListener( this ); + // ここを個別タップにすると邪魔すぎる tvName.setOnClickListener( this ); llBoosted.setOnClickListener( this ); llFollow.setOnClickListener( this ); btnFollow.setOnClickListener( this ); @@ -331,6 +456,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall tvContent.setMovementMethod( LinkMovementMethod.getInstance() ); tvTags.setMovementMethod( LinkMovementMethod.getInstance() ); tvMentions.setMovementMethod( LinkMovementMethod.getInstance() ); + } public void bind( ActMain activity, View view, Object item, SavedAccount access_info ){ @@ -346,21 +472,21 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall 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" + access_info.getFullAcct(account_boost ) + + "\n" + access_info.getFullAcct( account_boost ) ); - tvBoosted.setText( Utils.formatSpannable1( activity,R.string.display_name_favourited_by, account_boost.display_name)); - + 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" + access_info.getFullAcct(account_boost ) + + "\n" + access_info.getFullAcct( account_boost ) ); tvBoosted.setText( Utils.formatSpannable1( activity, R.string.display_name_boosted_by, account_boost.display_name ) ); account_boost = n.account; @@ -377,17 +503,17 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall account_follow = n.account; llFollow.setVisibility( View.VISIBLE ); ivFollow.setImageUrl( account_follow.avatar_static, App1.getImageLoader() ); - tvFollowerName.setText(account_follow.display_name ); + tvFollowerName.setText( account_follow.display_name ); tvFollowerAcct.setText( access_info.getFullAcct( account_follow ) ); }else if( TootNotification.TYPE_MENTION.equals( n.type ) ){ 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 ) + + "\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; @@ -414,8 +540,10 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall this.status = status; account_thumbnail = status.account; llStatus.setVisibility( View.VISIBLE ); - tvTime.setText( TootStatus.formatTime( status.time_created_at ) - + "\n" + account.getFullAcct( status.account ) + tvTime.setText( + status.id + + " " + TootStatus.formatTime( status.time_created_at ) + + "\n" + account.getFullAcct( status.account ) ); tvName.setText( status.account.display_name ); ivThumbnail.setImageUrl( status.account.avatar_static, App1.getImageLoader() ); @@ -425,14 +553,14 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall tvTags.setVisibility( View.GONE ); }else{ tvTags.setVisibility( View.VISIBLE ); - tvTags.setText( status.decoded_tags); + tvTags.setText( status.decoded_tags ); } if( status.decoded_mentions == null ){ tvMentions.setVisibility( View.GONE ); }else{ tvMentions.setVisibility( View.VISIBLE ); - tvMentions.setText( status.decoded_mentions); + tvMentions.setText( status.decoded_mentions ); } // Content warning @@ -463,14 +591,14 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall Drawable d; int color; - if( activity.isBusyBoost( account,status )){ + 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 ); - + }else{ color = ( status.reblogged ? 0xff0088ff : 0xff000000 ); d = ContextCompat.getDrawable( activity, R.drawable.btn_boost ).mutate(); @@ -481,7 +609,7 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall } - if( activity.isBusyFav( account,status )){ + if( activity.isBusyFav( account, status ) ){ color = 0xff000000; d = ContextCompat.getDrawable( activity, R.drawable.btn_refresh ).mutate(); d.setColorFilter( color, PorterDuff.Mode.SRC_ATOP ); @@ -546,27 +674,29 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall break; } + case R.id.btnConversation: + activity.performConversation( account, status ); + break; case R.id.btnReply: - activity.performReply( account,status); + activity.performReply( account, status ); break; case R.id.btnBoost: - activity.performBoost( account,status.reblog != null ? status.reblog : status ,false); + activity.performBoost( account, status, false ); break; case R.id.btnFavourite: - activity.performFavourite( account,status.reblog != null ? status.reblog : status); + activity.performFavourite( account, status ); break; case R.id.btnMore: - activity.performMore( account,status); + activity.performMore( account, status ); break; case R.id.ivThumbnail: - case R.id.tvName: - activity.performOpenUser(account,account_thumbnail); + activity.performOpenUser( account, account_thumbnail ); break; case R.id.llBoosted: - activity.performOpenUser(account,account_boost); + activity.performOpenUser( account, account_boost ); break; case R.id.llFollow: - activity.performOpenUser(account,account_follow); + activity.performOpenUser( account, account_follow ); break; } } @@ -584,86 +714,10 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall ex.printStackTrace(); } } - } - - - - class HeaderViewHolder implements View.OnClickListener { - final View viewRoot; - final NetworkImageView ivBackground; - final TextView tvCreated; - final NetworkImageView ivAvatar; - final TextView tvDisplayName; - final TextView tvAcct; - final Button btnFollowing; - final Button btnFollowers; - final Button btnStatusCount; - final TextView tvNote; - TootAccount who; - public HeaderViewHolder( final ActMain activity, ListView parent ){ - viewRoot = activity.getLayoutInflater().inflate( R.layout.lv_list_header, parent, false ); - this.ivBackground = (NetworkImageView) viewRoot.findViewById( R.id.ivBackground ); - this.tvCreated = (TextView) viewRoot.findViewById( R.id.tvCreated ); - this.ivAvatar = (NetworkImageView) viewRoot.findViewById( R.id.ivAvatar ); - this.tvDisplayName = (TextView) viewRoot.findViewById( R.id.tvDisplayName ); - this.tvAcct = (TextView) viewRoot.findViewById( R.id.tvAcct ); - this.btnFollowing = (Button) viewRoot.findViewById( R.id.btnFollowing ); - this.btnFollowers = (Button) viewRoot.findViewById( R.id.btnFollowers ); - this.btnStatusCount = (Button) viewRoot.findViewById( R.id.btnStatusCount ); - this.tvNote = (TextView) viewRoot.findViewById( R.id.tvNote ); - - ivBackground.setOnClickListener( this ); - btnFollowing.setOnClickListener( this ); - btnFollowers.setOnClickListener( this ); - btnStatusCount.setOnClickListener( this ); - - tvNote.setMovementMethod( LinkMovementMethod.getInstance() ); - } - - public void bind( ActMain activity, SavedAccount access_info, TootAccount who ){ - this.who = who; - if( who == null ){ - tvCreated.setText( "" ); - ivBackground.setImageDrawable( null ); - ivAvatar.setImageDrawable( null ); - tvDisplayName.setText( "" ); - tvAcct.setText( "" ); - tvNote.setText( "" ); - btnStatusCount.setText( activity.getString( R.string.statuses ) + "\n" + "?" ); - btnFollowing.setText( activity.getString( R.string.following ) + "\n" + "?" ); - btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + "?" ); - }else{ - tvCreated.setText( TootStatus.formatTime( who.time_created_at ) ); - ivBackground.setImageUrl( who.header_static, App1.getImageLoader() ); - ivAvatar.setImageUrl( who.avatar_static, App1.getImageLoader() ); - tvDisplayName.setText( who.display_name ); - tvAcct.setText( access_info.getFullAcct( who ) ); - tvNote.setText( who.note ); - btnStatusCount.setText( activity.getString( R.string.statuses ) + "\n" + who.statuses_count ); - btnFollowing.setText( activity.getString( R.string.following ) + "\n" + who.following_count ); - btnFollowers.setText( activity.getString( R.string.followers ) + "\n" + who.followers_count ); - } - } - - @Override - public void onClick( View v ){ - switch( v.getId() ){ - case R.id.ivBackground: - if( who != null ){ - activity.openBrowser( who.url ); - } - break; - case R.id.btnFollowing: - Utils.showToast( activity, false, "not implemented" ); - break; - case R.id.btnFollowers: - Utils.showToast( activity, false, "not implemented" ); - break; - case R.id.btnStatusCount: - Utils.showToast( activity, false, "not implemented" ); - break; - } + public void onItemClick(){ + activity.performConversation( account, status ); } } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.java b/app/src/main/java/jp/juggler/subwaytooter/Pref.java new file mode 100644 index 00000000..9123bd56 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.java @@ -0,0 +1,16 @@ +package jp.juggler.subwaytooter; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class Pref { + + public static SharedPreferences pref(Context context){ + return PreferenceManager.getDefaultSharedPreferences( context ); + } + + public static final String KEY_BACK_TO_COLUMN_LIST ="BackToColumnList"; + public static final String KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN ="DontConfirmBeforeCloseColumn"; + +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java index fa66c1d5..71d2ca00 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java @@ -69,8 +69,17 @@ public class TootApiClient { } public TootApiResult request( String path, Request.Builder request_builder ){ - - JSONObject client_info = null; + log.d("request: %s",path); + TootApiResult result = request_sub( path,request_builder ); + if( result.error != null ){ + log.d("error: %s",result.error); + } + return result; + } + + public TootApiResult request_sub( String path, Request.Builder request_builder ){ + + JSONObject client_info = null; JSONObject token_info = ( account == null ? null : account.token_info ); for( ; ; ){ @@ -84,7 +93,7 @@ public class TootApiClient { callback.publishApiProgress( context.getString( R.string.register_app_to_server, instance ) ); // OAuth2 クライアント登録 - String client_name = "jp.juggler.subwaytooter." + UUID.randomUUID().toString(); + String client_name = "SubwayTooter" ; // + UUID.randomUUID().toString(); Request request = new Request.Builder() .url( "https://" + instance + "/api/v1/apps" ) @@ -218,7 +227,7 @@ public class TootApiClient { 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 ); + return new TootApiResult( response,token_info, json, array ); }else{ JSONObject object = new JSONObject( json ); @@ -226,7 +235,7 @@ public class TootApiClient { if( ! TextUtils.isEmpty( error ) ){ return new TootApiResult( context.getString( R.string.api_error, error ) ); } - return new TootApiResult( token_info, json, object ); + return new TootApiResult( response,token_info, json, object ); } }catch( Throwable ex ){ ex.printStackTrace(); diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java index f4f2831f..4331eb78 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiResult.java @@ -3,26 +3,32 @@ package jp.juggler.subwaytooter.api; import org.json.JSONArray; import org.json.JSONObject; +import okhttp3.Response; + public class TootApiResult { public String error; public JSONObject object; public JSONArray array; public String json; public JSONObject token_info; + public Response response; + public TootApiResult( String error ){ this.error = error; } - public TootApiResult( JSONObject token_info,String json,JSONObject object ){ + public TootApiResult( Response response,JSONObject token_info,String json,JSONObject object ){ this.token_info = token_info; this.json = json; this.object = object; + this.response = response; } - public TootApiResult(JSONObject token_info, String json,JSONArray array ){ + public TootApiResult( Response response, JSONObject token_info, String json, JSONArray array ){ this.token_info = token_info; this.json = json; this.array = array; + this.response = response; } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootId.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootId.java new file mode 100644 index 00000000..a22dc740 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootId.java @@ -0,0 +1,10 @@ +package jp.juggler.subwaytooter.api.entity; + +/** + * Created by tateisu on 2017/04/23. + */ + +public class TootId { + + public long id; +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java index 5cde5efb..f5c1bcb3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootNotification.java @@ -8,10 +8,10 @@ import java.util.ArrayList; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; -public class TootNotification { +public class TootNotification extends TootId{ // The notification ID - public long id; + //TootId public long id; // One of: "mention", "reblog", "favourite", "follow" public String type; diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReport.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReport.java index 063b570a..073ed61a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReport.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReport.java @@ -8,10 +8,10 @@ import java.util.ArrayList; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; -public class TootReport { +public class TootReport extends TootId{ // The ID of the report - public long id; + //TootId public long id; // The action taken in response to the report public String action_taken; diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java index c65dd8da..21476fb3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.java @@ -17,7 +17,7 @@ import jp.juggler.subwaytooter.util.HTMLDecoder; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.util.Utils; -public class TootStatus { +public class TootStatus extends TootId{ public static class List extends ArrayList< TootStatus > { @@ -25,7 +25,7 @@ public class TootStatus { } // The ID of the status - public long id; + // TootId public long id; // A Fediverse-unique resource ID public String uri; @@ -94,12 +94,18 @@ public class TootStatus { public Spannable decoded_tags; public Spannable decoded_mentions; + public JSONObject json; + + public boolean conversation_main; + + public static TootStatus parse( LogCategory log, JSONObject src ){ if( src == null ) return null; try{ TootStatus status = new TootStatus(); + status.json = src; // log.d( "parse: %s", src.toString() ); status.id = src.optLong( "id" ); status.uri = Utils.optStringX( src, "uri" ); @@ -170,4 +176,25 @@ public class TootStatus { return date_format.format( new Date( t ) ); } + // 公開範囲を比較する + // 公開範囲が広い => 大きい + // aの方が小さい(狭い)ならマイナス + // aの方が大きい(狭い)ならプラス + // IndexOutOfBoundsException 公開範囲が想定外 + public static int compareVisibility( String a, String b ){ + int ia = compareVisibility_tmp(a); + int ib = compareVisibility_tmp(b); + if( ia < ib ) return -1; + if( ia > ib ) return 1; + return 0; + } + + private static int compareVisibility_tmp( String a ){ + if(TootStatus.VISIBILITY_DIRECT.equals( a ) ) return 0; + if(TootStatus.VISIBILITY_PRIVATE.equals( a ) ) return 1; + if(TootStatus.VISIBILITY_UNLISTED.equals( a ) ) return 2; + if(TootStatus.VISIBILITY_PUBLIC.equals( a ) ) return 3; + throw new IndexOutOfBoundsException( "visibility not in range" ); + } + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.java b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.java index 27b24dfb..0658df62 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.java +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.java @@ -10,6 +10,7 @@ import java.util.Comparator; import jp.juggler.subwaytooter.ActMain; import jp.juggler.subwaytooter.R; import jp.juggler.subwaytooter.table.SavedAccount; +import jp.juggler.subwaytooter.util.Utils; public class AccountPicker { @@ -21,12 +22,20 @@ public class AccountPicker { final ArrayList account_list = SavedAccount.loadAccountList(ActMain.log); + if( account_list == null || account_list.isEmpty() ){ + Utils.showToast(activity,false,R.string.account_empty); + return; + } + + if( account_list.size() == 1 ){ + callback.onAccountPicked(account_list.get(0)); + return; + } + Collections.sort( account_list, new Comparator< SavedAccount >() { @Override public int compare( SavedAccount o1, SavedAccount o2 ){ - int i = String.CASE_INSENSITIVE_ORDER.compare( o1.acct, o2.acct ); - if( i != 0 ) return i; - return String.CASE_INSENSITIVE_ORDER.compare( o1.host, o2.host ); + return String.CASE_INSENSITIVE_ORDER.compare( o1.user, o2.user ); } } ); @@ -34,7 +43,7 @@ public class AccountPicker { for(int i=0,ie=account_list.size();i map_name2unicode = EmojiMap._shortNameToUnicode; - private static final HashMap map_unicode2name = EmojiMap._unicodeToShortName; + public static final HashMap map_name2unicode = EmojiMap._shortNameToUnicode; + public static final HashMap map_unicode2name = EmojiMap._unicodeToShortName; static class DecodeEnv{ SpannableStringBuilder sb = new SpannableStringBuilder(); diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java index e7a91583..f4b3cf74 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.java @@ -1,6 +1,7 @@ package jp.juggler.subwaytooter.util; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; import java.io.ByteArrayInputStream; @@ -15,6 +16,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; +import android.content.ContextWrapper; import android.content.res.Resources; import android.os.Bundle; import android.os.Environment; @@ -31,6 +33,7 @@ import android.util.SparseBooleanArray; import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.view.View; import android.webkit.MimeTypeMap; import android.widget.Toast; @@ -797,4 +800,16 @@ public class Utils { return null; } + + public static Activity getActivityFromView( View view) { + Context context = view.getContext(); + while (context instanceof ContextWrapper ) { + if (context instanceof Activity) { + return (Activity)context; + } + context = ((ContextWrapper)context).getBaseContext(); + } + return null; + } + } diff --git a/app/src/main/res/drawable-hdpi/ic_bookmark.png b/app/src/main/res/drawable-hdpi/ic_bookmark.png new file mode 100644 index 00000000..17bfff74 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_bookmark.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_conversation.png b/app/src/main/res/drawable-hdpi/ic_conversation.png new file mode 100644 index 00000000..c6d53bfc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_conversation.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_knob.png b/app/src/main/res/drawable-hdpi/ic_knob.png new file mode 100644 index 00000000..5bc28b16 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_knob.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_locked_blue.png b/app/src/main/res/drawable-hdpi/ic_locked_blue.png new file mode 100644 index 00000000..4d16ef56 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_locked_blue.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_bookmark.png b/app/src/main/res/drawable-mdpi/ic_bookmark.png new file mode 100644 index 00000000..dc672be5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_bookmark.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_conversation.png b/app/src/main/res/drawable-mdpi/ic_conversation.png new file mode 100644 index 00000000..80e72248 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_conversation.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_knob.png b/app/src/main/res/drawable-mdpi/ic_knob.png new file mode 100644 index 00000000..5f0c78d7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_knob.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_locked_blue.png b/app/src/main/res/drawable-mdpi/ic_locked_blue.png new file mode 100644 index 00000000..728a798e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_locked_blue.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_bookmark.png b/app/src/main/res/drawable-xhdpi/ic_bookmark.png new file mode 100644 index 00000000..527833ba Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_bookmark.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_conversation.png b/app/src/main/res/drawable-xhdpi/ic_conversation.png new file mode 100644 index 00000000..fc8f6daa Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_conversation.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_knob.png b/app/src/main/res/drawable-xhdpi/ic_knob.png new file mode 100644 index 00000000..0b6186ad Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_knob.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_locked_blue.png b/app/src/main/res/drawable-xhdpi/ic_locked_blue.png new file mode 100644 index 00000000..2d65a2ed Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_locked_blue.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_bookmark.png b/app/src/main/res/drawable-xxhdpi/ic_bookmark.png new file mode 100644 index 00000000..b15970c6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_bookmark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_conversation.png b/app/src/main/res/drawable-xxhdpi/ic_conversation.png new file mode 100644 index 00000000..6fa5d0d4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_conversation.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_knob.png b/app/src/main/res/drawable-xxhdpi/ic_knob.png new file mode 100644 index 00000000..e46aa86c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_knob.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_locked_blue.png b/app/src/main/res/drawable-xxhdpi/ic_locked_blue.png new file mode 100644 index 00000000..a7425789 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_locked_blue.png differ diff --git a/app/src/main/res/drawable/list_item_selector.xml b/app/src/main/res/drawable/list_item_selector.xml new file mode 100644 index 00000000..bd710272 --- /dev/null +++ b/app/src/main/res/drawable/list_item_selector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/act_account_setting.xml b/app/src/main/res/layout/act_account_setting.xml index c662daf1..8961cf14 100644 --- a/app/src/main/res/layout/act_account_setting.xml +++ b/app/src/main/res/layout/act_account_setting.xml @@ -3,17 +3,7 @@ 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" > @@ -21,6 +11,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:padding="12dp" + android:paddingBottom="48dp" > @@ -39,6 +31,7 @@ /> + + +