1
0
mirror of https://github.com/tateisu/SubwayTooter synced 2025-01-27 09:11:23 +01:00

v0.0.7: フォロー一覧/フォロワー一覧。アプリ情報。OSSライセンス。

This commit is contained in:
tateisu 2017-04-24 20:04:56 +09:00
parent 4b4fb44c7d
commit cc4bf691aa
41 changed files with 971 additions and 315 deletions

View File

@ -4,6 +4,7 @@
<w>dont</w>
<w>emoji</w>
<w>emojione</w>
<w>enty</w>
<w>favourited</w>
<w>hashtag</w>
<w>noto</w>

View File

@ -9,8 +9,8 @@ android {
applicationId "jp.juggler.subwaytooter"
minSdkVersion 21
targetSdkVersion 25
versionCode 6
versionName "0.0.6"
versionCode 7
versionName "0.0.7"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -5,19 +5,19 @@
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".App1"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name=".App1"
>
<activity
android:name=".ActMain"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan|stateAlwaysHidden"
>
<intent-filter>
@ -53,8 +53,20 @@
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"
/>
<activity
android:name=".ActAbout"
android:label="@string/app_about"
android:theme="@style/AppTheme"
/>
<activity
android:name=".ActOSSLicense"
android:label="@string/oss_license"
android:theme="@style/AppTheme"
/>
<meta-data android:name="android.max_aspect" android:value="ratio_float"/>
<meta-data
android:name="android.max_aspect"
android:value="ratio_float"/>
</application>
</manifest>

View File

@ -0,0 +1,55 @@
package jp.juggler.subwaytooter;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class ActAbout extends AppCompatActivity {
static final String url_store = "https://play.google.com/store/apps/details?id=jp.juggler.subwaytooter";
static final String url_enty = "https://enty.jp/3WtlzHG10wZv";
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
setContentView( R.layout.act_about );
try{
PackageInfo pInfo = getPackageManager().getPackageInfo( getPackageName(), 0 );
( (TextView) findViewById( R.id.tvVersion ) ).setText( getString( R.string.version_is, pInfo.versionName ) );
}catch( PackageManager.NameNotFoundException ex ){
ex.printStackTrace();
}
Button b = (Button) findViewById( R.id.btnRate );
b.setText( url_store );
b.setOnClickListener( new View.OnClickListener() {
@Override public void onClick( View v ){
open_browser( url_store );
}
} );
b = (Button) findViewById( R.id.btnDonate );
b.setText( url_enty );
b.setOnClickListener( new View.OnClickListener() {
@Override public void onClick( View v ){
open_browser( url_enty );
}
} );
}
void open_browser( String url ){
try{
Intent intent = new Intent( Intent.ACTION_VIEW, Uri.parse( url ) );
startActivity( intent );
}catch( Throwable ex ){
ex.printStackTrace();
}
}
}

View File

@ -77,7 +77,7 @@ public class ActAccountSetting extends AppCompatActivity implements View.OnClick
private void loadUIFromData( SavedAccount a ){
tvInstance.setText( a.host );
tvUser.setText( a.user );
tvUser.setText( a.acct );
String sv = a.visibility;
if( sv != null ){

View File

@ -42,6 +42,7 @@ 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.TootRelationShip;
import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.dialog.AccountPicker;
import jp.juggler.subwaytooter.dialog.LoginForm;
@ -238,7 +239,12 @@ public class ActMain extends AppCompatActivity
}else if( id == R.id.nav_add_tl_search ){
performAddTimeline( Column.TYPE_SEARCH );
}else if( id == R.id.nav_app_about ){
openAppAbout();
}else if( id == R.id.nav_oss_license ){
openOSSLicense( );
// Handle the camera action
// }else if( id == R.id.nav_gallery ){
//
@ -257,6 +263,7 @@ public class ActMain extends AppCompatActivity
return true;
}
ViewPager pager;
ColumnPagerAdapter pager_adapter;
View llEmpty;
@ -442,12 +449,12 @@ public class ActMain extends AppCompatActivity
AccountPicker.pick( this, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
switch(type){
switch( type ){
default:
addColumn( ai, type, ai.id );
break;
case Column.TYPE_SEARCH:
addColumn( ai,type, "",false);
addColumn( ai, type, "", false );
break;
}
}
@ -615,7 +622,7 @@ public class ActMain extends AppCompatActivity
for( SavedAccount a : account_list ){
final SavedAccount _a = a;
dialog.addAction(
getString( R.string.open_in_account, a.user )
getString( R.string.open_in_account, a.acct )
, new Runnable() {
@Override
public void run(){
@ -632,7 +639,7 @@ public class ActMain extends AppCompatActivity
{
final SavedAccount _a = access_info;
dialog.addAction(
getString( R.string.open_in_account, access_info.user )
getString( R.string.open_in_account, access_info.acct )
, new Runnable() {
@Override
public void run(){
@ -687,7 +694,7 @@ public class ActMain extends AppCompatActivity
private void showColumnMatchAccount( SavedAccount account ){
for( Column column : pager_adapter.column_list ){
if( account.user.equals( column.access_info.user ) ){
if( account.acct.equals( column.access_info.acct ) ){
column.fireVisualCallback();
}
}
@ -1010,7 +1017,12 @@ public class ActMain extends AppCompatActivity
////////////////////////////////////////////////////////////////////////////
private void callFollow( final SavedAccount account, final TootAccount who, final boolean bFollow ){
public interface RelationChangedCallback {
// void onRelationChanged( TootRelationShip relationship );
void onRelationChanged( );
}
private void callFollow( final SavedAccount access_info, final TootAccount who, final boolean bFollow, final RelationChangedCallback callback ){
new AsyncTask< Void, Void, TootApiResult >() {
@Override
@ -1026,7 +1038,7 @@ public class ActMain extends AppCompatActivity
}
} );
client.setAccount( account );
client.setAccount( access_info );
TootApiResult result;
if( bFollow & who.acct.contains( "@" ) ){
@ -1036,6 +1048,17 @@ public class ActMain extends AppCompatActivity
, "uri=" + Uri.encode( who.acct )
) );
result = client.request( "/api/v1/follows", request_builder );
if( result != null ){
if( result.object != null ){
remote_who = TootAccount.parse( log, access_info, result.object );
Utils.showToast( ActMain.this, false, bFollow ? R.string.follow_succeeded : R.string.unfollow_succeeded );
}else if( bFollow && who.locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user );
}else{
Utils.showToast( ActMain.this, false, result.error );
}
}
}else{
Request.Builder request_builder = new Request.Builder().post(
RequestBody.create(
@ -1044,24 +1067,53 @@ public class ActMain extends AppCompatActivity
) );
result = client.request( "/api/v1/accounts/" + who.id + ( bFollow ? "/follow" : "/unfollow" )
, request_builder );
}
if( result != null ){
if( result.object != null ){
Utils.showToast( ActMain.this, false, bFollow ? R.string.follow_succeeded : R.string.unfollow_succeeded );
}else if( bFollow && who.locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user );
}else{
Utils.showToast( ActMain.this, false, result.error );
if( result != null ){
if( result.object != null ){
relation = TootRelationShip.parse( log, result.object );
Utils.showToast( ActMain.this, false, bFollow ? R.string.follow_succeeded : R.string.unfollow_succeeded );
}else if( bFollow && who.locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user );
}else{
Utils.showToast( ActMain.this, false, result.error );
}
}
}
return result;
}
TootRelationShip relation;
TootAccount remote_who;
@Override
protected void onCancelled( TootApiResult result ){
onPostExecute( null );
}
@Override
protected void onPostExecute( TootApiResult result ){
// if( relation != null ){
// App1.relationship_map.put( access_info, relation );
// if( callback != null ) callback.onRelationChanged( relation );
// }else if( remote_who != null ){
// App1.relationship_map.addFollowing( access_info, remote_who.id );
// if( callback != null )
// callback.onRelationChanged( App1.relationship_map.get( access_info, remote_who.id ) );
// }
if( relation != null ){
if( callback != null ) callback.onRelationChanged( );
}else if( remote_who != null ){
if( callback != null )
callback.onRelationChanged( );
}
}
}.execute();
}
// acct で指定したユーザをリモートフォローする
void callRemoteFollow( final SavedAccount access_info, final String acct, final boolean locked ){
void callRemoteFollow( final SavedAccount access_info, final String acct, final boolean locked, final RelationChangedCallback callback ){
new AsyncTask< Void, Void, TootApiResult >() {
@ -1089,6 +1141,7 @@ public class ActMain extends AppCompatActivity
if( result != null ){
if( result.object != null ){
who = TootAccount.parse( log, access_info, result.object );
Utils.showToast( ActMain.this, false, R.string.follow_succeeded );
}else if( locked && result.response.code() == 422 ){
Utils.showToast( ActMain.this, false, R.string.cant_follow_locked_user );
@ -1099,11 +1152,31 @@ public class ActMain extends AppCompatActivity
return result;
}
TootAccount who;
@Override
protected void onCancelled( TootApiResult result ){
onPostExecute( null );
}
@Override
protected void onPostExecute( TootApiResult result ){
if( who != null ){
// App1.relationship_map.addFollowing( access_info, who.id );
// if( callback != null )
// callback.onRelationChanged( App1.relationship_map.get( access_info, who.id ) );
if( callback != null )
callback.onRelationChanged( );
}
}
}.execute();
}
// アカウントを選択してからユーザをフォローする
void followFromAnotherAccount( final SavedAccount access_info, final TootAccount who ){
void followFromAnotherAccount( final SavedAccount access_info, final TootAccount who, final RelationChangedCallback callback ){
AccountPicker.pick( ActMain.this, new AccountPicker.AccountPickerCallback() {
@Override
public void onAccountPicked( SavedAccount ai ){
@ -1111,14 +1184,14 @@ public class ActMain extends AppCompatActivity
if( ! acct.contains( "@" ) ){
acct = acct + "@" + access_info.host;
}
callRemoteFollow( ai, acct, who.locked );
callRemoteFollow( ai, acct, who.locked, callback );
}
} );
}
////////////////////////////////////////
private void callMute( final SavedAccount account, final TootAccount who, final boolean bMute ){
private void callMute( final SavedAccount access_info, final TootAccount who, final boolean bMute, final RelationChangedCallback callback ){
new AsyncTask< Void, Void, TootApiResult >() {
@Override
@ -1134,7 +1207,7 @@ public class ActMain extends AppCompatActivity
}
} );
client.setAccount( account );
client.setAccount( access_info );
Request.Builder request_builder = new Request.Builder().post(
RequestBody.create(
@ -1145,24 +1218,44 @@ public class ActMain extends AppCompatActivity
, request_builder );
if( result != null ){
if( result.object != null ){
relation = TootRelationShip.parse( log, result.object );
Utils.showToast( ActMain.this, false, bMute ? R.string.mute_succeeded : R.string.unmute_succeeded );
if( bMute ){
for( Column column : pager_adapter.column_list ){
column.removeStatusByAccount( account, who.id );
}
showColumnMatchAccount( account );
}
}else{
Utils.showToast( ActMain.this, false, result.error );
}
}
return result;
}
TootRelationShip relation;
@Override
protected void onCancelled( TootApiResult result ){
onPostExecute( null );
}
@Override
protected void onPostExecute( TootApiResult result ){
if( relation != null ){
// App1.relationship_map.put( access_info, relation );
// if( callback != null ) callback.onRelationChanged( relation );
if( callback != null ) callback.onRelationChanged( );
if( bMute ){
for( Column column : pager_adapter.column_list ){
column.removeStatusByAccount( access_info, who.id );
}
}
showColumnMatchAccount( access_info );
}
}
}.execute();
}
private void callBlock( final SavedAccount account, final TootAccount who, final boolean bBlock ){
private void callBlock( final SavedAccount access_info, final TootAccount who, final boolean bBlock, final RelationChangedCallback callback ){
new AsyncTask< Void, Void, TootApiResult >() {
@Override
@ -1178,7 +1271,7 @@ public class ActMain extends AppCompatActivity
}
} );
client.setAccount( account );
client.setAccount( access_info );
Request.Builder request_builder = new Request.Builder().post(
RequestBody.create(
@ -1189,13 +1282,8 @@ public class ActMain extends AppCompatActivity
, request_builder );
if( result != null ){
if( result.object != null ){
relation = TootRelationShip.parse( log, result.object );
Utils.showToast( ActMain.this, false, bBlock ? R.string.block_succeeded : R.string.unblock_succeeded );
if( bBlock ){
for( Column column : pager_adapter.column_list ){
column.removeStatusByAccount( account, who.id );
}
showColumnMatchAccount( account );
}
}else{
Utils.showToast( ActMain.this, false, result.error );
}
@ -1203,6 +1291,40 @@ public class ActMain extends AppCompatActivity
return result;
}
TootRelationShip relation;
TootAccount remote_who;
@Override
protected void onCancelled( TootApiResult result ){
onPostExecute( null );
}
@Override
protected void onPostExecute( TootApiResult result ){
boolean bOK = false;
if( relation != null ){
bOK = true;
// App1.relationship_map.put( access_info, relation );
// if( callback != null ) callback.onRelationChanged( relation );
if( callback != null ) callback.onRelationChanged( );
}else if( remote_who != null ){
bOK = true;
// App1.relationship_map.addFollowing( access_info, remote_who.id );
// if( callback != null )
// callback.onRelationChanged( App1.relationship_map.get( access_info, remote_who.id ) );
if( callback != null )
callback.onRelationChanged( );
}
if( bOK ){
if( bBlock ){
for( Column column : pager_adapter.column_list ){
column.removeStatusByAccount( access_info, who.id );
}
}
showColumnMatchAccount( access_info );
}
}
}.execute();
}
@ -1285,88 +1407,95 @@ public class ActMain extends AppCompatActivity
////////////////////////////////////////////////
final RelationChangedCallback follow_comolete_callback = new RelationChangedCallback() {
// @Override public void onRelationChanged( TootRelationShip relationship ){
// Utils.showToast( ActMain.this,false,R.string.follow_succeeded );
// }
@Override public void onRelationChanged( ){
Utils.showToast( ActMain.this,false,R.string.follow_succeeded );
}
};
// ステータスのmoreメニュー
public void openStatusMoreMenu( final SavedAccount access_info, final TootStatus status ){
ActionsDialog dialog = new ActionsDialog();
final ArrayList<SavedAccount> tmp_list = new ArrayList<>();
for( SavedAccount a : SavedAccount.loadAccountList( log )){
if( a.host.equalsIgnoreCase( access_info.host )){
final ArrayList< SavedAccount > tmp_list = new ArrayList<>();
for( SavedAccount a : SavedAccount.loadAccountList( log ) ){
if( a.host.equalsIgnoreCase( access_info.host ) ){
tmp_list.add( a );
}
}
if( ! tmp_list.isEmpty() ){
dialog.addAction( getString( R.string. favourite_from_another_account), new Runnable() {
dialog.addAction( getString( R.string.favourite_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if(ai!= null) performFavourite( ai,status );
if( ai != null ) performFavourite( ai, status );
}
} );
}
} );
dialog.addAction( getString( R.string. boost_from_another_account), new Runnable() {
dialog.addAction( getString( R.string.boost_from_another_account ), new Runnable() {
@Override public void run(){
AccountPicker.pick( ActMain.this, tmp_list, new AccountPicker.AccountPickerCallback() {
@Override public void onAccountPicked( SavedAccount ai ){
if(ai!= null) performBoost( ai,status ,false);
if( ai != null ) performBoost( ai, status, false );
}
} );
}
} );
}
dialog.addAction( getString( R.string. follow), new Runnable() {
dialog.addAction( getString( R.string.follow ), new Runnable() {
@Override public void run(){
callFollow( access_info, status.account, true );
callFollow( access_info, status.account, true ,null);
}
} );
dialog.addAction( getString( R.string. follow_from_another_account), new Runnable() {
dialog.addAction( getString( R.string.follow_from_another_account ), new Runnable() {
@Override public void run(){
followFromAnotherAccount( access_info, status.account );
followFromAnotherAccount( access_info, status.account ,follow_comolete_callback);
}
} );
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override public void run(){
callFollow( access_info, status.account, false );
callFollow( access_info, status.account, false,null );
}
} );
dialog.addAction( getString( R.string. mute), new Runnable() {
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, true );
callMute( access_info, status.account, true ,null);
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override public void run(){
callMute( access_info, status.account, false );
callMute( access_info, status.account, false,null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, true );
callBlock( access_info, status.account, true ,null);
}
} );
dialog.addAction( getString( R.string. unblock), new Runnable() {
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, status.account, false );
callBlock( access_info, status.account, false ,null);
}
} );
dialog.addAction( getString( R.string. report), new Runnable() {
dialog.addAction( getString( R.string.report ), new Runnable() {
@Override public void run(){
openReportForm( access_info, status.account, status );
}
} );
dialog.addAction( getString( R.string. open_web_page), new Runnable() {
dialog.addAction( getString( R.string.open_web_page ), new Runnable() {
@Override public void run(){
// 強制的にブラウザで開く
openChromeTab( access_info, status.url, true );
}
} );
dialog.show(this, null );
dialog.show( this, null );
}
@ -1380,39 +1509,39 @@ public class ActMain extends AppCompatActivity
} );
dialog.addAction( getString( R.string.follow ), new Runnable() {
@Override public void run(){
callFollow( access_info, who, true );
callFollow( access_info, who, true ,null);
}
} );
dialog.addAction( getString( R.string.follow_from_another_account ), new Runnable() {
@Override public void run(){
followFromAnotherAccount( access_info, who );
followFromAnotherAccount( access_info, who,follow_comolete_callback );
}
} );
dialog.addAction( getString( R.string.unfollow ), new Runnable() {
@Override
public void run(){
callFollow( access_info, who, false );
callFollow( access_info, who, false ,null);
}
} );
dialog.addAction( getString( R.string.mute ), new Runnable() {
@Override public void run(){
callMute( access_info, who, true );
callMute( access_info, who, true ,null);
}
} );
dialog.addAction( getString( R.string.unmute ), new Runnable() {
@Override
public void run(){
callMute( access_info, who, false );
callMute( access_info, who, false,null );
}
} );
dialog.addAction( getString( R.string.block ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, true );
callBlock( access_info, who, true ,null);
}
} );
dialog.addAction( getString( R.string.unblock ), new Runnable() {
@Override public void run(){
callBlock( access_info, who, false );
callBlock( access_info, who, false ,null);
}
} );
dialog.addAction( getString( R.string.report ), new Runnable() {
@ -1422,4 +1551,14 @@ public class ActMain extends AppCompatActivity
} );
dialog.show( this, null );
}
private void openOSSLicense(){
startActivity( new Intent(this,ActOSSLicense.class) );
}
private void openAppAbout(){
startActivity( new Intent(this,ActAbout.class) );
}
}

View File

@ -0,0 +1,38 @@
package jp.juggler.subwaytooter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import jp.juggler.subwaytooter.util.Utils;
public class ActOSSLicense extends AppCompatActivity{
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
setContentView( R.layout.act_oss_license);
try{
InputStream is = getResources().openRawResource( R.raw.oss_license );
try{
ByteArrayOutputStream bao = new ByteArrayOutputStream( );
IOUtils.copy( is,bao );
String text = Utils.decodeUTF8(bao.toByteArray());
TextView tv = (TextView) findViewById( R.id.tvText );
tv.setText(text);
}finally{
IOUtils.closeQuietly( is );
}
}catch(Throwable ex){
ex.printStackTrace( );
}
}
}

View File

@ -445,7 +445,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
}
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;
caption_list[ i ] = tmp_account_list.get( i ).acct;
}
new AlertDialog.Builder( this )

View File

@ -151,4 +151,7 @@ public class App1 extends Application {
public static final OkHttpClient ok_http_client = new OkHttpClient();
public static Typeface typeface_emoji ;
// public static final RelationshipMap relationship_map = new RelationshipMap();
}

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v4.os.AsyncTaskCompat;
import android.text.TextUtils;
@ -21,7 +22,6 @@ 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.TootResults;
@ -40,9 +40,29 @@ class Column {
return params[ idx ];
}
private static final String PATH_HOME = "/api/v1/timelines/home?limit=80";
private static final String PATH_LOCAL = "/api/v1/timelines/public?limit=80&local=1";
private static final String PATH_FEDERATE = "/api/v1/timelines/public?limit=80";
private static final String PATH_FAVOURITES = "/api/v1/favourites?limit=80";
private static final String PATH_REPORTS = "/api/v1/reports?limit=80";
private static final String PATH_NOTIFICATIONS = "/api/v1/notifications?limit=80";
private static final String PATH_ACCOUNT = "/api/v1/accounts/%d?limit=80"; // 1:account_id
private static final String PATH_ACCOUNT_STATUSES = "/api/v1/accounts/%d/statuses?limit=80"; // 1:account_id
private static final String PATH_ACCOUNT_FOLLOWING = "/api/v1/accounts/%d/following?limit=80"; // 1:account_id
private static final String PATH_ACCOUNT_FOLLOWERS = "/api/v1/accounts/%d/followers?limit=80"; // 1:account_id
private static final String PATH_HASHTAG = "/api/v1/timelines/tag/%s?limit=80"; // 1: hashtag(url encoded)
private static final String PATH_STATUSES = "/api/v1/statuses/%d"; // 1:status_id
private static final String PATH_STATUSES_CONTEXT = "/api/v1/statuses/%d/context"; // 1:status_id
private static final String PATH_SEARCH = "/api/v1/search?limit=80&q=%s"; // 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
private static final String KEY_ACCOUNT_ROW_ID = "account_id";
private static final String KEY_TYPE = "type";
private static final String KEY_WHO_ID = "who_id";
private static final String KEY_PROFILE_ID = "profile_id";
private static final String KEY_PROFILE_TAB = "tab";
private static final String KEY_STATUS_ID = "status_id";
private static final String KEY_HASHTAG = "hashtag";
private static final String KEY_SEARCH_QUERY = "search_query";
@ -68,7 +88,12 @@ class Column {
static final int TYPE_HASHTAG = 9;
static final int TYPE_SEARCH = 10;
private long who_id;
private long profile_id;
volatile TootAccount who_account;
int profile_tab = TAB_STATUS;
static final int TAB_STATUS = 0;
static final int TAB_FOLLOWING = 1;
static final int TAB_FOLLOWERS = 2;
private long status_id;
@ -77,12 +102,11 @@ class Column {
String search_query;
boolean search_resolve;
int profile_tab = 0;
int scroll_pos;
int scroll_y;
Column( ActMain activity, SavedAccount access_info, int type, Object... params ){
Column( ActMain activity, @NonNull SavedAccount access_info, int type, Object... params ){
this.activity = activity;
this.access_info = access_info;
this.type = type;
@ -91,7 +115,7 @@ class Column {
this.status_id = (Long) getParamAt( params, 0 );
break;
case TYPE_PROFILE:
this.who_id = (Long) getParamAt( params, 0 );
this.profile_id = (Long) getParamAt( params, 0 );
break;
case TYPE_HASHTAG:
this.hashtag = (String) getParamAt( params, 0 );
@ -113,7 +137,8 @@ class Column {
item.put( KEY_STATUS_ID, status_id );
break;
case TYPE_PROFILE:
item.put( KEY_WHO_ID, who_id );
item.put( KEY_PROFILE_ID, profile_id );
item.put( KEY_PROFILE_TAB, profile_tab );
break;
case TYPE_HASHTAG:
item.put( KEY_HASHTAG, hashtag );
@ -121,10 +146,11 @@ class Column {
case TYPE_SEARCH:
item.put( KEY_SEARCH_QUERY, search_query );
item.put( KEY_SEARCH_RESOLVE, search_resolve );
break;
}
// 以下は保存には必要ないがカラムリスト画面で使う
item.put( KEY_COLUMN_ACCESS, access_info.user );
item.put( KEY_COLUMN_ACCESS, access_info.acct );
item.put( KEY_COLUMN_NAME, getColumnName( true ) );
item.put( KEY_OLD_INDEX, old_index );
}
@ -139,7 +165,8 @@ class Column {
this.status_id = src.optLong( KEY_STATUS_ID );
break;
case TYPE_PROFILE:
this.who_id = src.optLong( KEY_WHO_ID );
this.profile_id = src.optLong( KEY_PROFILE_ID );
this.profile_tab = src.optInt( KEY_PROFILE_TAB );
break;
case TYPE_HASHTAG:
this.hashtag = src.optString( KEY_HASHTAG );
@ -174,7 +201,7 @@ class Column {
case TYPE_PROFILE:
return activity.getString( R.string.statuses_of
, who_account != null ? access_info.getFullAcct( who_account ) : Long.toString( who_id )
, who_account != null ? access_info.getFullAcct( who_account ) : Long.toString( profile_id )
);
case TYPE_FAVOURITES:
@ -201,7 +228,7 @@ class Column {
}
boolean isSameSpec( SavedAccount ai, int type, Object[] params ){
if( type != this.type || ! Utils.equalsNullable( ai.user, access_info.user ) ) return false;
if( type != this.type || ! Utils.equalsNullable( ai.acct, access_info.acct ) ) return false;
switch( type ){
default:
return true;
@ -209,7 +236,7 @@ class Column {
case TYPE_PROFILE:
try{
long who_id = (Long) getParamAt( params, 0 );
return who_id == this.who_id;
return who_id == this.profile_id;
}catch( Throwable ex ){
return false;
}
@ -249,18 +276,26 @@ class Column {
// ブーストやお気に入りの更新に使うステータスを列挙する
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 ){
if( target_account.acct.equals( access_info.acct ) ){
for( int i = 0, ie = list_data.size() ; i < ie ; ++ i ){
Object data = list_data.get( i );
//
TootStatus status = status_list.get( i );
if( target_status_id == status.id ){
callback.onIterate( status );
if( data instanceof TootNotification ){
data = ( (TootNotification) data ).status;
}
//
TootStatus reblog = status.reblog;
if( reblog != null ){
if( target_status_id == reblog.id ){
callback.onIterate( reblog );
if( data instanceof TootStatus ){
//
TootStatus status = (TootStatus) data;
if( target_status_id == status.id ){
callback.onIterate( status );
}
//
TootStatus reblog = status.reblog;
if( reblog != null ){
if( target_status_id == reblog.id ){
callback.onIterate( reblog );
}
}
}
}
@ -269,35 +304,33 @@ class Column {
// ミュートブロックが成功した時に呼ばれる
void removeStatusByAccount( SavedAccount target_account, long who_id ){
if( ! target_account.user.equals( access_info.user ) ) return;
if( ! target_account.acct.equals( access_info.acct ) ) return;
{
// remove from status_list
TootStatus.List tmp_list = new TootStatus.List( status_list.size() );
for( TootStatus status : status_list ){
if( status.account.id == who_id
|| ( status.reblog != null && status.reblog.account.id == who_id )
){
continue;
}
tmp_list.add( status );
}
status_list.clear();
status_list.addAll( tmp_list );
}
{
// remove from notification_list
TootNotification.List tmp_list = new TootNotification.List( notification_list.size() );
for( TootNotification item : notification_list ){
if( item.account.id == who_id ) continue;
if( item.status != null ){
if( item.status.account.id == who_id ) continue;
if( item.status.reblog != null && item.status.reblog.account.id == who_id )
ArrayList< Object > tmp_list = new ArrayList<>( list_data.size() );
for( Object o : list_data ){
if( o instanceof TootStatus ){
TootStatus item = (TootStatus) o;
if( item.account.id == who_id
|| ( item.reblog != null && item.reblog.account.id == who_id )
){
continue;
}
}
tmp_list.add( item );
if( o instanceof TootNotification ){
TootNotification item = (TootNotification) o;
if( item.account.id == who_id ) continue;
if( item.status != null ){
if( item.status.account.id == who_id ) continue;
if( item.status.reblog != null && item.status.reblog.account.id == who_id )
continue;
}
}
tmp_list.add( o );
}
notification_list.clear();
notification_list.addAll( tmp_list );
list_data.clear();
list_data.addAll( tmp_list );
}
}
@ -358,36 +391,14 @@ class Column {
String task_progress;
final TootStatus.List status_list = new TootStatus.List();
final TootNotification.List notification_list = new TootNotification.List();
final TootReport.List report_list = new TootReport.List();
final ArrayList< Object > result_list = new ArrayList<>();
volatile TootAccount who_account;
final ArrayList< Object > list_data = new ArrayList<>();
void reload(){
status_list.clear();
notification_list.clear();
report_list.clear();
result_list.clear();
list_data.clear();
startLoading();
}
private static final String PATH_HOME = "/api/v1/timelines/home?limit=80";
private static final String PATH_LOCAL = "/api/v1/timelines/public?limit=80&local=1";
private static final String PATH_FEDERATE = "/api/v1/timelines/public?limit=80";
private static final String PATH_FAVOURITES = "/api/v1/favourites?limit=80";
private static final String PATH_REPORTS = "/api/v1/reports?limit=80";
private static final String PATH_NOTIFICATIONS = "/api/v1/notifications?limit=80";
private static final String PATH_PROFILE1_FORMAT = "/api/v1/accounts/%d?limit=80"; // 1:account_id
private static final String PATH_PROFILE2_FORMAT = "/api/v1/accounts/%d/statuses?limit=80"; // 1:account_id
private static final String PATH_HASHTAG_FORMAT = "/api/v1/timelines/tag/%s?limit=80"; // 1: tashtag(url encoded)
private static final String PATH_CONVERSATION1_FORMAT = "/api/v1/statuses/%d"; // 1:status_id
private static final String PATH_CONVERSATION2_FORMAT = "/api/v1/statuses/%d/context"; // 1:status_id
private static final String PATH_SEARCH_FORMAT = "/api/v1/search?limit=80&q=%s"; // 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
private void startLoading(){
cancelLastTask();
@ -402,23 +413,29 @@ class Column {
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;
ArrayList< Object > tmp_list_result;
TootApiResult parseStatuses( TootApiResult result ){
TootApiResult parseAccount1( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
tmp_list_status = TootStatus.parseList( log, access_info, result.array );
who_account = TootAccount.parse( log, access_info, result.object );
}
return result;
}
TootApiResult parseAccount( TootApiResult result ){
ArrayList< Object > list_tmp;
TootApiResult parseStatuses( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
who_account = TootAccount.parse( log, access_info, result.object );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootStatus.parseList( log, access_info, result.array ) );
}
return result;
}
TootApiResult parseAccountList( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootAccount.parseList( log, access_info, result.array ) );
}
return result;
}
@ -426,7 +443,8 @@ class Column {
TootApiResult parseReports( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
tmp_list_report = TootReport.parseList( log, result.array );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootReport.parseList( log, result.array ) );
}
return result;
}
@ -434,7 +452,8 @@ class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, true, true );
tmp_list_notification = TootNotification.parseList( log, access_info, result.array );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootNotification.parseList( log, access_info, result.array ) );
}
return result;
}
@ -478,20 +497,31 @@ class Column {
case TYPE_PROFILE:
if( who_account == null ){
parseAccount( client.request(
String.format( Locale.JAPAN, PATH_PROFILE1_FORMAT, who_id ) ) );
parseAccount1( client.request(
String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) ) );
client.callback.publishApiProgress( "" );
}
return parseStatuses( client.request(
String.format( Locale.JAPAN, PATH_PROFILE2_FORMAT, who_id ) ) );
switch( profile_tab ){
default:
case TAB_STATUS:
return parseStatuses( client.request(
String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id ) ) );
case TAB_FOLLOWING:
return parseAccountList( client.request(
String.format( Locale.JAPAN, PATH_ACCOUNT_FOLLOWING, profile_id ) ) );
case TAB_FOLLOWERS:
return parseAccountList( client.request(
String.format( Locale.JAPAN, PATH_ACCOUNT_FOLLOWERS, profile_id ) ) );
}
case TYPE_FAVOURITES:
return parseStatuses( client.request( PATH_FAVOURITES ) );
case TYPE_HASHTAG:
return parseStatuses( client.request(
String.format( Locale.JAPAN, PATH_HASHTAG_FORMAT, Uri.encode( hashtag ) ) ) );
String.format( Locale.JAPAN, PATH_HASHTAG, Uri.encode( hashtag ) ) ) );
case TYPE_REPORTS:
return parseReports( client.request( PATH_REPORTS ) );
@ -503,28 +533,28 @@ class Column {
// 指定された発言そのもの
result = client.request(
String.format( Locale.JAPAN, PATH_CONVERSATION1_FORMAT, status_id ) );
String.format( Locale.JAPAN, PATH_STATUSES, status_id ) );
if( result == null || result.object == null ) return result;
TootStatus target_status = TootStatus.parse( log, access_info, result.object );
target_status.conversation_main = true;
// 前後の会話
result = client.request(
String.format( Locale.JAPAN, PATH_CONVERSATION2_FORMAT, status_id ) );
String.format( Locale.JAPAN, PATH_STATUSES_CONTEXT, status_id ) );
if( result == null || result.object == null ) return result;
// 一つのリストにまとめる
TootContext context = TootContext.parse( log, access_info, 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 );
list_tmp = new ArrayList<>( 1 + context.ancestors.size() + context.descendants.size() );
if( context.ancestors != null ) list_tmp.addAll( context.ancestors );
list_tmp.add( target_status );
if( context.descendants != null ) list_tmp.addAll( context.descendants );
//
return result;
case TYPE_SEARCH:
String path = String.format( Locale.JAPAN, PATH_SEARCH_FORMAT, Uri.encode( search_query ) );
String path = String.format( Locale.JAPAN, PATH_SEARCH, Uri.encode( search_query ) );
if( search_resolve ) path = path + "&resolve=1";
result = client.request( path );
@ -532,10 +562,10 @@ class Column {
TootResults tmp = TootResults.parse( log, access_info, result.object );
if( tmp != null ){
tmp_list_result = new ArrayList<>();
tmp_list_result.addAll( tmp.accounts );
tmp_list_result.addAll( tmp.hashtags );
tmp_list_result.addAll( tmp.statuses );
list_tmp = new ArrayList<>();
list_tmp.addAll( tmp.hashtags );
list_tmp.addAll( tmp.accounts );
list_tmp.addAll( tmp.statuses );
}
return result;
@ -560,28 +590,10 @@ class Column {
if( result.error != null ){
Column.this.mInitialLoadingError = result.error;
}else{
switch( type ){
default:
case TYPE_HOME:
case TYPE_LOCAL:
case TYPE_FEDERATE:
case TYPE_PROFILE:
case TYPE_FAVOURITES:
case TYPE_CONVERSATION:
case TYPE_HASHTAG:
initList( status_list, tmp_list_status );
break;
case TYPE_REPORTS:
initList( report_list, tmp_list_report );
break;
case TYPE_NOTIFICATIONS:
initList( notification_list, tmp_list_notification );
break;
case TYPE_SEARCH:
initList( result_list, tmp_list_result );
if( list_tmp != null ){
list_data.clear();
list_data.addAll( list_tmp );
}
}
@ -592,11 +604,11 @@ class Column {
AsyncTaskCompat.executeParallel( task );
}
static final Pattern reMaxId = Pattern.compile( "&max_id=(\\d+)" ); // より古いデータの取得に使う
static final Pattern reSinceId = Pattern.compile( "&since_id=(\\d+)" ); // より新しいデータの取得に使う
private static final Pattern reMaxId = Pattern.compile( "&max_id=(\\d+)" ); // より古いデータの取得に使う
private static final Pattern reSinceId = Pattern.compile( "&since_id=(\\d+)" ); // より新しいデータの取得に使う
String max_id;
String since_id;
private String max_id;
private String since_id;
private void saveRange( TootApiResult result, boolean bBottom, boolean bTop ){
// Link: <https://mastodon.juggler.jp/api/v1/timelines/home?limit=80&max_id=405228>; rel="next",
@ -623,7 +635,7 @@ class Column {
}
}
String addRange( boolean bBottom, String path ){
private String addRange( boolean bBottom, String path ){
char delm = ( - 1 != path.indexOf( '?' ) ? '&' : '?' );
if( bBottom ){
if( max_id != null ) return path + delm + "max_id=" + max_id;
@ -633,34 +645,7 @@ class Column {
return path;
}
< T > 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 ){
boolean startRefresh( final boolean bBottom ){
if( last_task != null ){
log.d( "busy" );
return false;
@ -676,22 +661,29 @@ class Column {
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 ){
TootApiResult parseAccount1( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
tmp_list_status = TootStatus.parseList( log, access_info, result.array );
who_account = TootAccount.parse( log, access_info, result.object );
}
return result;
}
TootApiResult parseAccount( TootApiResult result ){
TootApiResult parseAccountList( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
who_account = TootAccount.parse( log, access_info, result.object );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootAccount.parseList( log, access_info, result.array ) );
}
return result;
}
ArrayList< Object > list_tmp;
TootApiResult parseStatuses( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootStatus.parseList( log, access_info, result.array ) );
}
return result;
}
@ -699,7 +691,8 @@ class Column {
TootApiResult parseReports( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
tmp_list_report = TootReport.parseList( log, result.array );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootReport.parseList( log, result.array ) );
}
return result;
}
@ -707,7 +700,8 @@ class Column {
TootApiResult parseNotifications( TootApiResult result ){
if( result != null ){
saveRange( result, bBottom, ! bBottom );
tmp_list_notification = TootNotification.parseList( log, access_info, result.array );
list_tmp = new ArrayList<>();
list_tmp.addAll( TootNotification.parseList( log, access_info, result.array ) );
}
return result;
}
@ -757,18 +751,30 @@ class Column {
case TYPE_PROFILE:
if( who_account == null ){
parseAccount( client.request(
String.format( Locale.JAPAN, PATH_PROFILE1_FORMAT, who_id ) ) );
parseAccount1( client.request(
String.format( Locale.JAPAN, PATH_ACCOUNT, profile_id ) ) );
client.callback.publishApiProgress( "" );
}
switch( profile_tab ){
default:
case TAB_STATUS:
return parseStatuses( client.request(addRange( bBottom,
String.format( Locale.JAPAN, PATH_ACCOUNT_STATUSES, profile_id ) ) ));
case TAB_FOLLOWING:
return parseAccountList( client.request(addRange( bBottom,
String.format( Locale.JAPAN, PATH_ACCOUNT_FOLLOWING, profile_id ) ) ));
case TAB_FOLLOWERS:
return parseAccountList( client.request(addRange( bBottom,
String.format( Locale.JAPAN, PATH_ACCOUNT_FOLLOWERS, profile_id ) ) ));
}
return parseStatuses( client.request( addRange( bBottom,
String.format( Locale.JAPAN, PATH_PROFILE2_FORMAT, who_id ) ) ) );
case TYPE_HASHTAG:
return parseStatuses( client.request( addRange( bBottom,
String.format( Locale.JAPAN, PATH_HASHTAG_FORMAT, Uri.encode( hashtag ) ) ) ) );
String.format( Locale.JAPAN, PATH_HASHTAG, Uri.encode( hashtag ) ) ) ) );
}
}
@ -789,27 +795,51 @@ class Column {
if( result.error != null ){
Column.this.mRefreshLoadingError = result.error;
}else{
switch( type ){
default:
case TYPE_HOME:
case TYPE_LOCAL:
case TYPE_FEDERATE:
case TYPE_PROFILE:
case TYPE_FAVOURITES:
case TYPE_HASHTAG:
mergeList( status_list, tmp_list_status, bBottom );
break;
case TYPE_REPORTS:
mergeList( report_list, tmp_list_report, bBottom );
break;
case TYPE_NOTIFICATIONS:
mergeList( notification_list, tmp_list_notification, bBottom );
break;
if( list_tmp != null ){
// 古いリストにある要素のIDの集合集合
HashSet< Long > set_status_id = new HashSet<>();
HashSet< Long > set_notification_id = new HashSet<>();
HashSet< Long > set_report_id = new HashSet<>();
HashSet< Long > set_account_id = new HashSet<>();
for( Object o : list_data ){
if( o instanceof TootStatus ){
set_status_id.add( ( (TootStatus) o ).id );
}else if( o instanceof TootNotification ){
set_notification_id.add( ( (TootNotification) o ).id );
}else if( o instanceof TootReport ){
set_report_id.add( ( (TootReport) o ).id );
}else if( o instanceof TootAccount ){
set_account_id.add( ( (TootAccount) o ).id );
}
}
ArrayList< Object > list_new = new ArrayList<>();
for( Object o : list_tmp ){
if( o instanceof TootStatus ){
if( set_status_id.contains( ( (TootStatus) o ).id ) ) continue;
}else if( o instanceof TootNotification ){
if( set_notification_id.contains( ( (TootNotification) o ).id ) )
continue;
}else if( o instanceof TootReport ){
if( set_report_id.contains( ( (TootReport) o ).id ) ) continue;
}else if( o instanceof TootAccount ){
if( set_account_id.contains( ( (TootAccount) o ).id ) ) continue;
}
list_new.add( o );
}
if( ! bBottom ){
// 新しいデータの後に今のデータが並ぶ
list_new.addAll( list_data );
list_data.clear();
list_data.addAll( list_new );
}else{
// 今のデータの後にさらに古いデータが続く
list_data.addAll( list_new );
}
}
}
fireVisualCallback();
}
};

View File

@ -1,6 +1,8 @@
package jp.juggler.subwaytooter;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
@ -32,6 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import jp.juggler.subwaytooter.api.entity.TootAccount;
import jp.juggler.subwaytooter.api.entity.TootAttachment;
import jp.juggler.subwaytooter.api.entity.TootNotification;
import jp.juggler.subwaytooter.api.entity.TootRelationShip;
import jp.juggler.subwaytooter.api.entity.TootStatus;
import jp.juggler.subwaytooter.table.ContentWarning;
import jp.juggler.subwaytooter.table.MediaShown;
@ -174,8 +177,16 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
tvLoading.setText( message );
}
// final RelationshipMap.UpdateCallback callback_relation = new RelationshipMap.UpdateCallback() {
// @Override public void onRelationShipUpdate(){
// onVisualColumn();
// }
// };
@Override
public void onVisualColumn(){
// App1.relationship_map.checkUpdate( activity, column.access_info,callback_relation );
tvColumnContext.setText( column.access_info.getFullAcct( column.access_info ) );
tvColumnName.setText( column.getColumnName( false ) );
@ -209,51 +220,12 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
}
}
switch( column.type ){
default:
case Column.TYPE_HOME:
case Column.TYPE_LOCAL:
case Column.TYPE_FEDERATE:
case Column.TYPE_FAVOURITES:
case Column.TYPE_PROFILE:
if( column.status_list.isEmpty() && vh_header == null ){
showError( activity.getString( R.string.list_empty ) );
}else{
tvLoading.setVisibility( View.GONE );
swipyRefreshLayout.setVisibility( View.VISIBLE );
status_adapter.set( column.status_list );
}
break;
case Column.TYPE_REPORTS:
if( column.report_list.isEmpty() ){
showError( activity.getString( R.string.list_empty ) );
}else{
tvLoading.setVisibility( View.GONE );
swipyRefreshLayout.setVisibility( View.VISIBLE );
status_adapter.set( column.report_list );
}
break;
case Column.TYPE_NOTIFICATIONS:
if( column.notification_list.isEmpty() ){
showError( activity.getString( R.string.list_empty ) );
}else{
tvLoading.setVisibility( View.GONE );
swipyRefreshLayout.setVisibility( View.VISIBLE );
status_adapter.set( column.notification_list );
}
break;
case Column.TYPE_SEARCH:
if( column.result_list.isEmpty() ){
showError( activity.getString( R.string.list_empty ) );
}else{
tvLoading.setVisibility( View.GONE );
swipyRefreshLayout.setVisibility( View.VISIBLE );
status_adapter.set( column.result_list );
}
break;
if( column. list_data.isEmpty() && vh_header == null ){
showError( activity.getString( R.string.list_empty ) );
}else{
tvLoading.setVisibility( View.GONE );
swipyRefreshLayout.setVisibility( View.VISIBLE );
status_adapter.set( column.list_data );
}
proc_restore_scroll.run();
@ -376,21 +348,29 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
@Override
public void onClick( View v ){
switch( v.getId() ){
case R.id.ivBackground:
if( who != null ){
// 強制的にブラウザで開く
activity.openChromeTab( access_info, who.url, true );
}
break;
case R.id.btnFollowing:
Utils.showToast( activity, false, "not implemented" );
column.profile_tab = Column.TAB_FOLLOWING;
column.reload();
break;
case R.id.btnFollowers:
Utils.showToast( activity, false, "not implemented" );
column.profile_tab = Column.TAB_FOLLOWERS;
column.reload();
break;
case R.id.btnStatusCount:
Utils.showToast( activity, false, "not implemented" );
column.profile_tab = Column.TAB_STATUS;
column.reload();
break;
case R.id.btnMore:
activity.openAccountMoreMenu( access_info, who );
break;
@ -663,6 +643,9 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
ivFollow.setImageUrl( who.avatar_static, App1.getImageLoader() );
tvFollowerName.setText( who.display_name );
tvFollowerAcct.setText( access_info.getFullAcct( who ) );
btnFollow.setImageResource( R.drawable.btn_follow );
}
private void showStatus( ActMain activity, View view, TootStatus status, SavedAccount account ){
@ -829,9 +812,11 @@ public class ColumnViewHolder implements View.OnClickListener, Column.VisualCall
break;
case R.id.btnFollow:
activity.openAccountMoreMenu( access_info, account_follow );
break;
case R.id.btnSearchTag:
activity.openHashTag( access_info,search_tag );
break;
}
}

View File

@ -0,0 +1,109 @@
//package jp.juggler.subwaytooter;
//
//import android.os.AsyncTask;
//import android.os.SystemClock;
//import android.support.annotation.NonNull;
//
//import java.util.HashMap;
//
//import jp.juggler.subwaytooter.api.TootApiClient;
//import jp.juggler.subwaytooter.api.TootApiResult;
//import jp.juggler.subwaytooter.api.entity.TootRelationShip;
//import jp.juggler.subwaytooter.table.SavedAccount;
//import jp.juggler.subwaytooter.util.LogCategory;
//
//class RelationshipMap {
//
// private static final LogCategory log = new LogCategory( "RelationshipMap" );
//
//
// interface UpdateCallback {
// void onRelationShipUpdate();
// }
//
// private static class RelationshipPerAccount extends HashMap< Long, TootRelationShip > {
// long last_update;
// }
//
// private final HashMap< String, RelationshipPerAccount > map_account = new HashMap<>();
//
// @NonNull
// private RelationshipPerAccount getRelationshipPerAccount( @NonNull SavedAccount access_info ){
// RelationshipPerAccount ra;
// ra = map_account.get( access_info.acct );
// if( ra == null ){
// ra = new RelationshipPerAccount();
// map_account.put( access_info.acct, ra );
// }
// return ra;
// }
//
// public TootRelationShip get( @NonNull SavedAccount access_info, long id ){
// return getRelationshipPerAccount( access_info ).get( id );
// }
//
// public void put( @NonNull SavedAccount access_info, @NonNull TootRelationShip relation ){
// getRelationshipPerAccount( access_info ).put( relation.id,relation );
// }
//
// public void addFollowing( SavedAccount access_info, long id ){
// RelationshipPerAccount ra = getRelationshipPerAccount( access_info );
// TootRelationShip rs = ra.get( id);
// if(rs == null ){
// rs = new TootRelationShip();
// ra.put( id, rs );
// }
// rs.following = true;
// }
//
// void checkUpdate( @NonNull final ActMain activity, @NonNull final SavedAccount access_info, final UpdateCallback callback ){
// final RelationshipPerAccount ra = getRelationshipPerAccount( access_info );
// long now = SystemClock.elapsedRealtime();
// if( now - ra.last_update < 300000L ) return;
// ra.last_update = now;
//
// new AsyncTask< Void, Void, TootApiResult >() {
//
// TootRelationShip.List list;
//
// @Override protected TootApiResult doInBackground( Void... params ){
//
// TootApiClient client = new TootApiClient( activity, new TootApiClient.Callback() {
// @Override public boolean isApiCancelled(){
// return isCancelled();
// }
//
// @Override
// public void publishApiProgress( final String s ){
// }
// } );
//
// client.setAccount( access_info );
// TootApiResult result = client.request( "/api/v1/accounts/relationships" );
// if( result != null && result.array != null ){
// list = TootRelationShip.parseList( log, result.array );
// }
// return result;
// }
//
// @Override
// protected void onCancelled( TootApiResult result ){
// onPostExecute( null );
// }
//
// @Override
// protected void onPostExecute( TootApiResult result ){
//
// if( isCancelled() || result == null ){
// return;
// }
// if( list != null ){
// for( TootRelationShip item : list ){
// ra.put( item.id, item );
// }
// callback.onRelationShipUpdate();
// }
// }
// }.execute();
// }
//}

View File

@ -1,7 +1,10 @@
package jp.juggler.subwaytooter.api.entity;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import jp.juggler.subwaytooter.util.LogCategory;
public class TootRelationShip {
@ -42,4 +45,27 @@ public class TootRelationShip {
}
}
public static class List extends ArrayList< TootRelationShip > {
public List(){
super();
}
public List( int capacity ){
super( capacity );
}
}
public static List parseList( LogCategory log, JSONArray array ){
List result = new List();
if( array != null ){
for( int i = array.length() - 1 ; i >= 0 ; -- i ){
JSONObject src = array.optJSONObject( i );
if( src == null ) continue;
TootRelationShip item = parse( log, src );
if( item != null ) result.add( 0, item );
}
}
return result;
}
}

View File

@ -38,7 +38,7 @@ public class AccountPicker {
Collections.sort( account_list, new Comparator< SavedAccount >() {
@Override
public int compare( SavedAccount o1, SavedAccount o2 ){
return String.CASE_INSENSITIVE_ORDER.compare( o1.user, o2.user );
return String.CASE_INSENSITIVE_ORDER.compare( o1.acct, o2.acct );
}
} );
@ -46,7 +46,7 @@ public class AccountPicker {
for(int i=0,ie=account_list.size();i<ie;++i){
SavedAccount ai = account_list.get(i);
caption_list[i] = ai.user;
caption_list[i] = ai.acct;
}
new AlertDialog.Builder(activity)

View File

@ -35,7 +35,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
// login information
public long db_id = INVALID_ID;
public String host;
public String user;
public String acct; // user@host
public JSONObject token_info;
public String visibility;
public boolean confirm_boost;
@ -73,7 +73,7 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
if( dst != null){
dst.db_id = cursor.getLong( cursor.getColumnIndex( COL_ID ) );
dst.host = cursor.getString( cursor.getColumnIndex( COL_HOST ) );
dst.user = cursor.getString( cursor.getColumnIndex( COL_USER ) );
dst.acct = cursor.getString( cursor.getColumnIndex( COL_USER ) );
int colIdx_visibility = cursor.getColumnIndex( COL_VISIBILITY );
dst.visibility = cursor.isNull( colIdx_visibility )? null : cursor.getString( colIdx_visibility );
@ -87,11 +87,11 @@ public class SavedAccount extends TootAccount implements LinkClickContext{
}
public static long insert( String host, String user, JSONObject account,JSONObject token ){
public static long insert( String host, String acct, JSONObject account,JSONObject token ){
try{
ContentValues cv = new ContentValues();
cv.put( COL_HOST, host );
cv.put( COL_USER, user );
cv.put( COL_USER, acct );
cv.put( COL_ACCOUNT, account.toString() );
cv.put( COL_TOKEN, token.toString() );
return App1.getDB().insert( table, null, cv );

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:scrollbarStyle="outsideOverlay"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="32sp"
/>
<TextView
android:id="@+id/tvVersion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="version 0.0.0"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0.2"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_rate"
/>
<Button
android:id="@+id/btnRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent"
android:padding="8dp"
android:text="@string/rate_on_store"
android:textAllCaps="false"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0.2"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_donate"
/>
<Button
android:id="@+id/btnDonate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent"
android:padding="8dp"
tools:text="enty...."
android:textAllCaps="false"
/>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true"
android:padding="12dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tvText"
>
</TextView>
</ScrollView>

View File

@ -79,6 +79,16 @@
android:id="@+id/nav_app_setting"
android:icon="@drawable/ic_setting"
android:title="@string/app_setting"/>
<item
android:id="@+id/nav_app_about"
android:icon="@drawable/ic_info"
android:title="@string/app_about"/>
<item
android:id="@+id/nav_oss_license"
android:icon="@drawable/ic_info"
android:title="@string/oss_license"/>
</menu>
</item>
</group>

View File

@ -0,0 +1,119 @@
====================================================
Ranks/emojione
https://github.com/Ranks/emojione
EmojiOne Artwork
- Applies to all PNG files found in the emojione-assets repo as well as any adaptations made.
- Free license: emojione.com/developers/free-license
- Premium license: emojione.com/developers/premium-license
EmojiOne Non-Artwork
- Applies to the Javascript, JSON, PHP, CSS, HTML files, and everything else not covered under the artwork license above, found in both the emojione and emojione-assets repos.
- License: MIT
- Complete Legal Terms: http://opensource.org/licenses/MIT
====================================================
google/volley
https://github.com/google/volley
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
====================================================
square/okhttp
https://github.com/square/okhttp
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
====================================================
Commons IO
https://commons.apache.org/proper/commons-io/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
====================================================
chrisjenx/Calligraphy
https://github.com/chrisjenx/Calligraphy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
====================================================
woxblom/DragListView
https://github.com/woxblom/DragListView
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
====================================================
omadahealth/SwipyRefreshLayout
https://github.com/omadahealth/SwipyRefreshLayout
The MIT License (MIT)
Copyright (c) 2015 OrangeGangsters
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -138,4 +138,10 @@
<string name="follow_from_another_account">別のアカウントでフォロー</string>
<string name="favourite_from_another_account">別のアカウントでお気に入り</string>
<string name="boost_from_another_account">別のアカウントでブースト</string>
<string name="app_about">このアプリについて</string>
<string name="please_rate">ストアで評価やレビューをお願いします!</string>
<string name="rate_on_store">ストアで評価</string>
<string name="please_donate">開発継続のために寄付をお願いします!</string>
<string name="version_is">バージョン %1$s</string>
<string name="oss_license">OSSライセンス</string>
</resources>

View File

@ -137,4 +137,10 @@
<string name="confirm_close_column">close thic colum?</string>
<string name="favourite_from_another_account">favourite from another account</string>
<string name="boost_from_another_account">boost from another account</string>
<string name="app_about">about this app</string>
<string name="oss_license">OSS license</string>
<string name="please_rate">Please rating and write review!</string>
<string name="rate_on_store">rate on store</string>
<string name="please_donate">Please support this app!</string>
<string name="version_is">version %1$s</string>
</resources>