diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0eace0e..6d78c91 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,14 +4,15 @@ plugins { android { namespace = "com.hi.music.player" - compileSdk = 33 + compileSdk = 34 defaultConfig { - applicationId = "com.hi.music.player" + //com.hi.music.player + applicationId = "com.hi.music.player.test" minSdk = 23 - targetSdk = 33 + targetSdk = 34 versionCode = 1 - versionName = "1.0" + versionName = "1.0.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -29,6 +30,9 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + buildFeatures { + viewBinding = true + } } dependencies { @@ -39,4 +43,12 @@ dependencies { testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") + + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.retrofit2:adapter-rxjava2:2.9.0") + implementation("io.reactivex.rxjava2:rxjava:2.2.21") + implementation("io.reactivex.rxjava2:rxandroid:2.1.1") + implementation ("com.squareup.okhttp3:logging-interceptor:4.11.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69d61f5..765b35a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + + diff --git a/app/src/main/java/com/hi/music/player/MainActivity.java b/app/src/main/java/com/hi/music/player/MainActivity.java deleted file mode 100644 index e2afe89..0000000 --- a/app/src/main/java/com/hi/music/player/MainActivity.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.hi.music.player; - -import androidx.appcompat.app.AppCompatActivity; - -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/hi/music/player/MusicApplication.java b/app/src/main/java/com/hi/music/player/MusicApplication.java new file mode 100644 index 0000000..4a96f4b --- /dev/null +++ b/app/src/main/java/com/hi/music/player/MusicApplication.java @@ -0,0 +1,13 @@ +package com.hi.music.player; + +import android.app.Application; + +public class MusicApplication extends Application { + + private final static MusicApplication sInstance = new MusicApplication(); + + + public static MusicApplication getInstance() { + return sInstance; + } +} diff --git a/app/src/main/java/com/hi/music/player/api/RequestListener.java b/app/src/main/java/com/hi/music/player/api/RequestListener.java new file mode 100644 index 0000000..79f7c9c --- /dev/null +++ b/app/src/main/java/com/hi/music/player/api/RequestListener.java @@ -0,0 +1,12 @@ +package com.hi.music.player.api; + +import com.hi.music.player.javabean.response.BaseResponse; + +import java.io.IOException; + +public interface RequestListener { + + void onFail(String errorMsg); + + void onSuccess(T data) ; +} diff --git a/app/src/main/java/com/hi/music/player/helper/BaseViewModelStoreOwner.java b/app/src/main/java/com/hi/music/player/helper/BaseViewModelStoreOwner.java new file mode 100644 index 0000000..e11b0f3 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/helper/BaseViewModelStoreOwner.java @@ -0,0 +1,24 @@ +package com.hi.music.player.helper; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModelStore; +import androidx.lifecycle.ViewModelStoreOwner; + +public class BaseViewModelStoreOwner implements ViewModelStoreOwner { + private final static BaseViewModelStoreOwner sInstance = new BaseViewModelStoreOwner(); + private ViewModelStore mAppViewModelStore; + + private BaseViewModelStoreOwner() { + } + + public static BaseViewModelStoreOwner getInstance() { + return sInstance; + } + + @NonNull + @Override + public ViewModelStore getViewModelStore() { + if (mAppViewModelStore == null) mAppViewModelStore = new ViewModelStore(); + return mAppViewModelStore; + } +} diff --git a/app/src/main/java/com/hi/music/player/helper/CommonUtils.java b/app/src/main/java/com/hi/music/player/helper/CommonUtils.java new file mode 100644 index 0000000..3bdb500 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/helper/CommonUtils.java @@ -0,0 +1,30 @@ +package com.hi.music.player.helper; + +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.ResponseBody; + +public class CommonUtils { + + private static String TAG = "----MUSIC---------"; + + public static void LogMsg(String msg) { + Log.d(TAG, msg); + } + + + + public static JSONObject toJsonObject(ResponseBody body) { + try { + String string = body.string(); + return new JSONObject(string); + } catch (IOException | JSONException exception) { + return null; + } + } +} diff --git a/app/src/main/java/com/hi/music/player/helper/ViewModelScope.java b/app/src/main/java/com/hi/music/player/helper/ViewModelScope.java new file mode 100644 index 0000000..ab25ec8 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/helper/ViewModelScope.java @@ -0,0 +1,31 @@ +package com.hi.music.player.helper; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import com.hi.music.player.MusicApplication; + +public class ViewModelScope { + private ViewModelProvider mFragmentProvider; + private ViewModelProvider mActivityProvider; + private ViewModelProvider mApplicationProvider; + + public T getFragmentScopeViewModel(@NonNull Fragment fragment, @NonNull Class modelClass) { + if (mFragmentProvider == null) mFragmentProvider = new ViewModelProvider(fragment); + return mFragmentProvider.get(modelClass); + } + + public T getActivityScopeViewModel(@NonNull AppCompatActivity activity, @NonNull Class modelClass) { + if (mActivityProvider == null) mActivityProvider = new ViewModelProvider(activity); + return mActivityProvider.get(modelClass); + } + + public T getApplicationScopeViewModel(@NonNull Class modelClass) { + if (mApplicationProvider == null) + mApplicationProvider = new ViewModelProvider(BaseViewModelStoreOwner.getInstance()); + return mApplicationProvider.get(modelClass); + } +} diff --git a/app/src/main/java/com/hi/music/player/javabean/requestbody/BodyHome.java b/app/src/main/java/com/hi/music/player/javabean/requestbody/BodyHome.java new file mode 100644 index 0000000..25bbe53 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/javabean/requestbody/BodyHome.java @@ -0,0 +1,28 @@ +package com.hi.music.player.javabean.requestbody; + +import java.io.Serializable; +import java.util.Locale; + + +/** + * 首页接口请求体 + */ +public class BodyHome implements Serializable { + + private String browseId = "FEmusic_home"; + + private Context context = new Context(); + + + private class Context implements Serializable{ + private Client client = new Client(); + } + + private class Client implements Serializable{ + private String clientName = "WEB_REMIX"; + private String clientVersion = "1.20220918"; + private String hl = Locale.getDefault().getLanguage(); + private String gl = "US"; + private String platform = "DESKTOP"; + } +} diff --git a/app/src/main/java/com/hi/music/player/javabean/response/BaseResponse.java b/app/src/main/java/com/hi/music/player/javabean/response/BaseResponse.java new file mode 100644 index 0000000..830b0bc --- /dev/null +++ b/app/src/main/java/com/hi/music/player/javabean/response/BaseResponse.java @@ -0,0 +1,32 @@ +package com.hi.music.player.javabean.response; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.MediaType; + +import okio.BufferedSource; +import retrofit2.Response; + +public class BaseResponse { + +// public JSONObject toJsonObject() { +// try { +// String string = this.string(); +// return new JSONObject(string); +// } catch (IOException | JSONException exception) { +// return null; +// } +// } + + + + + +} diff --git a/app/src/main/java/com/hi/music/player/javabean/response/ResponseHome.java b/app/src/main/java/com/hi/music/player/javabean/response/ResponseHome.java new file mode 100644 index 0000000..4ba5755 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/javabean/response/ResponseHome.java @@ -0,0 +1,5 @@ +package com.hi.music.player.javabean.response; + +public class ResponseHome { + private String backgroundUrl; +} diff --git a/app/src/main/java/com/hi/music/player/network/JsonHelper.java b/app/src/main/java/com/hi/music/player/network/JsonHelper.java new file mode 100644 index 0000000..349e4d1 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/network/JsonHelper.java @@ -0,0 +1,92 @@ +package com.hi.music.player.network; + +import com.hi.music.player.helper.CommonUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class JsonHelper { + + + + public static void ResolveHomeJson(JSONObject jsonObject){ + try { + String bgUrl = jsonObject + .getJSONObject("background") + .getJSONObject("musicThumbnailRenderer") + .getJSONObject("thumbnail") + .getJSONArray("thumbnails") + .getJSONObject(0) + .getString("url"); + + JSONArray contents = jsonObject + .getJSONObject("contents") + .getJSONObject("singleColumnBrowseResultsRenderer") + .getJSONArray("tabs") + .getJSONObject(0) + .getJSONObject("tabRenderer") + .getJSONObject("content") + .getJSONObject("sectionListRenderer") + .getJSONArray("contents"); + JSONObject responseContext = jsonObject + .getJSONObject("responseContext"); + JSONArray serviceTrackingParams = responseContext.getJSONArray("serviceTrackingParams"); + String visitorData = responseContext.getString("visitorData"); + + for (int i = 0; i < contents.length(); i++) { + JSONObject object = contents.getJSONObject(i); + + JSONObject musicCarouselShelfRenderer = object.optJSONObject("musicCarouselShelfRenderer"); + if(musicCarouselShelfRenderer!= null){ + String title = musicCarouselShelfRenderer + .getJSONObject("header") + .getJSONObject("musicCarouselShelfBasicHeaderRenderer") + .getJSONObject("title") + .getJSONArray("runs") + .getJSONObject(0) + .getString("text"); + CommonUtils.LogMsg("----------headertitle=" + title); + JSONArray childContents = musicCarouselShelfRenderer + .getJSONArray("contents"); + + + for (int j = 0; j < childContents.length(); j++) { + JSONObject jsonList = childContents.getJSONObject(j); + JSONObject musicResponsiveListItemRenderer = jsonList.optJSONObject("musicResponsiveListItemRenderer"); + if(musicResponsiveListItemRenderer!= null){ + String covert = musicResponsiveListItemRenderer + .getJSONObject("thumbnail") + .getJSONObject("musicThumbnailRenderer") + .getJSONObject("thumbnail") + .getJSONArray("thumbnails") + .getJSONObject(0) + .getString("url"); + + String text = musicResponsiveListItemRenderer.getJSONArray("flexColumns") + .getJSONObject(0) + .getJSONObject("musicResponsiveListItemFlexColumnRenderer") + .getJSONObject("text") + .getJSONArray("runs") + .getJSONObject(0) + .getString("text"); + + CommonUtils.LogMsg(" --------------text=" + text+"-covert="+covert); + } + } + } + + + + + + } + + + } catch (JSONException exception) { + CommonUtils.LogMsg("----------exception="); + exception.printStackTrace(); + + } + } +} diff --git a/app/src/main/java/com/hi/music/player/network/MusicApi.java b/app/src/main/java/com/hi/music/player/network/MusicApi.java new file mode 100644 index 0000000..0fcf012 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/network/MusicApi.java @@ -0,0 +1,20 @@ +package com.hi.music.player.network; +import com.hi.music.player.javabean.response.BaseResponse; + +import io.reactivex.Observable; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Response; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +public interface MusicApi { + + @POST("youtubei/v1/browse?prettyPrint=false") + @Headers("X-Goog-Api-Key:AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8") + Observable getHomeData(@Body RequestBody requestBody); + + + +} diff --git a/app/src/main/java/com/hi/music/player/network/ObserverWrapper.java b/app/src/main/java/com/hi/music/player/network/ObserverWrapper.java new file mode 100644 index 0000000..a518961 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/network/ObserverWrapper.java @@ -0,0 +1,39 @@ +package com.hi.music.player.network; + +import com.hi.music.player.api.RequestListener; +import com.hi.music.player.helper.CommonUtils; +import com.hi.music.player.javabean.response.BaseResponse; + +import io.reactivex.Observer; +import io.reactivex.disposables.Disposable; + +public class ObserverWrapper implements Observer { + private RequestListener requestListener; + + public ObserverWrapper(RequestListener requestListener) { + this.requestListener = requestListener; + } + + @Override + public void onSubscribe(Disposable d) { + CommonUtils.LogMsg("----------onSubscribe"); + } + + @Override + public void onNext(T t) { + CommonUtils.LogMsg("----------onNext"); + requestListener.onSuccess(t); + } + + @Override + public void onError(Throwable e) { + CommonUtils.LogMsg("----------onError"+e.getMessage()); + requestListener.onFail(e.getMessage()); + } + + @Override + public void onComplete() { + CommonUtils.LogMsg("----------onComplete"); + } +} + diff --git a/app/src/main/java/com/hi/music/player/network/RetrofitManager.java b/app/src/main/java/com/hi/music/player/network/RetrofitManager.java new file mode 100644 index 0000000..780d1fb --- /dev/null +++ b/app/src/main/java/com/hi/music/player/network/RetrofitManager.java @@ -0,0 +1,108 @@ +package com.hi.music.player.network; + +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.hi.music.player.api.RequestListener; +import com.hi.music.player.helper.CommonUtils; +import com.hi.music.player.javabean.requestbody.BodyHome; +import com.hi.music.player.javabean.response.BaseResponse; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +public class RetrofitManager { + + private String base_Host = "https://music.youtube.com/"; + private static volatile RetrofitManager REQUEST_MANAGER; + + private Retrofit retrofit; + public MediaType JSON = MediaType.get("application/json; charset=utf-8"); + private MusicApi musicApi; + + private RetrofitManager() { + + musicApi = getRetrofit().create(MusicApi.class); + } + + + // public T getService(Class cls){ +// return getRetrofit().create(cls); +// } + private synchronized Retrofit getRetrofit() { + if (retrofit == null) { + long DEFAULT_TIMEOUT = 10; + HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); + httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .addInterceptor(httpLoggingInterceptor) + .build(); + retrofit = new Retrofit.Builder() + .baseUrl(base_Host) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + } + return retrofit; + } + + public static RetrofitManager getInstance() { + if (REQUEST_MANAGER == null) { + synchronized (RetrofitManager.class) { //锁,防止线程问题 + if (REQUEST_MANAGER == null) { + REQUEST_MANAGER = new RetrofitManager(); + } + } + } + return REQUEST_MANAGER; + } + + + public void getHomeData() { + BodyHome bodyHome = new BodyHome(); + Gson gson = new Gson(); + String s = gson.toJson(bodyHome); + RequestBody requestBody = RequestBody.Companion.create(s, JSON); +// RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), s); + musicApi.getHomeData(requestBody) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new ObserverWrapper(new RequestListener() { + + @Override + public void onFail(String errorMsg) { + + } + + @Override + public void onSuccess(ResponseBody data) { + JSONObject jsonObject = CommonUtils.toJsonObject(data); + if (jsonObject != null) { + JsonHelper.ResolveHomeJson(jsonObject); + } + + } + })); + } + + +} diff --git a/app/src/main/java/com/hi/music/player/ui/BaseActivity.java b/app/src/main/java/com/hi/music/player/ui/BaseActivity.java new file mode 100644 index 0000000..6787586 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/ui/BaseActivity.java @@ -0,0 +1,67 @@ +package com.hi.music.player.ui; + +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsControllerCompat; +import androidx.lifecycle.ViewModel; +import androidx.viewbinding.ViewBinding; + +import com.hi.music.player.helper.ViewModelScope; + +public abstract class BaseActivity extends AppCompatActivity { + + private final ViewModelScope mViewModelScope = new ViewModelScope(); + + protected T vb; + + private Window window; + private View decorView; + protected View mView; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + vb = getViewBinding(); + setContentView(vb.getRoot()); + window = getWindow(); + decorView = window.getDecorView(); + mView = decorView.getRootView(); + setStatusBar(); + if (isFullScreen()) + initFullScreen(); + onCreateInit(); + } + protected abstract T getViewBinding(); + + protected abstract void onCreateInit(); + + public abstract boolean isFullScreen(); + + public abstract boolean statusBarLight(); + + private void setStatusBar() { + //深色模式 + WindowInsetsControllerCompat insetsController = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()); + insetsController.setAppearanceLightStatusBars(statusBarLight()); + } + + private void initFullScreen() { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + + } + + protected K getActivityScopeViewModel(@NonNull Class modelClass) { + return mViewModelScope.getActivityScopeViewModel(this, modelClass); + } + + protected K getApplicationScopeViewModel(@NonNull Class modelClass) { + return mViewModelScope.getApplicationScopeViewModel(modelClass); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hi/music/player/ui/BaseFragment.java b/app/src/main/java/com/hi/music/player/ui/BaseFragment.java new file mode 100644 index 0000000..fc1bba7 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/ui/BaseFragment.java @@ -0,0 +1,52 @@ +package com.hi.music.player.ui; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModel; +import androidx.viewbinding.ViewBinding; + +import com.hi.music.player.helper.ViewModelScope; + + +public abstract class BaseFragment extends Fragment { + private final ViewModelScope mViewModelScope = new ViewModelScope(); + protected T Vb; + + protected AppCompatActivity mActivity; + public BaseFragment() { + + } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mActivity = (AppCompatActivity) context; + } + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Vb = getFragmentVb(); + initView(); + return Vb.getRoot(); + } + + protected abstract T getFragmentVb(); + protected abstract void initView(); + + protected K getFragmentScopeViewModel(@NonNull Class modelClass) { + return mViewModelScope.getFragmentScopeViewModel(this, modelClass); + } + protected K getActivityScopeViewModel(@NonNull Class modelClass) { + return mViewModelScope.getActivityScopeViewModel(mActivity, modelClass); + } + + protected K getApplicationScopeViewModel(@NonNull Class modelClass) { + return mViewModelScope.getApplicationScopeViewModel(modelClass); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hi/music/player/ui/MainActivity.java b/app/src/main/java/com/hi/music/player/ui/MainActivity.java new file mode 100644 index 0000000..a7b64d2 --- /dev/null +++ b/app/src/main/java/com/hi/music/player/ui/MainActivity.java @@ -0,0 +1,38 @@ +package com.hi.music.player.ui; + +import android.view.View; + +import com.hi.music.player.databinding.ActivityMainBinding; +import com.hi.music.player.network.RetrofitManager; +import com.hi.music.player.ui.BaseActivity; + +public class MainActivity extends BaseActivity { + + +// EdgeToEdge.enable(this); + @Override + protected ActivityMainBinding getViewBinding() { + return ActivityMainBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onCreateInit() { + vb.tv.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + RetrofitManager.getInstance().getHomeData(); + } + }); + + } + + @Override + public boolean isFullScreen() { + return false; + } + + @Override + public boolean statusBarLight() { + return false; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_base.xml b/app/src/main/res/layout/activity_base.xml new file mode 100644 index 0000000..d9642c5 --- /dev/null +++ b/app/src/main/res/layout/activity_base.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 17eab17..c66375e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,12 +4,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context=".ui.MainActivity">